Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Doc/library/hashlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ More condensed:

Using :func:`new` with an algorithm provided by OpenSSL:

>>> h = hashlib.new('ripemd160')
>>> h = hashlib.new('sha256')
>>> h.update(b"Nobody inspects the spammish repetition")
>>> h.hexdigest()
'cc4a5ce1b3df48aec5d22d1f16b894a0b894eccc'
'031edd7d41651593c5fe5c006fa5752b37fddff7bc4e843aa6af0c950f4b9406'

Hashlib provides the following constant attributes:

Expand Down
41 changes: 41 additions & 0 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ probably additional platforms, as long as OpenSSL is installed on that platform.
cause variations in behavior. For example, TLSv1.1 and TLSv1.2 come with
openssl version 1.0.1.

.. note::

This build of the module requires OpenSSL 1.0.2 or newer and supports
OpenSSL 3. When built against OpenSSL 3, only the OpenSSL 3.0 API is
used. Notable differences with OpenSSL 3:

* OpenSSL 3 refuses TLS 1.0 and TLS 1.1 at the default security level.
:data:`PROTOCOL_TLSv1` and :data:`PROTOCOL_TLSv1_1` contexts only
work after lowering it, e.g. with
``context.set_ciphers("@SECLEVEL=0:ALL")``.
* :data:`PROTOCOL_TLSv1`, :data:`PROTOCOL_TLSv1_1` and
:data:`PROTOCOL_TLSv1_2` contexts are created from the generic TLS
method with both the minimum and maximum protocol version set to the
requested version. Like the version-specific methods used with
older OpenSSL, this overrides a ``MinProtocol`` set in the system-wide
OpenSSL configuration.
* :data:`OP_IGNORE_UNEXPECTED_EOF` is enabled by default.
* Errors from :meth:`SSLContext.load_dh_params` are reported by the
``OSSL_DECODER`` library instead of ``PEM``.

.. warning::
Don't use this module without reading the :ref:`ssl-security`. Doing so
may lead to a false sense of security, as the default settings of the
Expand Down Expand Up @@ -844,6 +864,27 @@ Constants

.. versionadded:: 3.6

.. data:: OP_IGNORE_UNEXPECTED_EOF

Ignore unexpected shutdown of TLS connections: an EOF from the peer
without a TLS ``close_notify`` alert is treated like a regular
shutdown instead of raising :exc:`SSLEOFError`. This mirrors the
behaviour of OpenSSL 1.1.1 and is enabled by default on every
:class:`SSLContext`.

.. warning::

With this option an attacker able to close the connection can
truncate the data stream without being detected. Protocols that do
not delimit their messages themselves (e.g. HTTP/1.0 responses
without ``Content-Length``) should clear it:
``context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF``.

This option is only available with OpenSSL 3.0.0 and later.

.. versionadded:: 3.6.15
Backported for OpenSSL 3 support (added in Python 3.10).

.. data:: HAS_ALPN

Whether the OpenSSL library has built-in support for the *Application-Layer
Expand Down
51 changes: 51 additions & 0 deletions Lib/test/support/hashlib_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import functools
import hashlib
import unittest

try:
import _hashlib
except ImportError:
_hashlib = None


def requires_hashdigest(digestname, openssl=None):
"""Decorator raising SkipTest if a hashing algorithm is not available

The hashing algorithm could be missing or blocked by a strict crypto
policy.

If 'openssl' is True, then the decorator checks that OpenSSL provides
the algorithm. Otherwise the check falls back to built-in
implementations.

ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
ValueError: unsupported hash type md4
"""
def decorator(func_or_class):
if isinstance(func_or_class, type):
setUpClass = func_or_class.__dict__.get('setUpClass')
if setUpClass is None:
def setUpClass(cls):
super(func_or_class, cls).setUpClass()
setUpClass.__qualname__ = func_or_class.__qualname__ + '.setUpClass'
setUpClass.__module__ = func_or_class.__module__
else:
setUpClass = setUpClass.__func__
setUpClass = classmethod(decorator(setUpClass))
func_or_class.setUpClass = setUpClass
return func_or_class

@functools.wraps(func_or_class)
def wrapper(*args, **kwargs):
try:
if openssl and _hashlib is not None:
_hashlib.new(digestname)
else:
hashlib.new(digestname)
except ValueError:
raise unittest.SkipTest(
"hash digest '{}' is not available.".format(digestname)
)
return func_or_class(*args, **kwargs)
return wrapper
return decorator
4 changes: 4 additions & 0 deletions Lib/test/test_ftplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ class SSLConnection(asyncore.dispatcher):

def secure_connection(self):
context = ssl.SSLContext()
# TODO: fix TLSv1.3 support (bpo-32947). With TLS 1.3 the
# server only learns about a client-side certificate rejection
# after the handshake, so test_check_hostname hangs.
context.options |= getattr(ssl, 'OP_NO_TLSv1_3', 0)
context.load_cert_chain(CERTFILE)
socket = context.wrap_socket(self.socket,
suppress_ragged_eofs=False,
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@
c_hashlib = import_fresh_module('hashlib', fresh=['_hashlib'])
py_hashlib = import_fresh_module('hashlib', blocked=['_hashlib'])


def get_fips_mode():
"""Return 1 if the kernel runs in FIPS mode, 0 otherwise.

Python 3.6's _hashlib has no get_fips_mode(); OpenSSL follows the
kernel setting on distributions that support FIPS; SUSE's OpenSSL
can also be forced into FIPS mode with OPENSSL_FORCE_FIPS_MODE.
"""
if os.environ.get('OPENSSL_FORCE_FIPS_MODE', '0') not in ('', '0'):
return 1
try:
with open('/proc/sys/crypto/fips_enabled') as f:
return int(f.read().strip() or 0)
except (OSError, ValueError):
return 0

try:
import _blake2
except ImportError:
Expand Down Expand Up @@ -950,6 +966,7 @@ def test_pbkdf2_hmac_c(self):

@unittest.skipUnless(hasattr(c_hashlib, 'scrypt'),
' test requires OpenSSL > 1.1')
@unittest.skipIf(get_fips_mode(), reason="scrypt is blocked in FIPS mode")
def test_scrypt(self):
for password, salt, n, r, p, expected in self.scrypt_test_vectors:
result = hashlib.scrypt(password, salt=salt, n=n, r=r, p=p)
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from test import support
from test.support import hashlib_helper
# If we end up with a significant number of tests that don't require
# threading, this test module should be split. Right now we skip
# them all if we don't have threading.
Expand Down Expand Up @@ -370,6 +371,7 @@ def cmd_AUTHENTICATE(self, tag, args):
self.assertEqual(code, 'OK')
self.assertEqual(server.response, b'ZmFrZQ==\r\n') # b64 encoded 'fake'

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_bytes(self):
class AuthHandler(SimpleIMAPHandler):
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
Expand All @@ -387,6 +389,7 @@ def cmd_AUTHENTICATE(self, tag, args):
ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
self.assertEqual(ret, "OK")

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_plain_text(self):
class AuthHandler(SimpleIMAPHandler):
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
Expand Down Expand Up @@ -804,6 +807,7 @@ def cmd_AUTHENTICATE(self, tag, args):
b'ZmFrZQ==\r\n') # b64 encoded 'fake'

@reap_threads
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5(self):

class AuthHandler(SimpleIMAPHandler):
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_poplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from unittest import TestCase, skipUnless
from test import support as test_support
from test.support import control_characters_c0
from test.support import hashlib_helper
threading = test_support.import_module('threading')

HOST = test_support.HOST
Expand Down Expand Up @@ -305,9 +306,11 @@ def test_noop(self):
def test_rpop(self):
self.assertOK(self.client.rpop('foo'))

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_apop_normal(self):
self.assertOK(self.client.apop('foo', 'dummypassword'))

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_apop_REDOS(self):
# Replace welcome with very long evil welcome.
# NB The upper bound on welcome length is currently 2048.
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_smtplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import unittest
from test import support, mock_socket
from test.support import hashlib_helper
from unittest.mock import Mock

HOST = "localhost"
Expand Down Expand Up @@ -1023,13 +1024,15 @@ def testAUTH_LOGIN(self):
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def testAUTH_CRAM_MD5(self):
self.serv.add_feature("AUTH CRAM-MD5")
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
resp = smtp.login(sim_auth[0], sim_auth[1])
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def testAUTH_multiple(self):
# Test that multiple authentication methods are tried.
self.serv.add_feature("AUTH BOGUS PLAIN LOGIN CRAM-MD5")
Expand Down
Loading
Loading