bpo-9216: hashlib usedforsecurity fixes (GH-20258)

func:`hashlib.new` passed ``usedforsecurity`` to OpenSSL EVP constructor
``_hashlib.new()``. test_hashlib and test_smtplib handle strict security
policy better.

Signed-off-by: Christian Heimes <christian@python.org>

Automerge-Triggered-By: @tiran
(cherry picked from commit 909b5714e1)

Co-authored-by: Christian Heimes <christian@python.org>
This commit is contained in:
Miss Islington (bot) 2020-05-22 11:22:30 -07:00 committed by GitHub
parent 983b17ca13
commit a08b7c3bb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 34 deletions

View File

@ -154,7 +154,7 @@ def __hash_new(name, data=b'', **kwargs):
# salt, personal, tree hashing or SSE.
return __get_builtin_constructor(name)(data, **kwargs)
try:
return _hashlib.new(name, data)
return _hashlib.new(name, data, **kwargs)
except ValueError:
# If the _hashlib module (OpenSSL) doesn't support the named
# hash, try using our builtin implementations.

View File

@ -13,6 +13,7 @@ import importlib
import itertools
import os
import sys
import sysconfig
import threading
import unittest
import warnings
@ -26,11 +27,20 @@ COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount')
c_hashlib = import_fresh_module('hashlib', fresh=['_hashlib'])
py_hashlib = import_fresh_module('hashlib', blocked=['_hashlib'])
builtin_hashes = sysconfig.get_config_var("PY_BUILTIN_HASHLIB_HASHES")
if builtin_hashes is None:
builtin_hashes = {'md5', 'sha1', 'sha256', 'sha512', 'sha3', 'blake2'}
else:
builtin_hashes = {
m.strip() for m in builtin_hashes.strip('"').lower().split(",")
}
try:
from _hashlib import HASH, HASHXOF
from _hashlib import HASH, HASHXOF, openssl_md_meth_names
except ImportError:
HASH = None
HASHXOF = None
openssl_md_meth_names = frozenset()
try:
import _blake2
@ -175,10 +185,17 @@ class HashLibTestCase(unittest.TestCase):
constructors = self.constructors_to_test.values()
return itertools.chain.from_iterable(constructors)
@property
def is_fips_mode(self):
if hasattr(self._hashlib, "get_fips_mode"):
return self._hashlib.get_fips_mode()
else:
return None
def test_hash_array(self):
a = array.array("b", range(10))
for cons in self.hash_constructors:
c = cons(a)
c = cons(a, usedforsecurity=False)
if c.name in self.shakes:
c.hexdigest(16)
else:
@ -193,14 +210,26 @@ class HashLibTestCase(unittest.TestCase):
self.assertTrue(set(hashlib.algorithms_guaranteed).
issubset(hashlib.algorithms_available))
def test_usedforsecurity(self):
def test_usedforsecurity_true(self):
hashlib.new("sha256", usedforsecurity=True)
if self.is_fips_mode:
self.skipTest("skip in FIPS mode")
for cons in self.hash_constructors:
cons(usedforsecurity=True)
cons(usedforsecurity=False)
cons(b'', usedforsecurity=True)
cons(b'', usedforsecurity=False)
hashlib.new("sha256", usedforsecurity=True)
hashlib.new("md5", usedforsecurity=True)
hashlib.md5(usedforsecurity=True)
if self._hashlib is not None:
self._hashlib.new("md5", usedforsecurity=True)
self._hashlib.openssl_md5(usedforsecurity=True)
def test_usedforsecurity_false(self):
hashlib.new("sha256", usedforsecurity=False)
for cons in self.hash_constructors:
cons(usedforsecurity=False)
cons(b'', usedforsecurity=False)
hashlib.new("md5", usedforsecurity=False)
hashlib.md5(usedforsecurity=False)
if self._hashlib is not None:
self._hashlib.new("md5", usedforsecurity=False)
self._hashlib.openssl_md5(usedforsecurity=False)
@ -240,7 +269,7 @@ class HashLibTestCase(unittest.TestCase):
def test_hexdigest(self):
for cons in self.hash_constructors:
h = cons()
h = cons(usedforsecurity=False)
if h.name in self.shakes:
self.assertIsInstance(h.digest(16), bytes)
self.assertEqual(hexstr(h.digest(16)), h.hexdigest(16))
@ -252,7 +281,7 @@ class HashLibTestCase(unittest.TestCase):
# See issue #34922
large_sizes = (2**29, 2**32-10, 2**32+10, 2**61, 2**64-10, 2**64+10)
for cons in self.hash_constructors:
h = cons()
h = cons(usedforsecurity=False)
if h.name not in self.shakes:
continue
if HASH is not None and isinstance(h, HASH):
@ -266,13 +295,16 @@ class HashLibTestCase(unittest.TestCase):
def test_name_attribute(self):
for cons in self.hash_constructors:
h = cons()
h = cons(usedforsecurity=False)
self.assertIsInstance(h.name, str)
if h.name in self.supported_hash_names:
self.assertIn(h.name, self.supported_hash_names)
else:
self.assertNotIn(h.name, self.supported_hash_names)
self.assertEqual(h.name, hashlib.new(h.name).name)
self.assertEqual(
h.name,
hashlib.new(h.name, usedforsecurity=False).name
)
def test_large_update(self):
aas = b'a' * 128
@ -281,7 +313,7 @@ class HashLibTestCase(unittest.TestCase):
dees = b'd' * 2048 # HASHLIB_GIL_MINSIZE
for cons in self.hash_constructors:
m1 = cons()
m1 = cons(usedforsecurity=False)
m1.update(aas)
m1.update(bees)
m1.update(cees)
@ -291,15 +323,15 @@ class HashLibTestCase(unittest.TestCase):
else:
args = ()
m2 = cons()
m2 = cons(usedforsecurity=False)
m2.update(aas + bees + cees + dees)
self.assertEqual(m1.digest(*args), m2.digest(*args))
m3 = cons(aas + bees + cees + dees)
m3 = cons(aas + bees + cees + dees, usedforsecurity=False)
self.assertEqual(m1.digest(*args), m3.digest(*args))
# verify copy() doesn't touch original
m4 = cons(aas + bees + cees)
m4 = cons(aas + bees + cees, usedforsecurity=False)
m4_digest = m4.digest(*args)
m4_copy = m4.copy()
m4_copy.update(dees)
@ -359,7 +391,7 @@ class HashLibTestCase(unittest.TestCase):
digest_length=None):
constructors = self.constructors_to_test[name]
for hash_object_constructor in constructors:
m = hash_object_constructor()
m = hash_object_constructor(usedforsecurity=False)
self.assertEqual(m.block_size, block_size)
self.assertEqual(m.digest_size, digest_size)
if digest_length:
@ -418,15 +450,24 @@ class HashLibTestCase(unittest.TestCase):
self.check_blocksize_name('blake2s', 64, 32)
def test_case_md5_0(self):
self.check('md5', b'', 'd41d8cd98f00b204e9800998ecf8427e')
self.check(
'md5', b'', 'd41d8cd98f00b204e9800998ecf8427e',
usedforsecurity=False
)
def test_case_md5_1(self):
self.check('md5', b'abc', '900150983cd24fb0d6963f7d28e17f72')
self.check(
'md5', b'abc', '900150983cd24fb0d6963f7d28e17f72',
usedforsecurity=False
)
def test_case_md5_2(self):
self.check('md5',
b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
'd174ab98d277d9f5a5611c2c9f419d9f')
self.check(
'md5',
b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
'd174ab98d277d9f5a5611c2c9f419d9f',
usedforsecurity=False
)
@unittest.skipIf(sys.maxsize < _4G + 5, 'test cannot run on 32-bit systems')
@bigmemtest(size=_4G + 5, memuse=1, dry_run=False)
@ -806,22 +847,28 @@ class HashLibTestCase(unittest.TestCase):
gil_minsize = 2048
for cons in self.hash_constructors:
m = cons()
m = cons(usedforsecurity=False)
m.update(b'1')
m.update(b'#' * gil_minsize)
m.update(b'1')
m = cons(b'x' * gil_minsize)
m = cons(b'x' * gil_minsize, usedforsecurity=False)
m.update(b'1')
m = hashlib.md5()
m = hashlib.sha256()
m.update(b'1')
m.update(b'#' * gil_minsize)
m.update(b'1')
self.assertEqual(m.hexdigest(), 'cb1e1a2cbc80be75e19935d621fb9b21')
self.assertEqual(
m.hexdigest(),
'1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
)
m = hashlib.md5(b'x' * gil_minsize)
self.assertEqual(m.hexdigest(), 'cfb767f225d58469c5de3632a8803958')
m = hashlib.sha256(b'1' + b'#' * gil_minsize + b'1')
self.assertEqual(
m.hexdigest(),
'1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
)
@support.reap_threads
def test_threaded_hashing(self):
@ -859,10 +906,10 @@ class HashLibTestCase(unittest.TestCase):
self.assertEqual(expected_hash, hasher.hexdigest())
@unittest.skipUnless(hasattr(c_hashlib, 'get_fips_mode'),
'need _hashlib.get_fips_mode')
def test_get_fips_mode(self):
self.assertIsInstance(c_hashlib.get_fips_mode(), int)
fips_mode = self.is_fips_mode
if fips_mode is not None:
self.assertIsInstance(fips_mode, int)
@unittest.skipUnless(HASH is not None, 'need _hashlib')
def test_internal_types(self):
@ -934,8 +981,10 @@ class KDFTests(unittest.TestCase):
(bytes.fromhex('9d9e9c4cd21fe4be24d5b8244c759665'), None),],
}
def _test_pbkdf2_hmac(self, pbkdf2):
def _test_pbkdf2_hmac(self, pbkdf2, supported):
for digest_name, results in self.pbkdf2_results.items():
if digest_name not in supported:
continue
for i, vector in enumerate(self.pbkdf2_test_vectors):
password, salt, rounds, dklen = vector
expected, overwrite_dklen = results[i]
@ -946,6 +995,7 @@ class KDFTests(unittest.TestCase):
(digest_name, password, salt, rounds, dklen))
out = pbkdf2(digest_name, memoryview(password),
memoryview(salt), rounds, dklen)
self.assertEqual(out, expected)
out = pbkdf2(digest_name, bytearray(password),
bytearray(salt), rounds, dklen)
self.assertEqual(out, expected)
@ -967,12 +1017,12 @@ class KDFTests(unittest.TestCase):
self.assertEqual(out, self.pbkdf2_results['sha1'][0][0])
def test_pbkdf2_hmac_py(self):
self._test_pbkdf2_hmac(py_hashlib.pbkdf2_hmac)
self._test_pbkdf2_hmac(py_hashlib.pbkdf2_hmac, builtin_hashes)
@unittest.skipUnless(hasattr(c_hashlib, 'pbkdf2_hmac'),
' test requires OpenSSL > 1.0')
def test_pbkdf2_hmac_c(self):
self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac)
self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac, openssl_md_meth_names)
@unittest.skipUnless(hasattr(c_hashlib, 'scrypt'),

View File

@ -1067,6 +1067,7 @@ class SMTPSimTests(unittest.TestCase):
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()
@hashlib_helper.requires_hashdigest('md5')
def testAUTH_multiple(self):
# Test that multiple authentication methods are tried.
self.serv.add_feature("AUTH BOGUS PLAIN LOGIN CRAM-MD5")

View File

@ -3,12 +3,14 @@
import os
import unittest
from test import support
from test.support import hashlib_helper
from test.support.script_helper import assert_python_ok, assert_python_failure
from test.test_tools import scriptsdir, skip_if_missing
skip_if_missing()
@hashlib_helper.requires_hashdigest('md5')
class MD5SumTests(unittest.TestCase):
@classmethod
def setUpClass(cls):

View File

@ -316,6 +316,7 @@ class BasicAuthTests(unittest.TestCase):
self.assertRaises(urllib.error.HTTPError, urllib.request.urlopen, self.server_url)
@hashlib_helper.requires_hashdigest("md5")
class ProxyAuthTests(unittest.TestCase):
URL = "http://localhost"
@ -323,7 +324,6 @@ class ProxyAuthTests(unittest.TestCase):
PASSWD = "test123"
REALM = "TestRealm"
@hashlib_helper.requires_hashdigest("md5")
def setUp(self):
super(ProxyAuthTests, self).setUp()
# Ignore proxy bypass settings in the environment.

View File

@ -0,0 +1,3 @@
func:`hashlib.new` passed ``usedforsecurity`` to OpenSSL EVP constructor
``_hashlib.new()``. test_hashlib and test_smtplib handle strict security
policy better.