From 1f290dccfe838500250525ef6ea66cf7e5380f71 Mon Sep 17 00:00:00 2001 From: CPython Developers <> Date: Sat, 14 Feb 2026 12:49:39 +0900 Subject: [PATCH 1/2] Update hashlib from v3.14.3 --- Lib/hashlib.py | 105 ++------- Lib/test/test_hashlib.py | 312 +++++++++++++++++--------- Lib/test/test_hmac.py | 1 - Lib/test/test_inspect/test_inspect.py | 1 - Lib/test/test_keywordonlyarg.py | 1 - Lib/test/test_positional_only_arg.py | 1 - Lib/test/test_smtplib.py | 2 - 7 files changed, 228 insertions(+), 195 deletions(-) diff --git a/Lib/hashlib.py b/Lib/hashlib.py index e5f81d754ac..0e9bd98aa1f 100644 --- a/Lib/hashlib.py +++ b/Lib/hashlib.py @@ -33,7 +33,7 @@ - hexdigest(): Like digest() except the digest is returned as a string of double length, containing only hexadecimal digits. - copy(): Return a copy (clone) of the hash object. This can be used to - efficiently compute the digests of datas that share a common + efficiently compute the digests of data that share a common initial substring. For example, to obtain the digest of the byte string 'Nobody inspects the @@ -65,7 +65,7 @@ algorithms_available = set(__always_supported) __all__ = __always_supported + ('new', 'algorithms_guaranteed', - 'algorithms_available', 'pbkdf2_hmac', 'file_digest') + 'algorithms_available', 'file_digest') __builtin_constructor_cache = {} @@ -92,13 +92,13 @@ def __get_builtin_constructor(name): import _md5 cache['MD5'] = cache['md5'] = _md5.md5 elif name in {'SHA256', 'sha256', 'SHA224', 'sha224'}: - import _sha256 - cache['SHA224'] = cache['sha224'] = _sha256.sha224 - cache['SHA256'] = cache['sha256'] = _sha256.sha256 + import _sha2 + cache['SHA224'] = cache['sha224'] = _sha2.sha224 + cache['SHA256'] = cache['sha256'] = _sha2.sha256 elif name in {'SHA512', 'sha512', 'SHA384', 'sha384'}: - import _sha512 - cache['SHA384'] = cache['sha384'] = _sha512.sha384 - cache['SHA512'] = cache['sha512'] = _sha512.sha512 + import _sha2 + cache['SHA384'] = cache['sha384'] = _sha2.sha384 + cache['SHA512'] = cache['sha512'] = _sha2.sha512 elif name in {'blake2b', 'blake2s'}: import _blake2 cache['blake2b'] = _blake2.blake2b @@ -141,38 +141,37 @@ def __get_openssl_constructor(name): return __get_builtin_constructor(name) -def __py_new(name, data=b'', **kwargs): +def __py_new(name, *args, **kwargs): """new(name, data=b'', **kwargs) - Return a new hashing object using the named algorithm; optionally initialized with data (which must be a bytes-like object). """ - return __get_builtin_constructor(name)(data, **kwargs) + return __get_builtin_constructor(name)(*args, **kwargs) -def __hash_new(name, data=b'', **kwargs): +def __hash_new(name, *args, **kwargs): """new(name, data=b'') - Return a new hashing object using the named algorithm; optionally initialized with data (which must be a bytes-like object). """ if name in __block_openssl_constructor: # Prefer our builtin blake2 implementation. - return __get_builtin_constructor(name)(data, **kwargs) + return __get_builtin_constructor(name)(*args, **kwargs) try: - return _hashlib.new(name, data, **kwargs) + return _hashlib.new(name, *args, **kwargs) except ValueError: # If the _hashlib module (OpenSSL) doesn't support the named # hash, try using our builtin implementations. # This allows for SHA224/256 and SHA384/512 support even though # the OpenSSL library prior to 0.9.8 doesn't provide them. - return __get_builtin_constructor(name)(data) + return __get_builtin_constructor(name)(*args, **kwargs) try: import _hashlib new = __hash_new __get_hash = __get_openssl_constructor - # TODO: RUSTPYTHON set in _hashlib instance PyFrozenSet algorithms_available - '''algorithms_available = algorithms_available.union( - _hashlib.openssl_md_meth_names)''' + algorithms_available = algorithms_available.union( + _hashlib.openssl_md_meth_names) except ImportError: _hashlib = None new = __py_new @@ -181,76 +180,14 @@ def __hash_new(name, data=b'', **kwargs): try: # OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA from _hashlib import pbkdf2_hmac + __all__ += ('pbkdf2_hmac',) except ImportError: - from warnings import warn as _warn - _trans_5C = bytes((x ^ 0x5C) for x in range(256)) - _trans_36 = bytes((x ^ 0x36) for x in range(256)) - - def pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None): - """Password based key derivation function 2 (PKCS #5 v2.0) - - This Python implementations based on the hmac module about as fast - as OpenSSL's PKCS5_PBKDF2_HMAC for short passwords and much faster - for long passwords. - """ - _warn( - "Python implementation of pbkdf2_hmac() is deprecated.", - category=DeprecationWarning, - stacklevel=2 - ) - if not isinstance(hash_name, str): - raise TypeError(hash_name) - - if not isinstance(password, (bytes, bytearray)): - password = bytes(memoryview(password)) - if not isinstance(salt, (bytes, bytearray)): - salt = bytes(memoryview(salt)) - - # Fast inline HMAC implementation - inner = new(hash_name) - outer = new(hash_name) - blocksize = getattr(inner, 'block_size', 64) - if len(password) > blocksize: - password = new(hash_name, password).digest() - password = password + b'\x00' * (blocksize - len(password)) - inner.update(password.translate(_trans_36)) - outer.update(password.translate(_trans_5C)) - - def prf(msg, inner=inner, outer=outer): - # PBKDF2_HMAC uses the password as key. We can re-use the same - # digest objects and just update copies to skip initialization. - icpy = inner.copy() - ocpy = outer.copy() - icpy.update(msg) - ocpy.update(icpy.digest()) - return ocpy.digest() - - if iterations < 1: - raise ValueError(iterations) - if dklen is None: - dklen = outer.digest_size - if dklen < 1: - raise ValueError(dklen) - - dkey = b'' - loop = 1 - from_bytes = int.from_bytes - while len(dkey) < dklen: - prev = prf(salt + loop.to_bytes(4)) - # endianness doesn't matter here as long to / from use the same - rkey = from_bytes(prev) - for i in range(iterations - 1): - prev = prf(prev) - # rkey = rkey ^ prev - rkey ^= from_bytes(prev) - loop += 1 - dkey += rkey.to_bytes(inner.digest_size) - - return dkey[:dklen] + pass + try: # OpenSSL's scrypt requires OpenSSL 1.1+ - from _hashlib import scrypt + from _hashlib import scrypt # noqa: F401 except ImportError: pass @@ -294,6 +231,8 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18): view = memoryview(buf) while True: size = fileobj.readinto(buf) + if size is None: + raise BlockingIOError("I/O operation would block.") if size == 0: break # EOF digestobj.update(view[:size]) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 974c6f6a4a5..9c4c1c7b0e8 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -1,6 +1,4 @@ -# Test hashlib module -# -# $Id$ +# Test the hashlib module. # # Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org) # Licensed to PSF under a Contributor Agreement. @@ -12,50 +10,46 @@ import importlib import io import itertools +import logging import os +import re import sys import sysconfig +import tempfile import threading import unittest import warnings from test import support from test.support import _4G, bigmemtest +from test.support import hashlib_helper from test.support.import_helper import import_fresh_module -from test.support import os_helper +from test.support import requires_resource from test.support import threading_helper -from test.support import warnings_helper from http.client import HTTPException -# Were we compiled --with-pydebug or with #define Py_DEBUG? -COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount') -# default builtin hash module -default_builtin_hashes = {'md5', 'sha1', 'sha256', 'sha512', 'sha3', 'blake2'} +default_builtin_hashes = {'md5', 'sha1', 'sha2', 'sha3', 'blake2'} # --with-builtin-hashlib-hashes override builtin_hashes = sysconfig.get_config_var("PY_BUILTIN_HASHLIB_HASHES") if builtin_hashes is None: builtin_hashes = default_builtin_hashes else: - builtin_hashes = { - m.strip() for m in builtin_hashes.strip('"').lower().split(",") - } + builtin_hash_names = builtin_hashes.strip('"').lower().split(",") + builtin_hashes = set(map(str.strip, builtin_hash_names)) -# hashlib with and without OpenSSL backend for PBKDF2 -# only import builtin_hashlib when all builtin hashes are available. -# Otherwise import prints noise on stderr +# Public 'hashlib' module with OpenSSL backend for PBKDF2. openssl_hashlib = import_fresh_module('hashlib', fresh=['_hashlib']) -if builtin_hashes == default_builtin_hashes: - builtin_hashlib = import_fresh_module('hashlib', blocked=['_hashlib']) -else: - builtin_hashlib = None try: - from _hashlib import HASH, HASHXOF, openssl_md_meth_names, get_fips_mode + import _hashlib except ImportError: - HASH = None - HASHXOF = None - openssl_md_meth_names = frozenset() - + _hashlib = None +# The extension module may exist but only define some of these. gh-141907 +HASH = getattr(_hashlib, 'HASH', None) +HASHXOF = getattr(_hashlib, 'HASHXOF', None) +openssl_md_meth_names = getattr(_hashlib, 'openssl_md_meth_names', frozenset()) +get_fips_mode = getattr(_hashlib, 'get_fips_mode', None) +if not get_fips_mode: def get_fips_mode(): return 0 @@ -66,9 +60,12 @@ def get_fips_mode(): requires_blake2 = unittest.skipUnless(_blake2, 'requires _blake2') -# bpo-46913: Don't test the _sha3 extension on a Python UBSAN build -SKIP_SHA3 = support.check_sanitizer(ub=True) -requires_sha3 = unittest.skipUnless(not SKIP_SHA3, 'requires _sha3') +try: + import _sha3 +except ImportError: + _sha3 = None + +requires_sha3 = unittest.skipUnless(_sha3, 'requires _sha3') def hexstr(s): @@ -108,8 +105,8 @@ class HashLibTestCase(unittest.TestCase): shakes = {'shake_128', 'shake_256'} - # Issue #14693: fallback modules are always compiled under POSIX - _warn_on_extension_import = os.name == 'posix' or COMPILED_WITH_PYDEBUG + # gh-58898: Fallback modules are always compiled under POSIX. + _warn_on_extension_import = (os.name == 'posix' or support.Py_DEBUG) def _conditional_import_module(self, module_name): """Import a module and return a reference to it or None on failure.""" @@ -117,7 +114,11 @@ def _conditional_import_module(self, module_name): return importlib.import_module(module_name) except ModuleNotFoundError as error: if self._warn_on_extension_import and module_name in builtin_hashes: - warnings.warn('Did a C extension fail to compile? %s' % error) + logging.getLogger(__name__).warning( + 'Did a C extension fail to compile? %s', + error, + exc_info=error, + ) return None def __init__(self, *args, **kwargs): @@ -131,27 +132,24 @@ def __init__(self, *args, **kwargs): self.constructors_to_test = {} for algorithm in algorithms: - if SKIP_SHA3 and algorithm.startswith('sha3_'): - continue self.constructors_to_test[algorithm] = set() # For each algorithm, test the direct constructor and the use # of hashlib.new given the algorithm name. for algorithm, constructors in self.constructors_to_test.items(): constructors.add(getattr(hashlib, algorithm)) - def _test_algorithm_via_hashlib_new(data=None, _alg=algorithm, **kwargs): - if data is None: - return hashlib.new(_alg, **kwargs) - return hashlib.new(_alg, data, **kwargs) - constructors.add(_test_algorithm_via_hashlib_new) + def c(*args, __algorithm_name=algorithm, **kwargs): + return hashlib.new(__algorithm_name, *args, **kwargs) + c.__name__ = f'do_test_algorithm_via_hashlib_new_{algorithm}' + constructors.add(c) _hashlib = self._conditional_import_module('_hashlib') self._hashlib = _hashlib if _hashlib: - # These two algorithms should always be present when this module + # These algorithms should always be present when this module # is compiled. If not, something was compiled wrong. - self.assertTrue(hasattr(_hashlib, 'openssl_md5')) - self.assertTrue(hasattr(_hashlib, 'openssl_sha1')) + self.assertHasAttr(_hashlib, 'openssl_md5') + self.assertHasAttr(_hashlib, 'openssl_sha1') for algorithm, constructors in self.constructors_to_test.items(): constructor = getattr(_hashlib, 'openssl_'+algorithm, None) if constructor: @@ -173,28 +171,24 @@ def add_builtin_constructor(name): _sha1 = self._conditional_import_module('_sha1') if _sha1: add_builtin_constructor('sha1') - _sha256 = self._conditional_import_module('_sha256') - if _sha256: + _sha2 = self._conditional_import_module('_sha2') + if _sha2: add_builtin_constructor('sha224') add_builtin_constructor('sha256') - _sha512 = self._conditional_import_module('_sha512') - if _sha512: add_builtin_constructor('sha384') add_builtin_constructor('sha512') + _sha3 = self._conditional_import_module('_sha3') + if _sha3: + add_builtin_constructor('sha3_224') + add_builtin_constructor('sha3_256') + add_builtin_constructor('sha3_384') + add_builtin_constructor('sha3_512') + add_builtin_constructor('shake_128') + add_builtin_constructor('shake_256') if _blake2: add_builtin_constructor('blake2s') add_builtin_constructor('blake2b') - if not SKIP_SHA3: - _sha3 = self._conditional_import_module('_sha3') - if _sha3: - add_builtin_constructor('sha3_224') - add_builtin_constructor('sha3_256') - add_builtin_constructor('sha3_384') - add_builtin_constructor('sha3_512') - add_builtin_constructor('shake_128') - add_builtin_constructor('shake_256') - super(HashLibTestCase, self).__init__(*args, **kwargs) @property @@ -252,6 +246,80 @@ def test_usedforsecurity_false(self): self._hashlib.new("md5", usedforsecurity=False) self._hashlib.openssl_md5(usedforsecurity=False) + @unittest.skipIf(get_fips_mode(), "skip in FIPS mode") + def test_clinic_signature(self): + for constructor in self.hash_constructors: + with self.subTest(constructor.__name__): + constructor(b'') + constructor(data=b'') + constructor(string=b'') # should be deprecated in the future + + digest_name = constructor(b'').name + with self.subTest(digest_name): + hashlib.new(digest_name, b'') + hashlib.new(digest_name, data=b'') + hashlib.new(digest_name, string=b'') + # Make sure that _hashlib contains the constructor + # to test when using a combination of libcrypto and + # interned hash implementations. + if self._hashlib and digest_name in self._hashlib._constructors: + self._hashlib.new(digest_name, b'') + self._hashlib.new(digest_name, data=b'') + self._hashlib.new(digest_name, string=b'') + + @unittest.expectedFailure # TODO: RUSTPYTHON; duplicate positional/keyword arg error message differs + @unittest.skipIf(get_fips_mode(), "skip in FIPS mode") + def test_clinic_signature_errors(self): + nomsg = b'' + mymsg = b'msg' + conflicting_call = re.escape( + "'data' and 'string' are mutually exclusive " + "and support for 'string' keyword parameter " + "is slated for removal in a future version." + ) + duplicated_param = re.escape("given by name ('data') and position") + unexpected_param = re.escape("got an unexpected keyword argument '_'") + for args, kwds, errmsg in [ + # Reject duplicated arguments before unknown keyword arguments. + ((nomsg,), dict(data=nomsg, _=nomsg), duplicated_param), + ((mymsg,), dict(data=nomsg, _=nomsg), duplicated_param), + # Reject duplicated arguments before conflicting ones. + *itertools.product( + [[nomsg], [mymsg]], + [dict(data=nomsg), dict(data=nomsg, string=nomsg)], + [duplicated_param] + ), + # Reject unknown keyword arguments before conflicting ones. + *itertools.product( + [()], + [ + dict(_=None), + dict(data=nomsg, _=None), + dict(string=nomsg, _=None), + dict(string=nomsg, data=nomsg, _=None), + ], + [unexpected_param] + ), + ((nomsg,), dict(_=None), unexpected_param), + ((mymsg,), dict(_=None), unexpected_param), + # Reject conflicting arguments. + [(nomsg,), dict(string=nomsg), conflicting_call], + [(mymsg,), dict(string=nomsg), conflicting_call], + [(), dict(data=nomsg, string=nomsg), conflicting_call], + ]: + for constructor in self.hash_constructors: + digest_name = constructor(b'').name + with self.subTest(constructor.__name__, args=args, kwds=kwds): + with self.assertRaisesRegex(TypeError, errmsg): + constructor(*args, **kwds) + with self.subTest(digest_name, args=args, kwds=kwds): + with self.assertRaisesRegex(TypeError, errmsg): + hashlib.new(digest_name, *args, **kwds) + if (self._hashlib and + digest_name in self._hashlib._constructors): + with self.assertRaisesRegex(TypeError, errmsg): + self._hashlib.new(digest_name, *args, **kwds) + def test_unknown_hash(self): self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') self.assertRaises(TypeError, hashlib.new, 1) @@ -259,6 +327,7 @@ def test_unknown_hash(self): def test_new_upper_to_lower(self): self.assertEqual(hashlib.new("SHA256").name, "sha256") + @support.thread_unsafe("modifies sys.modules") def test_get_builtin_constructor(self): get_builtin_constructor = getattr(hashlib, '__get_builtin_constructor') @@ -356,6 +425,35 @@ def test_large_update(self): self.assertEqual(m1.digest(*args), m4_copy.digest(*args)) self.assertEqual(m4.digest(*args), m4_digest) + @requires_resource('cpu') + def test_sha256_update_over_4gb(self): + zero_1mb = b"\0" * 1024 * 1024 + h = hashlib.sha256() + for i in range(0, 4096): + h.update(zero_1mb) + h.update(b"hello world") + self.assertEqual(h.hexdigest(), "a5364f7a52ebe2e25f1838a4ca715a893b6fd7a23f2a0d9e9762120da8b1bf53") + + @requires_resource('cpu') + def test_sha3_256_update_over_4gb(self): + zero_1mb = b"\0" * 1024 * 1024 + h = hashlib.sha3_256() + for i in range(0, 4096): + h.update(zero_1mb) + h.update(b"hello world") + self.assertEqual(h.hexdigest(), "e2d4535e3b613135c14f2fe4e026d7ad8d569db44901740beffa30d430acb038") + + @requires_resource('cpu') + def test_blake2_update_over_4gb(self): + # blake2s or blake2b doesn't matter based on how our C code is structured, this tests the + # common loop macro logic. + zero_1mb = b"\0" * 1024 * 1024 + h = hashlib.blake2s() + for i in range(0, 4096): + h.update(zero_1mb) + h.update(b"hello world") + self.assertEqual(h.hexdigest(), "8a268e83dd30528bc0907fa2008c91de8f090a0b6e0e60a5ff0d999d8485526f") + def check(self, name, data, hexdigest, shake=False, **kwargs): length = len(hexdigest)//2 hexdigest = hexdigest.lower() @@ -391,21 +489,18 @@ def check_file_digest(self, name, data, hexdigest): digests = [name] digests.extend(self.constructors_to_test[name]) - with open(os_helper.TESTFN, "wb") as f: + with tempfile.TemporaryFile() as f: f.write(data) - try: for digest in digests: buf = io.BytesIO(data) buf.seek(0) self.assertEqual( hashlib.file_digest(buf, digest).hexdigest(), hexdigest ) - with open(os_helper.TESTFN, "rb") as f: - digestobj = hashlib.file_digest(f, digest) + f.seek(0) + digestobj = hashlib.file_digest(f, digest) self.assertEqual(digestobj.hexdigest(), hexdigest) - finally: - os.unlink(os_helper.TESTFN) def check_no_unicode(self, algorithm_name): # Unicode objects are not allowed as input. @@ -452,9 +547,9 @@ def check_blocksize_name(self, name, block_size=0, digest_size=0, self.assertEqual(len(m.hexdigest()), 2*digest_size) self.assertEqual(m.name, name) # split for sha3_512 / _sha3.sha3 object - self.assertIn(name.split("_")[0], repr(m)) + self.assertIn(name.split("_")[0], repr(m).lower()) - def test_blocksize_name(self): + def test_blocksize_and_name(self): self.check_blocksize_name('md5', 64, 16) self.check_blocksize_name('sha1', 64, 20) self.check_blocksize_name('sha224', 64, 28) @@ -462,8 +557,7 @@ def test_blocksize_name(self): self.check_blocksize_name('sha384', 128, 48) self.check_blocksize_name('sha512', 128, 64) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'shake' not found in '<_hashlib.hashxof object at 0xc68a2c800>' @requires_sha3 def test_blocksize_name_sha3(self): self.check_blocksize_name('sha3_224', 144, 28) @@ -477,9 +571,14 @@ def check_sha3(self, name, capacity, rate, suffix): constructors = self.constructors_to_test[name] for hash_object_constructor in constructors: m = hash_object_constructor() - if HASH is not None and isinstance(m, HASH): - # _hashopenssl's variant does not have extra SHA3 attributes - continue + if name.startswith('shake_'): + if HASHXOF is not None and isinstance(m, HASHXOF): + # _hashopenssl's variant does not have extra SHA3 attributes + continue + else: + if HASH is not None and isinstance(m, HASH): + # _hashopenssl's variant does not have extra SHA3 attributes + continue self.assertEqual(capacity + rate, 1600) self.assertEqual(m._capacity_bits, capacity) self.assertEqual(m._rate_bits, rate) @@ -696,8 +795,6 @@ def check_blake2(self, constructor, salt_size, person_size, key_size, self.assertRaises(ValueError, constructor, node_offset=-1) self.assertRaises(OverflowError, constructor, node_offset=max_offset+1) - self.assertRaises(TypeError, constructor, data=b'') - self.assertRaises(TypeError, constructor, string=b'') self.assertRaises(TypeError, constructor, '') constructor( @@ -737,8 +834,7 @@ def selftest_seq(length, seed): outer.update(keyed.digest()) return outer.hexdigest() - # TODO: RUSTPYTHON add to constructor const value - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; add to constructor const value @requires_blake2 def test_blake2b(self): self.check_blake2(hashlib.blake2b, 16, 16, 64, 64, (1<<64)-1) @@ -760,8 +856,7 @@ def test_case_blake2b_1(self): "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1"+ "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923") - # TODO: RUSTPYTHON implement all blake2 fields - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; implement all blake2 fields @requires_blake2 def test_case_blake2b_all_parameters(self): # This checks that all the parameters work in general, and also that @@ -780,15 +875,14 @@ def test_case_blake2b_all_parameters(self): inner_size=7, last_node=True) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; blake2 key parameter not supported @requires_blake2 def test_blake2b_vectors(self): for msg, key, md in read_vectors('blake2b'): key = bytes.fromhex(key) self.check('blake2b', msg, md, key=key) - # TODO: RUSTPYTHON add to constructor const value - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; add to constructor const value @requires_blake2 def test_blake2s(self): self.check_blake2(hashlib.blake2s, 8, 8, 32, 32, (1<<48)-1) @@ -808,8 +902,7 @@ def test_case_blake2s_1(self): self.check('blake2s', b"abc", "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982") - # TODO: RUSTPYTHON implement all blake2 fields - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; implement all blake2 fields @requires_blake2 def test_case_blake2s_all_parameters(self): # This checks that all the parameters work in general, and also that @@ -828,7 +921,7 @@ def test_case_blake2s_all_parameters(self): inner_size=7, last_node=True) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; blake2 key parameter not supported @requires_blake2 def test_blake2s_vectors(self): for msg, key, md in read_vectors('blake2s'): @@ -899,10 +992,13 @@ def test_case_shake256_vector(self): def test_gil(self): # Check things work fine with an input larger than the size required - # for multithreaded operation (which is hardwired to 2048). - gil_minsize = 2048 - + # for multithreaded operation. Currently, all cryptographic modules + # have the same constant value (2048) but in the future it might not + # be the case. + mods = ['_md5', '_sha1', '_sha2', '_sha3', '_blake2', '_hashlib'] + gil_minsize = hashlib_helper.find_gil_minsize(mods) for cons in self.hash_constructors: + # constructors belong to one of the above modules m = cons(usedforsecurity=False) m.update(b'1') m.update(b'#' * gil_minsize) @@ -911,6 +1007,8 @@ def test_gil(self): m = cons(b'x' * gil_minsize, usedforsecurity=False) m.update(b'1') + def test_sha256_gil(self): + gil_minsize = hashlib_helper.find_gil_minsize(['_sha2', '_hashlib']) m = hashlib.sha256() m.update(b'1') m.update(b'#' * gil_minsize) @@ -988,7 +1086,8 @@ def test_disallow_instantiation(self): def test_hash_disallow_instantiation(self): # internal types like _hashlib.HASH are not constructable support.check_disallow_instantiation(self, HASH) - support.check_disallow_instantiation(self, HASHXOF) + if HASHXOF is not None: + support.check_disallow_instantiation(self, HASHXOF) def test_readonly_types(self): for algorithm, constructors in self.constructors_to_test.items(): @@ -1110,15 +1209,7 @@ def _test_pbkdf2_hmac(self, pbkdf2, supported): iterations=1, dklen=None) self.assertEqual(out, self.pbkdf2_results['sha1'][0][0]) - @unittest.skipIf(builtin_hashlib is None, "test requires builtin_hashlib") - def test_pbkdf2_hmac_py(self): - with warnings_helper.check_warnings(): - self._test_pbkdf2_hmac( - builtin_hashlib.pbkdf2_hmac, builtin_hashes - ) - - @unittest.skipUnless(hasattr(openssl_hashlib, 'pbkdf2_hmac'), - ' test requires OpenSSL > 1.0') + @unittest.skipIf(openssl_hashlib is None, "requires OpenSSL bindings") def test_pbkdf2_hmac_c(self): self._test_pbkdf2_hmac(openssl_hashlib.pbkdf2_hmac, openssl_md_meth_names) @@ -1169,29 +1260,38 @@ def test_normalized_name(self): def test_file_digest(self): data = b'a' * 65536 d1 = hashlib.sha256() - self.addCleanup(os.unlink, os_helper.TESTFN) - with open(os_helper.TESTFN, "wb") as f: + with tempfile.NamedTemporaryFile(delete_on_close=False) as fp: for _ in range(10): d1.update(data) - f.write(data) + fp.write(data) + fp.close() + + with open(fp.name, "rb") as f: + d2 = hashlib.file_digest(f, hashlib.sha256) - with open(os_helper.TESTFN, "rb") as f: - d2 = hashlib.file_digest(f, hashlib.sha256) + self.assertEqual(d1.hexdigest(), d2.hexdigest()) + self.assertEqual(d1.name, d2.name) + self.assertIs(type(d1), type(d2)) - self.assertEqual(d1.hexdigest(), d2.hexdigest()) - self.assertEqual(d1.name, d2.name) - self.assertIs(type(d1), type(d2)) + with self.assertRaises(ValueError): + with open(fp.name, "r") as f: + hashlib.file_digest(f, "sha256") + + with self.assertRaises(ValueError): + with open(fp.name, "wb") as f: + hashlib.file_digest(f, "sha256") with self.assertRaises(ValueError): hashlib.file_digest(None, "sha256") - with self.assertRaises(ValueError): - with open(os_helper.TESTFN, "r") as f: - hashlib.file_digest(f, "sha256") + class NonBlocking: + def readinto(self, buf): + return None + def readable(self): + return True - with self.assertRaises(ValueError): - with open(os_helper.TESTFN, "wb") as f: - hashlib.file_digest(f, "sha256") + with self.assertRaises(BlockingIOError): + hashlib.file_digest(NonBlocking(), hashlib.sha256) if __name__ == "__main__": diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 30cb02b6718..d5023a6c1b3 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -1583,7 +1583,6 @@ def digest(self): hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash) self.fail('Expected warning about small block_size') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module '_hashlib' has no attribute 'hmac_digest' @hashlib_helper.requires_hashdigest('sha256') def test_with_fallback(self): cache = getattr(hashlib, '__builtin_constructor_cache') diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 3d19a35a1cd..0aaf9176d8d 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5161,7 +5161,6 @@ def test(): sig = test.__signature__ = inspect.Signature(parameters=(spam_param,)) self.assertEqual(sig, inspect.signature(test)) - @unittest.expectedFailure # TODO: RUSTPYTHON; Ellipsis) def test_signature_on_mangled_parameters(self): class Spam: def foo(self, __p1:1=2, *, __p2:2=3): diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py index 8fd2e89122f..918f953cae5 100644 --- a/Lib/test/test_keywordonlyarg.py +++ b/Lib/test/test_keywordonlyarg.py @@ -156,7 +156,6 @@ def test_issue13343(self): # used to fail with a SystemError. lambda *, k1=unittest: None - @unittest.expectedFailure # TODO: RUSTPYTHON def test_mangling(self): class X: def f(self, *, __a=42): diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py index cb43cfe7eef..e002babab44 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -338,7 +338,6 @@ def f(something,/,**kwargs): self.assertEqual(f(42), (42, {})) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_mangling(self): class X: def f(self, __a=42, /): diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index ade0dc1308c..fb3ea34d766 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -1176,7 +1176,6 @@ def auth_buggy(challenge=None): finally: smtp.close() - @unittest.expectedFailure # TODO: RUSTPYTHON @hashlib_helper.requires_hashdigest('md5', openssl=True) def testAUTH_CRAM_MD5(self): self.serv.add_feature("AUTH CRAM-MD5") @@ -1229,7 +1228,6 @@ def testAUTH_multiple(self): self.assertEqual(resp, (235, b'Authentication Succeeded')) smtp.close() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_auth_function(self): supported = {'PLAIN', 'LOGIN'} try: From 12db02eaf0f6d7451e02d9a529ba0cd93dd8eca0 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 14 Feb 2026 12:52:21 +0900 Subject: [PATCH 2/2] Fix _hashlib --- Cargo.lock | 2 + crates/codegen/src/compile.rs | 4 +- crates/stdlib/Cargo.toml | 4 +- crates/stdlib/src/blake2.rs | 4 +- crates/stdlib/src/hashlib.rs | 389 +++++++++++++++++++++++++++++----- crates/stdlib/src/md5.rs | 2 +- crates/stdlib/src/sha1.rs | 2 +- crates/stdlib/src/sha256.rs | 4 +- crates/stdlib/src/sha3.rs | 12 +- crates/stdlib/src/sha512.rs | 4 +- crates/vm/src/vm/mod.rs | 2 +- 11 files changed, 362 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa661627d5e..906621e099c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3226,6 +3226,7 @@ dependencies = [ "foreign-types-shared", "gethostname", "hex", + "hmac", "indexmap", "itertools 0.14.0", "libc", @@ -3250,6 +3251,7 @@ dependencies = [ "page_size", "parking_lot", "paste", + "pbkdf2", "pem-rfc7468 1.0.0", "phf 0.13.1", "pkcs8", diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index c13ec694a52..c132c0f4b09 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -3717,7 +3717,7 @@ impl Compiler { // Compile kwdefaults and build dict for (arg, default) in &kw_with_defaults { self.emit_load_const(ConstantData::Str { - value: arg.name.as_str().into(), + value: self.mangle(arg.name.as_str()).into_owned().into(), }); self.compile_expression(default)?; } @@ -6983,7 +6983,7 @@ impl Compiler { let default_kw_count = kw_with_defaults.len(); for (arg, default) in &kw_with_defaults { self.emit_load_const(ConstantData::Str { - value: arg.name.as_str().into(), + value: self.mangle(arg.name.as_str()).into_owned().into(), }); self.compile_expression(default)?; } diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index d0da89d42ef..0fed25e8b9c 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -61,12 +61,14 @@ rand_core = { workspace = true } mt19937 = "3.1" # Crypto: -digest = "0.10.3" +digest = "0.10.7" md-5 = "0.10.1" sha-1 = "0.10.0" sha2 = "0.10.2" sha3 = "0.10.1" blake2 = "0.10.4" +hmac = "0.12" +pbkdf2 = { version = "0.12", features = ["hmac"] } ## unicode stuff unicode_names2 = { workspace = true } diff --git a/crates/stdlib/src/blake2.rs b/crates/stdlib/src/blake2.rs index 5d2e991b929..53687c7027a 100644 --- a/crates/stdlib/src/blake2.rs +++ b/crates/stdlib/src/blake2.rs @@ -9,11 +9,11 @@ mod _blake2 { #[pyfunction] fn blake2b(args: BlakeHashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_blake2b(args).into_pyobject(vm)) + Ok(local_blake2b(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn blake2s(args: BlakeHashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_blake2s(args).into_pyobject(vm)) + Ok(local_blake2s(args, vm)?.into_pyobject(vm)) } } diff --git a/crates/stdlib/src/hashlib.rs b/crates/stdlib/src/hashlib.rs index eeb87b90e9e..5a2a55b93a7 100644 --- a/crates/stdlib/src/hashlib.rs +++ b/crates/stdlib/src/hashlib.rs @@ -1,4 +1,7 @@ -// spell-checker:ignore usedforsecurity HASHXOF +// spell-checker:ignore usedforsecurity HASHXOF hashopenssl dklen +// NOTE: Function names like `openssl_md5` match CPython's `_hashopenssl.c` interface +// for compatibility, but the implementation uses pure Rust crates (md5, sha2, etc.), +// not OpenSSL. pub(crate) use _hashlib::module_def; @@ -7,7 +10,9 @@ pub mod _hashlib { use crate::common::lock::PyRwLock; use crate::vm::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyBytes, PyStrRef, PyTypeRef, PyValueError}, + builtins::{ + PyBaseExceptionRef, PyBytes, PyFrozenSet, PyStr, PyStrRef, PyTypeRef, PyValueError, + }, class::StaticType, convert::ToPyObject, function::{ArgBytesLike, ArgStrOrBytesLike, FuncArgs, OptionalArg}, @@ -17,17 +22,59 @@ pub mod _hashlib { use digest::{DynDigest, core_api::BlockSizeUser}; use digest::{ExtendableOutput, Update}; use dyn_clone::{DynClone, clone_trait_object}; + use hmac::Mac; use md5::Md5; use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; + const HASH_ALGORITHMS: &[&str] = &[ + "md5", + "sha1", + "sha224", + "sha256", + "sha384", + "sha512", + "sha3_224", + "sha3_256", + "sha3_384", + "sha3_512", + "shake_128", + "shake_256", + "blake2b", + "blake2s", + ]; + + #[pyattr] + const _GIL_MINSIZE: usize = 2048; + #[pyattr] #[pyexception(name = "UnsupportedDigestmodError", base = PyValueError, impl)] #[derive(Debug)] #[repr(transparent)] pub struct UnsupportedDigestmodError(PyValueError); + #[pyattr] + fn openssl_md_meth_names(vm: &VirtualMachine) -> PyObjectRef { + PyFrozenSet::from_iter( + vm, + HASH_ALGORITHMS.iter().map(|n| vm.ctx.new_str(*n).into()), + ) + .expect("failed to create openssl_md_meth_names frozenset") + .into_ref(&vm.ctx) + .into() + } + + #[pyattr] + fn _constructors(vm: &VirtualMachine) -> PyObjectRef { + let dict = vm.ctx.new_dict(); + for name in HASH_ALGORITHMS { + let s = vm.ctx.new_str(*name); + dict.set_item(&*s, s.clone().into(), vm).unwrap(); + } + dict.into() + } + #[derive(FromArgs, Debug)] #[allow(unused)] struct NewHashArgs { @@ -37,15 +84,19 @@ pub mod _hashlib { data: OptionalArg, #[pyarg(named, default = true)] usedforsecurity: bool, + #[pyarg(named, optional)] + string: OptionalArg, } #[derive(FromArgs)] #[allow(unused)] pub struct BlakeHashArgs { - #[pyarg(positional, optional)] + #[pyarg(any, optional)] pub data: OptionalArg, #[pyarg(named, default = true)] usedforsecurity: bool, + #[pyarg(named, optional)] + pub string: OptionalArg, } impl From for BlakeHashArgs { @@ -53,6 +104,7 @@ pub mod _hashlib { Self { data: args.data, usedforsecurity: args.usedforsecurity, + string: args.string, } } } @@ -61,16 +113,19 @@ pub mod _hashlib { #[allow(unused)] pub struct HashArgs { #[pyarg(any, optional)] - pub string: OptionalArg, + pub data: OptionalArg, #[pyarg(named, default = true)] usedforsecurity: bool, + #[pyarg(named, optional)] + pub string: OptionalArg, } impl From for HashArgs { fn from(args: NewHashArgs) -> Self { Self { - string: args.data, + data: args.data, usedforsecurity: args.usedforsecurity, + string: args.string, } } } @@ -118,6 +173,91 @@ pub mod _hashlib { } } + #[derive(FromArgs)] + #[allow(unused)] + struct HmacDigestArgs { + #[pyarg(positional)] + key: ArgBytesLike, + #[pyarg(positional)] + msg: ArgBytesLike, + #[pyarg(positional)] + digest: PyObjectRef, + } + + #[derive(FromArgs)] + #[allow(unused)] + struct Pbkdf2HmacArgs { + #[pyarg(any)] + hash_name: PyStrRef, + #[pyarg(any)] + password: ArgBytesLike, + #[pyarg(any)] + salt: ArgBytesLike, + #[pyarg(any)] + iterations: i64, + #[pyarg(any, optional)] + dklen: OptionalArg, + } + + fn resolve_data( + data: OptionalArg, + string: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + match (data.into_option(), string.into_option()) { + (Some(d), None) => Ok(OptionalArg::Present(d)), + (None, Some(s)) => Ok(OptionalArg::Present(s)), + (None, None) => Ok(OptionalArg::Missing), + (Some(_), Some(_)) => Err(vm.new_type_error( + "'data' and 'string' are mutually exclusive \ + and support for 'string' keyword parameter \ + is slated for removal in a future version." + .to_owned(), + )), + } + } + + fn resolve_digestmod(digestmod: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(name) = digestmod.downcast_ref::() { + return Ok(name.as_str().to_lowercase()); + } + if let Ok(name_obj) = digestmod.get_attr("__name__", vm) + && let Some(name) = name_obj.downcast_ref::() + && let Some(algo) = name.as_str().strip_prefix("openssl_") + { + return Ok(algo.to_owned()); + } + Err(vm.new_exception_msg( + UnsupportedDigestmodError::static_type().to_owned(), + "unsupported digestmod".to_owned(), + )) + } + + fn hash_digest_size(name: &str) -> Option { + match name { + "md5" => Some(16), + "sha1" => Some(20), + "sha224" => Some(28), + "sha256" => Some(32), + "sha384" => Some(48), + "sha512" => Some(64), + "sha3_224" => Some(28), + "sha3_256" => Some(32), + "sha3_384" => Some(48), + "sha3_512" => Some(64), + "blake2b" => Some(64), + "blake2s" => Some(32), + _ => None, + } + } + + fn unsupported_hash(name: &str, vm: &VirtualMachine) -> PyBaseExceptionRef { + vm.new_exception_msg( + UnsupportedDigestmodError::static_type().to_owned(), + format!("unsupported hash type {name}"), + ) + } + #[pyattr] #[pyclass(module = "_hashlib", name = "HASH")] #[derive(PyPayload)] @@ -309,93 +449,163 @@ pub mod _hashlib { #[pyfunction(name = "new")] fn hashlib_new(args: NewHashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; match args.name.as_str().to_lowercase().as_str() { - "md5" => Ok(local_md5(args.into()).into_pyobject(vm)), - "sha1" => Ok(local_sha1(args.into()).into_pyobject(vm)), - "sha224" => Ok(local_sha224(args.into()).into_pyobject(vm)), - "sha256" => Ok(local_sha256(args.into()).into_pyobject(vm)), - "sha384" => Ok(local_sha384(args.into()).into_pyobject(vm)), - "sha512" => Ok(local_sha512(args.into()).into_pyobject(vm)), - "sha3_224" => Ok(local_sha3_224(args.into()).into_pyobject(vm)), - "sha3_256" => Ok(local_sha3_256(args.into()).into_pyobject(vm)), - "sha3_384" => Ok(local_sha3_384(args.into()).into_pyobject(vm)), - "sha3_512" => Ok(local_sha3_512(args.into()).into_pyobject(vm)), - "shake_128" => Ok(local_shake_128(args.into()).into_pyobject(vm)), - "shake_256" => Ok(local_shake_256(args.into()).into_pyobject(vm)), - "blake2b" => Ok(local_blake2b(args.into()).into_pyobject(vm)), - "blake2s" => Ok(local_blake2s(args.into()).into_pyobject(vm)), + "md5" => Ok(PyHasher::new("md5", HashWrapper::new::(data)).into_pyobject(vm)), + "sha1" => Ok(PyHasher::new("sha1", HashWrapper::new::(data)).into_pyobject(vm)), + "sha224" => { + Ok(PyHasher::new("sha224", HashWrapper::new::(data)).into_pyobject(vm)) + } + "sha256" => { + Ok(PyHasher::new("sha256", HashWrapper::new::(data)).into_pyobject(vm)) + } + "sha384" => { + Ok(PyHasher::new("sha384", HashWrapper::new::(data)).into_pyobject(vm)) + } + "sha512" => { + Ok(PyHasher::new("sha512", HashWrapper::new::(data)).into_pyobject(vm)) + } + "sha3_224" => { + Ok(PyHasher::new("sha3_224", HashWrapper::new::(data)).into_pyobject(vm)) + } + "sha3_256" => { + Ok(PyHasher::new("sha3_256", HashWrapper::new::(data)).into_pyobject(vm)) + } + "sha3_384" => { + Ok(PyHasher::new("sha3_384", HashWrapper::new::(data)).into_pyobject(vm)) + } + "sha3_512" => { + Ok(PyHasher::new("sha3_512", HashWrapper::new::(data)).into_pyobject(vm)) + } + "shake_128" => Ok( + PyHasherXof::new("shake_128", HashXofWrapper::new_shake_128(data)) + .into_pyobject(vm), + ), + "shake_256" => Ok( + PyHasherXof::new("shake_256", HashXofWrapper::new_shake_256(data)) + .into_pyobject(vm), + ), + "blake2b" => Ok( + PyHasher::new("blake2b", HashWrapper::new::(data)).into_pyobject(vm), + ), + "blake2s" => Ok( + PyHasher::new("blake2s", HashWrapper::new::(data)).into_pyobject(vm), + ), other => Err(vm.new_value_error(format!("Unknown hashing algorithm: {other}"))), } } #[pyfunction(name = "openssl_md5")] - pub fn local_md5(args: HashArgs) -> PyHasher { - PyHasher::new("md5", HashWrapper::new::(args.string)) + pub fn local_md5(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new("md5", HashWrapper::new::(data))) } #[pyfunction(name = "openssl_sha1")] - pub fn local_sha1(args: HashArgs) -> PyHasher { - PyHasher::new("sha1", HashWrapper::new::(args.string)) + pub fn local_sha1(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new("sha1", HashWrapper::new::(data))) } #[pyfunction(name = "openssl_sha224")] - pub fn local_sha224(args: HashArgs) -> PyHasher { - PyHasher::new("sha224", HashWrapper::new::(args.string)) + pub fn local_sha224(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new("sha224", HashWrapper::new::(data))) } #[pyfunction(name = "openssl_sha256")] - pub fn local_sha256(args: HashArgs) -> PyHasher { - PyHasher::new("sha256", HashWrapper::new::(args.string)) + pub fn local_sha256(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new("sha256", HashWrapper::new::(data))) } #[pyfunction(name = "openssl_sha384")] - pub fn local_sha384(args: HashArgs) -> PyHasher { - PyHasher::new("sha384", HashWrapper::new::(args.string)) + pub fn local_sha384(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new("sha384", HashWrapper::new::(data))) } #[pyfunction(name = "openssl_sha512")] - pub fn local_sha512(args: HashArgs) -> PyHasher { - PyHasher::new("sha512", HashWrapper::new::(args.string)) + pub fn local_sha512(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new("sha512", HashWrapper::new::(data))) } #[pyfunction(name = "openssl_sha3_224")] - pub fn local_sha3_224(args: HashArgs) -> PyHasher { - PyHasher::new("sha3_224", HashWrapper::new::(args.string)) + pub fn local_sha3_224(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new( + "sha3_224", + HashWrapper::new::(data), + )) } #[pyfunction(name = "openssl_sha3_256")] - pub fn local_sha3_256(args: HashArgs) -> PyHasher { - PyHasher::new("sha3_256", HashWrapper::new::(args.string)) + pub fn local_sha3_256(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new( + "sha3_256", + HashWrapper::new::(data), + )) } #[pyfunction(name = "openssl_sha3_384")] - pub fn local_sha3_384(args: HashArgs) -> PyHasher { - PyHasher::new("sha3_384", HashWrapper::new::(args.string)) + pub fn local_sha3_384(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new( + "sha3_384", + HashWrapper::new::(data), + )) } #[pyfunction(name = "openssl_sha3_512")] - pub fn local_sha3_512(args: HashArgs) -> PyHasher { - PyHasher::new("sha3_512", HashWrapper::new::(args.string)) + pub fn local_sha3_512(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new( + "sha3_512", + HashWrapper::new::(data), + )) } #[pyfunction(name = "openssl_shake_128")] - pub fn local_shake_128(args: HashArgs) -> PyHasherXof { - PyHasherXof::new("shake_128", HashXofWrapper::new_shake_128(args.string)) + pub fn local_shake_128(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasherXof::new( + "shake_128", + HashXofWrapper::new_shake_128(data), + )) } #[pyfunction(name = "openssl_shake_256")] - pub fn local_shake_256(args: HashArgs) -> PyHasherXof { - PyHasherXof::new("shake_256", HashXofWrapper::new_shake_256(args.string)) + pub fn local_shake_256(args: HashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasherXof::new( + "shake_256", + HashXofWrapper::new_shake_256(data), + )) } #[pyfunction(name = "openssl_blake2b")] - pub fn local_blake2b(args: BlakeHashArgs) -> PyHasher { - PyHasher::new("blake2b", HashWrapper::new::(args.data)) + pub fn local_blake2b(args: BlakeHashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new( + "blake2b", + HashWrapper::new::(data), + )) } #[pyfunction(name = "openssl_blake2s")] - pub fn local_blake2s(args: BlakeHashArgs) -> PyHasher { - PyHasher::new("blake2s", HashWrapper::new::(args.data)) + pub fn local_blake2s(args: BlakeHashArgs, vm: &VirtualMachine) -> PyResult { + let data = resolve_data(args.data, args.string, vm)?; + Ok(PyHasher::new( + "blake2s", + HashWrapper::new::(data), + )) + } + + #[pyfunction] + fn get_fips_mode() -> i32 { + 0 } #[pyfunction] @@ -434,7 +644,6 @@ pub mod _hashlib { #[pyfunction] fn hmac_new(args: NewHMACHashArgs, vm: &VirtualMachine) -> PyResult { - // Raise UnsupportedDigestmodError so Python's hmac.py falls back to pure-Python implementation let _ = args; Err(vm.new_exception_msg( UnsupportedDigestmodError::static_type().to_owned(), @@ -442,12 +651,94 @@ pub mod _hashlib { )) } + #[pyfunction] + fn hmac_digest(args: HmacDigestArgs, vm: &VirtualMachine) -> PyResult { + let name = resolve_digestmod(&args.digest, vm)?; + + let key_buf = args.key.borrow_buf(); + let msg_buf = args.msg.borrow_buf(); + + macro_rules! do_hmac { + ($hash_ty:ty) => {{ + let mut mac = as Mac>::new_from_slice(&key_buf) + .map_err(|_| vm.new_value_error("invalid key length".to_owned()))?; + Mac::update(&mut mac, &msg_buf); + Ok(mac.finalize().into_bytes().to_vec().into()) + }}; + } + + match name.as_str() { + "md5" => do_hmac!(Md5), + "sha1" => do_hmac!(Sha1), + "sha224" => do_hmac!(Sha224), + "sha256" => do_hmac!(Sha256), + "sha384" => do_hmac!(Sha384), + "sha512" => do_hmac!(Sha512), + "sha3_224" => do_hmac!(Sha3_224), + "sha3_256" => do_hmac!(Sha3_256), + "sha3_384" => do_hmac!(Sha3_384), + "sha3_512" => do_hmac!(Sha3_512), + _ => Err(unsupported_hash(&name, vm)), + } + } + + #[pyfunction] + fn pbkdf2_hmac(args: Pbkdf2HmacArgs, vm: &VirtualMachine) -> PyResult { + let name = args.hash_name.as_str().to_lowercase(); + + if args.iterations < 1 { + return Err(vm.new_value_error("iteration value must be greater than 0.".to_owned())); + } + let rounds = u32::try_from(args.iterations) + .map_err(|_| vm.new_overflow_error("iteration value is too great.".to_owned()))?; + + let dklen: usize = match args.dklen.into_option() { + Some(obj) if vm.is_none(&obj) => { + hash_digest_size(&name).ok_or_else(|| unsupported_hash(&name, vm))? + } + Some(obj) => { + let len: i64 = obj.try_into_value(vm)?; + if len < 1 { + return Err(vm.new_value_error("key length must be greater than 0.".to_owned())); + } + usize::try_from(len).map_err(|_| { + vm.new_overflow_error("key length is too great.".to_owned()) + })? + } + None => hash_digest_size(&name).ok_or_else(|| unsupported_hash(&name, vm))?, + }; + + let password_buf = args.password.borrow_buf(); + let salt_buf = args.salt.borrow_buf(); + let mut dk = vec![0u8; dklen]; + + macro_rules! do_pbkdf2 { + ($hash_ty:ty) => {{ + pbkdf2::pbkdf2_hmac::<$hash_ty>(&password_buf, &salt_buf, rounds, &mut dk); + Ok(dk.into()) + }}; + } + + match name.as_str() { + "md5" => do_pbkdf2!(Md5), + "sha1" => do_pbkdf2!(Sha1), + "sha224" => do_pbkdf2!(Sha224), + "sha256" => do_pbkdf2!(Sha256), + "sha384" => do_pbkdf2!(Sha384), + "sha512" => do_pbkdf2!(Sha512), + "sha3_224" => do_pbkdf2!(Sha3_224), + "sha3_256" => do_pbkdf2!(Sha3_256), + "sha3_384" => do_pbkdf2!(Sha3_384), + "sha3_512" => do_pbkdf2!(Sha3_512), + _ => Err(unsupported_hash(&name, vm)), + } + } + pub trait ThreadSafeDynDigest: DynClone + DynDigest + Sync + Send {} impl ThreadSafeDynDigest for T where T: DynClone + DynDigest + Sync + Send {} clone_trait_object!(ThreadSafeDynDigest); - /// Generic wrapper patching around the hashing libraries. #[derive(Clone)] pub struct HashWrapper { block_size: usize, diff --git a/crates/stdlib/src/md5.rs b/crates/stdlib/src/md5.rs index 11923b165ed..2ff6cd24ff7 100644 --- a/crates/stdlib/src/md5.rs +++ b/crates/stdlib/src/md5.rs @@ -7,6 +7,6 @@ mod _md5 { #[pyfunction] fn md5(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_md5(args).into_pyobject(vm)) + Ok(local_md5(args, vm)?.into_pyobject(vm)) } } diff --git a/crates/stdlib/src/sha1.rs b/crates/stdlib/src/sha1.rs index 28d4bcbbee1..3e3d4928c79 100644 --- a/crates/stdlib/src/sha1.rs +++ b/crates/stdlib/src/sha1.rs @@ -7,6 +7,6 @@ mod _sha1 { #[pyfunction] fn sha1(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha1(args).into_pyobject(vm)) + Ok(local_sha1(args, vm)?.into_pyobject(vm)) } } diff --git a/crates/stdlib/src/sha256.rs b/crates/stdlib/src/sha256.rs index bf4a39373ea..b4c26dc0dd6 100644 --- a/crates/stdlib/src/sha256.rs +++ b/crates/stdlib/src/sha256.rs @@ -5,12 +5,12 @@ mod _sha256 { #[pyfunction] fn sha224(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha224(args).into_pyobject(vm)) + Ok(local_sha224(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn sha256(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha256(args).into_pyobject(vm)) + Ok(local_sha256(args, vm)?.into_pyobject(vm)) } pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { diff --git a/crates/stdlib/src/sha3.rs b/crates/stdlib/src/sha3.rs index e7342d0dd69..0eb2dfa84d5 100644 --- a/crates/stdlib/src/sha3.rs +++ b/crates/stdlib/src/sha3.rs @@ -10,31 +10,31 @@ mod _sha3 { #[pyfunction] fn sha3_224(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha3_224(args).into_pyobject(vm)) + Ok(local_sha3_224(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn sha3_256(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha3_256(args).into_pyobject(vm)) + Ok(local_sha3_256(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn sha3_384(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha3_384(args).into_pyobject(vm)) + Ok(local_sha3_384(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn sha3_512(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha3_512(args).into_pyobject(vm)) + Ok(local_sha3_512(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn shake_128(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_shake_128(args).into_pyobject(vm)) + Ok(local_shake_128(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn shake_256(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_shake_256(args).into_pyobject(vm)) + Ok(local_shake_256(args, vm)?.into_pyobject(vm)) } } diff --git a/crates/stdlib/src/sha512.rs b/crates/stdlib/src/sha512.rs index 303c0067daa..b7c6f02ed66 100644 --- a/crates/stdlib/src/sha512.rs +++ b/crates/stdlib/src/sha512.rs @@ -5,12 +5,12 @@ mod _sha512 { #[pyfunction] fn sha384(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha384(args).into_pyobject(vm)) + Ok(local_sha384(args, vm)?.into_pyobject(vm)) } #[pyfunction] fn sha512(args: HashArgs, vm: &VirtualMachine) -> PyResult { - Ok(local_sha512(args).into_pyobject(vm)) + Ok(local_sha512(args, vm)?.into_pyobject(vm)) } pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 934c79b2ebd..10409d943b3 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -397,7 +397,7 @@ impl VirtualMachine { )?; let mode = if write { "w" } else { "r" }; stdio.set_attr("mode", self.ctx.new_str(mode), self)?; - Ok(stdio) + Ok::<_, self::PyBaseExceptionRef>(stdio) }; // Sandbox stdio: lightweight wrapper using Rust's std::io directly