Commit 90558158 authored by Christian Heimes's avatar Christian Heimes Committed by Miss Islington (bot)

bpo-38270: More fixes for strict crypto policy (GH-16418)



test_hmac and test_hashlib test built-in hashing implementations and
OpenSSL-based hashing implementations. Add more checks to skip OpenSSL
implementations when a strict crypto policy is active.

Use EVP_DigestInit_ex() instead of EVP_DigestInit() to initialize the
EVP context. The EVP_DigestInit() function clears alls flags and breaks
usedforsecurity flag again.
Signed-off-by: default avatarChristian Heimes <christian@python.org>



https://bugs.python.org/issue38270
parent 5faff977
...@@ -69,6 +69,11 @@ try: ...@@ -69,6 +69,11 @@ try:
except ImportError: except ImportError:
resource = None resource = None
try:
import _hashlib
except ImportError:
_hashlib = None
__all__ = [ __all__ = [
# globals # globals
"PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast", "PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast",
...@@ -86,8 +91,8 @@ __all__ = [ ...@@ -86,8 +91,8 @@ __all__ = [
"create_empty_file", "can_symlink", "fs_is_case_insensitive", "create_empty_file", "can_symlink", "fs_is_case_insensitive",
# unittest # unittest
"is_resource_enabled", "requires", "requires_freebsd_version", "is_resource_enabled", "requires", "requires_freebsd_version",
"requires_linux_version", "requires_mac_ver", "check_syntax_error", "requires_linux_version", "requires_mac_ver", "requires_hashdigest",
"check_syntax_warning", "check_syntax_error", "check_syntax_warning",
"TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset", "TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset",
"transient_internet", "BasicTestRunner", "run_unittest", "run_doctest", "transient_internet", "BasicTestRunner", "run_unittest", "run_doctest",
"skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma", "skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
...@@ -649,12 +654,16 @@ def requires_mac_ver(*min_version): ...@@ -649,12 +654,16 @@ def requires_mac_ver(*min_version):
return decorator return decorator
def requires_hashdigest(digestname): def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
"""Decorator raising SkipTest if a hashing algorithm is not available """Decorator raising SkipTest if a hashing algorithm is not available
The hashing algorithm could be missing or blocked by a strict crypto The hashing algorithm could be missing or blocked by a strict crypto
policy. policy.
If 'openssl' is True, then the decorator checks that OpenSSL provides
the algorithm. Otherwise the check falls back to built-in
implementations. The usedforsecurity flag is passed to the constructor.
ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
ValueError: unsupported hash type md4 ValueError: unsupported hash type md4
""" """
...@@ -662,7 +671,10 @@ def requires_hashdigest(digestname): ...@@ -662,7 +671,10 @@ def requires_hashdigest(digestname):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
try: try:
hashlib.new(digestname) if openssl and _hashlib is not None:
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
else:
hashlib.new(digestname, usedforsecurity=usedforsecurity)
except ValueError: except ValueError:
raise unittest.SkipTest( raise unittest.SkipTest(
f"hash digest '{digestname}' is not available." f"hash digest '{digestname}' is not available."
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import array import array
from binascii import unhexlify from binascii import unhexlify
import functools
import hashlib import hashlib
import importlib import importlib
import itertools import itertools
...@@ -18,6 +19,7 @@ import unittest ...@@ -18,6 +19,7 @@ import unittest
import warnings import warnings
from test import support from test import support
from test.support import _4G, bigmemtest, import_fresh_module from test.support import _4G, bigmemtest, import_fresh_module
from test.support import requires_hashdigest
from http.client import HTTPException from http.client import HTTPException
# Were we compiled --with-pydebug or with #define Py_DEBUG? # Were we compiled --with-pydebug or with #define Py_DEBUG?
...@@ -119,6 +121,7 @@ class HashLibTestCase(unittest.TestCase): ...@@ -119,6 +121,7 @@ class HashLibTestCase(unittest.TestCase):
constructors.add(_test_algorithm_via_hashlib_new) constructors.add(_test_algorithm_via_hashlib_new)
_hashlib = self._conditional_import_module('_hashlib') _hashlib = self._conditional_import_module('_hashlib')
self._hashlib = _hashlib
if _hashlib: if _hashlib:
# These two algorithms should always be present when this module # These two algorithms should always be present when this module
# is compiled. If not, something was compiled wrong. # is compiled. If not, something was compiled wrong.
...@@ -127,7 +130,13 @@ class HashLibTestCase(unittest.TestCase): ...@@ -127,7 +130,13 @@ class HashLibTestCase(unittest.TestCase):
for algorithm, constructors in self.constructors_to_test.items(): for algorithm, constructors in self.constructors_to_test.items():
constructor = getattr(_hashlib, 'openssl_'+algorithm, None) constructor = getattr(_hashlib, 'openssl_'+algorithm, None)
if constructor: if constructor:
constructors.add(constructor) try:
constructor()
except ValueError:
# default constructor blocked by crypto policy
pass
else:
constructors.add(constructor)
def add_builtin_constructor(name): def add_builtin_constructor(name):
constructor = getattr(hashlib, "__get_builtin_constructor")(name) constructor = getattr(hashlib, "__get_builtin_constructor")(name)
...@@ -193,6 +202,9 @@ class HashLibTestCase(unittest.TestCase): ...@@ -193,6 +202,9 @@ class HashLibTestCase(unittest.TestCase):
cons(b'', usedforsecurity=False) cons(b'', usedforsecurity=False)
hashlib.new("sha256", usedforsecurity=True) hashlib.new("sha256", usedforsecurity=True)
hashlib.new("sha256", usedforsecurity=False) hashlib.new("sha256", usedforsecurity=False)
if self._hashlib is not None:
self._hashlib.new("md5", usedforsecurity=False)
self._hashlib.openssl_md5(usedforsecurity=False)
def test_unknown_hash(self): def test_unknown_hash(self):
self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam')
......
...@@ -21,7 +21,7 @@ def ignore_warning(func): ...@@ -21,7 +21,7 @@ def ignore_warning(func):
class TestVectorsTestCase(unittest.TestCase): class TestVectorsTestCase(unittest.TestCase):
@requires_hashdigest('md5') @requires_hashdigest('md5', openssl=True)
def test_md5_vectors(self): def test_md5_vectors(self):
# Test the HMAC module against test vectors from the RFC. # Test the HMAC module against test vectors from the RFC.
...@@ -79,7 +79,7 @@ class TestVectorsTestCase(unittest.TestCase): ...@@ -79,7 +79,7 @@ class TestVectorsTestCase(unittest.TestCase):
b"and Larger Than One Block-Size Data"), b"and Larger Than One Block-Size Data"),
"6f630fad67cda0ee1fb1f562db3aa53e") "6f630fad67cda0ee1fb1f562db3aa53e")
@requires_hashdigest('sha1') @requires_hashdigest('sha1', openssl=True)
def test_sha_vectors(self): def test_sha_vectors(self):
def shatest(key, data, digest): def shatest(key, data, digest):
h = hmac.HMAC(key, data, digestmod=hashlib.sha1) h = hmac.HMAC(key, data, digestmod=hashlib.sha1)
...@@ -272,19 +272,19 @@ class TestVectorsTestCase(unittest.TestCase): ...@@ -272,19 +272,19 @@ class TestVectorsTestCase(unittest.TestCase):
'134676fb6de0446065c97440fa8c6a58', '134676fb6de0446065c97440fa8c6a58',
}) })
@requires_hashdigest('sha224') @requires_hashdigest('sha224', openssl=True)
def test_sha224_rfc4231(self): def test_sha224_rfc4231(self):
self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64) self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64)
@requires_hashdigest('sha256') @requires_hashdigest('sha256', openssl=True)
def test_sha256_rfc4231(self): def test_sha256_rfc4231(self):
self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64) self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64)
@requires_hashdigest('sha384') @requires_hashdigest('sha384', openssl=True)
def test_sha384_rfc4231(self): def test_sha384_rfc4231(self):
self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128) self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128)
@requires_hashdigest('sha512') @requires_hashdigest('sha512', openssl=True)
def test_sha512_rfc4231(self): def test_sha512_rfc4231(self):
self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128) self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128)
......
...@@ -552,7 +552,7 @@ EVPnew(const EVP_MD *digest, ...@@ -552,7 +552,7 @@ EVPnew(const EVP_MD *digest,
} }
if (!EVP_DigestInit(self->ctx, digest)) { if (!EVP_DigestInit_ex(self->ctx, digest, NULL)) {
_setException(PyExc_ValueError); _setException(PyExc_ValueError);
Py_DECREF(self); Py_DECREF(self);
return NULL; return NULL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment