Skip to content

Commit

Permalink
Issue #26470: Port ssl and hashlib module to OpenSSL 1.1.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
tiran committed Sep 5, 2016
1 parent 3445827 commit c2fc7c4
Show file tree
Hide file tree
Showing 6 changed files with 342 additions and 135 deletions.
51 changes: 49 additions & 2 deletions Doc/library/ssl.rst
Expand Up @@ -322,6 +322,16 @@ purposes.
Random generation
^^^^^^^^^^^^^^^^^

.. deprecated::

2.7.13 OpenSSL has deprecated :func:`ssl.RAND_pseudo_bytes`, use
:func:`ssl.RAND_bytes` instead.

.. deprecated::

2.7.13 OpenSSL has deprecated :func:`ssl.RAND_pseudo_bytes`, use
:func:`ssl.RAND_bytes` instead.

.. function:: RAND_status()

Return ``True`` if the SSL pseudo-random number generator has been seeded
Expand All @@ -340,7 +350,7 @@ Random generation
See http://egd.sourceforge.net/ or http://prngd.sourceforge.net/ for sources
of entropy-gathering daemons.

Availability: not available with LibreSSL.
Availability: not available with LibreSSL and OpenSSL > 1.1.0

.. function:: RAND_add(bytes, entropy)

Expand Down Expand Up @@ -444,6 +454,9 @@ Certificate handling
* :attr:`openssl_capath_env` - OpenSSL's environment key that points to a capath,
* :attr:`openssl_capath` - hard coded path to a capath directory

Availability: LibreSSL ignores the environment vars
:attr:`openssl_cafile_env` and :attr:`openssl_capath_env`

.. versionadded:: 2.7.9

.. function:: enum_certificates(store_name)
Expand Down Expand Up @@ -561,11 +574,19 @@ Constants

.. versionadded:: 2.7.10

.. data:: PROTOCOL_SSLv23
.. data:: PROTOCOL_TLS

Selects the highest protocol version that both the client and server support.
Despite the name, this option can select "TLS" protocols as well as "SSL".

.. versionadded:: 2.7.13

.. data:: PROTOCOL_SSLv23

Alias for ``PROTOCOL_TLS``.

.. deprecated:: 2.7.13 Use ``PROTOCOL_TLS`` instead.

.. data:: PROTOCOL_SSLv2

Selects SSL version 2 as the channel encryption protocol.
Expand All @@ -577,6 +598,8 @@ Constants

SSL version 2 is insecure. Its use is highly discouraged.

.. deprecated:: 2.7.13 OpenSSL has removed support for SSLv2.

.. data:: PROTOCOL_SSLv3

Selects SSL version 3 as the channel encryption protocol.
Expand All @@ -588,17 +611,32 @@ Constants

SSL version 3 is insecure. Its use is highly discouraged.

.. deprecated:: 2.7.13

OpenSSL has deprecated all version specific protocols. Use the default
protocol with flags like ``OP_NO_SSLv3`` instead.

.. data:: PROTOCOL_TLSv1

Selects TLS version 1.0 as the channel encryption protocol.

.. deprecated:: 2.7.13

OpenSSL has deprecated all version specific protocols. Use the default
protocol with flags like ``OP_NO_SSLv3`` instead.

.. data:: PROTOCOL_TLSv1_1

Selects TLS version 1.1 as the channel encryption protocol.
Available only with openssl version 1.0.1+.

.. versionadded:: 2.7.9

.. deprecated:: 2.7.13

OpenSSL has deprecated all version specific protocols. Use the default
protocol with flags like ``OP_NO_SSLv3`` instead.

.. data:: PROTOCOL_TLSv1_2

Selects TLS version 1.2 as the channel encryption protocol. This is the
Expand All @@ -607,6 +645,12 @@ Constants

.. versionadded:: 2.7.9

.. deprecated:: 2.7.13

OpenSSL has deprecated all version specific protocols. Use the default
protocol with flags like ``OP_NO_SSLv3`` instead.


.. data:: OP_ALL

Enables workarounds for various bugs present in other SSL implementations.
Expand Down Expand Up @@ -1112,6 +1156,9 @@ to speed up repeated connections from the same clients.
This method will raise :exc:`NotImplementedError` if :data:`HAS_ALPN` is
False.

OpenSSL 1.1.0+ will abort the handshake and raise :exc:`SSLError` when
both sides support ALPN but cannot agree on a protocol.

.. versionadded:: 2.7.10

.. method:: SSLContext.set_npn_protocols(protocols)
Expand Down
16 changes: 10 additions & 6 deletions Lib/ssl.py
Expand Up @@ -51,6 +51,7 @@
PROTOCOL_SSLv2
PROTOCOL_SSLv3
PROTOCOL_SSLv23
PROTOCOL_TLS
PROTOCOL_TLSv1
PROTOCOL_TLSv1_1
PROTOCOL_TLSv1_2
Expand Down Expand Up @@ -126,7 +127,10 @@ def _import_symbols(prefix):

from _ssl import _OPENSSL_API_VERSION

_PROTOCOL_NAMES = {value: name for name, value in globals().items() if name.startswith('PROTOCOL_')}
_PROTOCOL_NAMES = {value: name for name, value in globals().items()
if name.startswith('PROTOCOL_')
and name != 'PROTOCOL_SSLv23'}
PROTOCOL_SSLv23 = PROTOCOL_TLS

try:
_SSLv2_IF_EXISTS = PROTOCOL_SSLv2
Expand Down Expand Up @@ -408,7 +412,7 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None,
if not isinstance(purpose, _ASN1Object):
raise TypeError(purpose)

context = SSLContext(PROTOCOL_SSLv23)
context = SSLContext(PROTOCOL_TLS)

# SSLv2 considered harmful.
context.options |= OP_NO_SSLv2
Expand Down Expand Up @@ -445,7 +449,7 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None,
context.load_default_certs(purpose)
return context

def _create_unverified_context(protocol=PROTOCOL_SSLv23, cert_reqs=None,
def _create_unverified_context(protocol=PROTOCOL_TLS, cert_reqs=None,
check_hostname=False, purpose=Purpose.SERVER_AUTH,
certfile=None, keyfile=None,
cafile=None, capath=None, cadata=None):
Expand Down Expand Up @@ -518,7 +522,7 @@ class SSLSocket(socket):

def __init__(self, sock=None, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_SSLv23, ca_certs=None,
ssl_version=PROTOCOL_TLS, ca_certs=None,
do_handshake_on_connect=True,
family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
suppress_ragged_eofs=True, npn_protocols=None, ciphers=None,
Expand Down Expand Up @@ -920,7 +924,7 @@ def version(self):

def wrap_socket(sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_SSLv23, ca_certs=None,
ssl_version=PROTOCOL_TLS, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
ciphers=None):
Expand Down Expand Up @@ -989,7 +993,7 @@ def PEM_cert_to_DER_cert(pem_cert_string):
d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)]
return base64.decodestring(d.encode('ASCII', 'strict'))

def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv23, ca_certs=None):
def get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None):
"""Retrieve the certificate from the server at the specified address,
and return it as a PEM-encoded string.
If 'ca_certs' is specified, validate the server cert against it.
Expand Down
66 changes: 40 additions & 26 deletions Lib/test/test_ssl.py
Expand Up @@ -26,6 +26,9 @@

PROTOCOLS = sorted(ssl._PROTOCOL_NAMES)
HOST = support.HOST
IS_LIBRESSL = ssl.OPENSSL_VERSION.startswith('LibreSSL')
IS_OPENSSL_1_1 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0)


def data_file(*name):
return os.path.join(os.path.dirname(__file__), *name)
Expand Down Expand Up @@ -164,7 +167,6 @@ def test_constants(self):
self.assertIn(ssl.HAS_SNI, {True, False})
self.assertIn(ssl.HAS_ECDH, {True, False})


def test_random(self):
v = ssl.RAND_status()
if support.verbose:
Expand Down Expand Up @@ -281,9 +283,9 @@ def test_openssl_version(self):
self.assertGreaterEqual(status, 0)
self.assertLessEqual(status, 15)
# Version string as returned by {Open,Libre}SSL, the format might change
if "LibreSSL" in s:
self.assertTrue(s.startswith("LibreSSL {:d}.{:d}".format(major, minor)),
(s, t))
if IS_LIBRESSL:
self.assertTrue(s.startswith("LibreSSL {:d}".format(major)),
(s, t, hex(n)))
else:
self.assertTrue(s.startswith("OpenSSL {:d}.{:d}.{:d}".format(major, minor, fix)),
(s, t))
Expand Down Expand Up @@ -742,15 +744,15 @@ def test_ciphers(self):
def test_options(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
# OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value
self.assertEqual(ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3,
ctx.options)
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
if not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0):
default |= ssl.OP_NO_COMPRESSION
self.assertEqual(default, ctx.options)
ctx.options |= ssl.OP_NO_TLSv1
self.assertEqual(ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1,
ctx.options)
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
if can_clear_options():
ctx.options = (ctx.options & ~ssl.OP_NO_SSLv2) | ssl.OP_NO_TLSv1
self.assertEqual(ssl.OP_ALL | ssl.OP_NO_TLSv1 | ssl.OP_NO_SSLv3,
ctx.options)
ctx.options = (ctx.options & ~ssl.OP_NO_TLSv1)
self.assertEqual(default, ctx.options)
ctx.options = 0
self.assertEqual(0, ctx.options)
else:
Expand Down Expand Up @@ -1088,6 +1090,7 @@ def test_load_default_certs(self):
self.assertRaises(TypeError, ctx.load_default_certs, 'SERVER_AUTH')

@unittest.skipIf(sys.platform == "win32", "not-Windows specific")
@unittest.skipIf(IS_LIBRESSL, "LibreSSL doesn't support env vars")
def test_load_default_certs_env(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
with support.EnvironmentVarGuard() as env:
Expand Down Expand Up @@ -1534,7 +1537,6 @@ def _test_get_server_certificate(host, port, cert=None):
sys.stdout.write("%s\n" % x)
else:
self.fail("Got server certificate %s for %s:%s!" % (pem, host, port))

pem = ssl.get_server_certificate((host, port),
ca_certs=cert)
if not pem:
Expand Down Expand Up @@ -2783,7 +2785,7 @@ def test_version_basic(self):
with closing(context.wrap_socket(socket.socket())) as s:
self.assertIs(s.version(), None)
s.connect((HOST, server.port))
self.assertEqual(s.version(), "TLSv1")
self.assertEqual(s.version(), 'TLSv1')
self.assertIs(s.version(), None)

@unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
Expand Down Expand Up @@ -2925,24 +2927,36 @@ def test_alpn_protocols(self):
(['http/3.0', 'http/4.0'], None)
]
for client_protocols, expected in protocol_tests:
server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
server_context.load_cert_chain(CERTFILE)
server_context.set_alpn_protocols(server_protocols)
client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
client_context.load_cert_chain(CERTFILE)
client_context.set_alpn_protocols(client_protocols)
stats = server_params_test(client_context, server_context,
chatty=True, connectionchatty=True)

msg = "failed trying %s (s) and %s (c).\n" \
"was expecting %s, but got %%s from the %%s" \
% (str(server_protocols), str(client_protocols),
str(expected))
client_result = stats['client_alpn_protocol']
self.assertEqual(client_result, expected, msg % (client_result, "client"))
server_result = stats['server_alpn_protocols'][-1] \
if len(stats['server_alpn_protocols']) else 'nothing'
self.assertEqual(server_result, expected, msg % (server_result, "server"))
try:
stats = server_params_test(client_context,
server_context,
chatty=True,
connectionchatty=True)
except ssl.SSLError as e:
stats = e

if expected is None and IS_OPENSSL_1_1:
# OpenSSL 1.1.0 raises handshake error
self.assertIsInstance(stats, ssl.SSLError)
else:
msg = "failed trying %s (s) and %s (c).\n" \
"was expecting %s, but got %%s from the %%s" \
% (str(server_protocols), str(client_protocols),
str(expected))
client_result = stats['client_alpn_protocol']
self.assertEqual(client_result, expected,
msg % (client_result, "client"))
server_result = stats['server_alpn_protocols'][-1] \
if len(stats['server_alpn_protocols']) else 'nothing'
self.assertEqual(server_result, expected,
msg % (server_result, "server"))

def test_selected_npn_protocol(self):
# selected_npn_protocol() is None unless NPN is used
Expand Down
2 changes: 2 additions & 0 deletions Misc/NEWS
Expand Up @@ -36,6 +36,8 @@ Core and Builtins
Library
-------

- Issue #26470: Port ssl and hashlib module to OpenSSL 1.1.0.

- Issue #27944: Fix some memory-corruption bugs in the log reading code of the
_hotshot module.

Expand Down

0 comments on commit c2fc7c4

Please sign in to comment.