diff --git a/.gitignore b/.gitignore index fa15286..c5bf0b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/Python-3.9.7.tar.xz +SOURCES/Python-3.9.13.tar.xz diff --git a/.python39.metadata b/.python39.metadata index 547271a..00daee8 100644 --- a/.python39.metadata +++ b/.python39.metadata @@ -1 +1 @@ -5208c1d1e0e859f42a9bdbb110efd0855ec4ecf1 SOURCES/Python-3.9.7.tar.xz +d57e5c8b94fe42e2b403e6eced02b25ed47ca8da SOURCES/Python-3.9.13.tar.xz diff --git a/SOURCES/00189-use-rpm-wheels.patch b/SOURCES/00189-use-rpm-wheels.patch index 02063a1..c7edc79 100644 --- a/SOURCES/00189-use-rpm-wheels.patch +++ b/SOURCES/00189-use-rpm-wheels.patch @@ -1,4 +1,4 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From 2c91575950d4de95d308e30cc4ab20d032b1aceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Wed, 15 Aug 2018 15:36:29 +0200 Subject: [PATCH] 00189: Instead of bundled wheels, use our RPM packaged wheels @@ -8,11 +8,11 @@ We keep them in /usr/share/python-wheels Downstream only: upstream bundles We might eventually pursuit upstream support, but it's low prio --- - Lib/ensurepip/__init__.py | 33 ++++++++++++++++++++++----------- - 1 file changed, 22 insertions(+), 11 deletions(-) + Lib/ensurepip/__init__.py | 37 ++++++++++++++++++++++++++----------- + 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py -index 97dfa7ea71..984e587ea0 100644 +index e510cc7..8de2e55 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -1,3 +1,5 @@ @@ -21,7 +21,7 @@ index 97dfa7ea71..984e587ea0 100644 import os import os.path import sys -@@ -6,16 +8,28 @@ import tempfile +@@ -6,13 +8,29 @@ import tempfile import subprocess from importlib import resources @@ -30,13 +30,13 @@ index 97dfa7ea71..984e587ea0 100644 __all__ = ["version", "bootstrap"] - +-_SETUPTOOLS_VERSION = "58.1.0" +-_PIP_VERSION = "22.0.4" ++ +_WHEEL_DIR = "/usr/share/python39-wheels/" - --_SETUPTOOLS_VERSION = "57.4.0" ++ +_wheels = {} - --_PIP_VERSION = "21.2.3" ++ +def _get_most_recent_wheel_version(pkg): + prefix = os.path.join(_WHEEL_DIR, "{}-".format(pkg)) + _wheels[pkg] = {} @@ -51,10 +51,11 @@ index 97dfa7ea71..984e587ea0 100644 +_SETUPTOOLS_VERSION = _get_most_recent_wheel_version("setuptools") + +_PIP_VERSION = _get_most_recent_wheel_version("pip") - ++ _PROJECTS = [ ("setuptools", _SETUPTOOLS_VERSION, "py3"), -@@ -105,13 +119,10 @@ def _bootstrap(*, root=None, upgrade=False, user=False, + ("pip", _PIP_VERSION, "py3"), +@@ -101,13 +119,10 @@ def _bootstrap(*, root=None, upgrade=False, user=False, # additional paths that need added to sys.path additional_paths = [] for project, version, py_tag in _PROJECTS: @@ -72,3 +73,6 @@ index 97dfa7ea71..984e587ea0 100644 additional_paths.append(os.path.join(tmpdir, wheel_name)) +-- +2.35.3 + diff --git a/SOURCES/00329-fips.patch b/SOURCES/00329-fips.patch index 33ebae8..4170fe4 100644 --- a/SOURCES/00329-fips.patch +++ b/SOURCES/00329-fips.patch @@ -1,715 +1,970 @@ -From 918e294d56e646e67553550c87b4a9e30cac1f67 Mon Sep 17 00:00:00 2001 -From: Charalampos Stratakis -Date: Fri, 29 Jan 2021 14:16:21 +0100 -Subject: [PATCH 01/13] Use python's fall backs for the crypto it implements - only if we are not in FIPS mode +From 37aa11f4c57e08bd3859c0de1c22f1d5296b6fdc Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 11 Aug 2021 16:51:03 +0200 +Subject: [PATCH 01/10] Backport PyModule_AddObjectRef as + _PyModule_AddObjectRef +Having PyModule_AddObjectRef available should make backporting +newer patches easier. The new API is much safer. +The backport adds an underscore so that we don't break extension +modules that define PyModule_AddObjectRef themselves on Python<=3.9 +(which would be a virtuous thing to do). --- - Lib/hashlib.py | 194 +++++++++++++++------------------------ - Lib/test/test_hashlib.py | 1 + - 2 files changed, 76 insertions(+), 119 deletions(-) + Include/modsupport.h | 10 ++++++++++ + Python/modsupport.c | 13 +++++++++++-- + 2 files changed, 21 insertions(+), 2 deletions(-) -diff --git a/Lib/hashlib.py b/Lib/hashlib.py -index 58c340d..1fd80c7 100644 ---- a/Lib/hashlib.py -+++ b/Lib/hashlib.py -@@ -68,8 +68,6 @@ __all__ = __always_supported + ('new', 'algorithms_guaranteed', - 'algorithms_available', 'pbkdf2_hmac') - - --__builtin_constructor_cache = {} -- - # Prefer our blake2 implementation - # OpenSSL 1.1.0 comes with a limited implementation of blake2b/s. The OpenSSL - # implementations neither support keyed blake2 (blake2 MAC) nor advanced -@@ -79,54 +77,64 @@ __block_openssl_constructor = { - 'blake2b', 'blake2s', +diff --git a/Include/modsupport.h b/Include/modsupport.h +index 4c4aab6..d9fac52 100644 +--- a/Include/modsupport.h ++++ b/Include/modsupport.h +@@ -136,7 +136,17 @@ PyAPI_FUNC(PyObject * const *) _PyArg_UnpackKeywords( + void _PyArg_Fini(void); + #endif /* Py_LIMITED_API */ + ++// Add an attribute with name 'name' and value 'obj' to the module 'mod. ++// On success, return 0 on success. ++// On error, raise an exception and return -1. ++// Backported from Python 3.10, where it's available without the underscore ++// in the name, to ease porting patches to RHEL ++PyAPI_FUNC(int) _PyModule_AddObjectRef(PyObject *mod, const char *name, PyObject *value); ++ ++// Similar to PyModule_AddObjectRef() but steal a reference to 'obj' ++// (Py_DECREF(obj)) on success (if it returns 0). + PyAPI_FUNC(int) PyModule_AddObject(PyObject *, const char *, PyObject *); ++ + PyAPI_FUNC(int) PyModule_AddIntConstant(PyObject *, const char *, long); + PyAPI_FUNC(int) PyModule_AddStringConstant(PyObject *, const char *, const char *); + #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000 +diff --git a/Python/modsupport.c b/Python/modsupport.c +index 13482c6..fca1083 100644 +--- a/Python/modsupport.c ++++ b/Python/modsupport.c +@@ -631,7 +631,7 @@ va_build_stack(PyObject **small_stack, Py_ssize_t small_stack_len, + + + int +-PyModule_AddObject(PyObject *m, const char *name, PyObject *o) ++_PyModule_AddObjectRef(PyObject *m, const char *name, PyObject *o) + { + PyObject *dict; + if (!PyModule_Check(m)) { +@@ -655,10 +655,19 @@ PyModule_AddObject(PyObject *m, const char *name, PyObject *o) + } + if (PyDict_SetItemString(dict, name, o)) + return -1; +- Py_DECREF(o); + return 0; } --def __get_builtin_constructor(name): -- cache = __builtin_constructor_cache -- constructor = cache.get(name) -- if constructor is not None: -- return constructor -- try: -- if name in {'SHA1', 'sha1'}: -- import _sha1 -- cache['SHA1'] = cache['sha1'] = _sha1.sha1 -- elif name in {'MD5', 'md5'}: -- 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 -- elif name in {'SHA512', 'sha512', 'SHA384', 'sha384'}: -- import _sha512 -- cache['SHA384'] = cache['sha384'] = _sha512.sha384 -- cache['SHA512'] = cache['sha512'] = _sha512.sha512 -- elif name in {'blake2b', 'blake2s'}: -- import _blake2 -- cache['blake2b'] = _blake2.blake2b -- cache['blake2s'] = _blake2.blake2s -- elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512'}: -- import _sha3 -- cache['sha3_224'] = _sha3.sha3_224 -- cache['sha3_256'] = _sha3.sha3_256 -- cache['sha3_384'] = _sha3.sha3_384 -- cache['sha3_512'] = _sha3.sha3_512 -- elif name in {'shake_128', 'shake_256'}: -- import _sha3 -- cache['shake_128'] = _sha3.shake_128 -- cache['shake_256'] = _sha3.shake_256 -- except ImportError: -- pass # no extension module, this hash is unsupported. -- -- constructor = cache.get(name) -- if constructor is not None: -- return constructor -- -- raise ValueError('unsupported hash type ' + name) -+try: -+ from _hashlib import get_fips_mode -+except ImportError: -+ def get_fips_mode(): -+ return 0 -+ -+if not get_fips_mode(): -+ __builtin_constructor_cache = {} ++int ++PyModule_AddObject(PyObject *mod, const char *name, PyObject *value) ++{ ++ int res = _PyModule_AddObjectRef(mod, name, value); ++ if (res == 0) { ++ Py_DECREF(value); ++ } ++ return res; ++} + -+ def __get_builtin_constructor(name): -+ cache = __builtin_constructor_cache -+ constructor = cache.get(name) -+ if constructor is not None: -+ return constructor -+ try: -+ if name in {'SHA1', 'sha1'}: -+ import _sha1 -+ cache['SHA1'] = cache['sha1'] = _sha1.sha1 -+ elif name in {'MD5', 'md5'}: -+ 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 -+ elif name in {'SHA512', 'sha512', 'SHA384', 'sha384'}: -+ import _sha512 -+ cache['SHA384'] = cache['sha384'] = _sha512.sha384 -+ cache['SHA512'] = cache['sha512'] = _sha512.sha512 -+ elif name in {'blake2b', 'blake2s'}: -+ import _blake2 -+ cache['blake2b'] = _blake2.blake2b -+ cache['blake2s'] = _blake2.blake2s -+ elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512'}: -+ import _sha3 -+ cache['sha3_224'] = _sha3.sha3_224 -+ cache['sha3_256'] = _sha3.sha3_256 -+ cache['sha3_384'] = _sha3.sha3_384 -+ cache['sha3_512'] = _sha3.sha3_512 -+ elif name in {'shake_128', 'shake_256'}: -+ import _sha3 -+ cache['shake_128'] = _sha3.shake_128 -+ cache['shake_256'] = _sha3.shake_256 -+ except ImportError: -+ pass # no extension module, this hash is unsupported. -+ -+ constructor = cache.get(name) -+ if constructor is not None: -+ return constructor -+ -+ raise ValueError('unsupported hash type ' + name) - - - def __get_openssl_constructor(name): -- if name in __block_openssl_constructor: -- # Prefer our builtin blake2 implementation. -- return __get_builtin_constructor(name) -+ if not get_fips_mode(): -+ if name in __block_openssl_constructor: -+ # Prefer our builtin blake2 implementation. -+ return __get_builtin_constructor(name) - try: - # MD5, SHA1, and SHA2 are in all supported OpenSSL versions - # SHA3/shake are available in OpenSSL 1.1.1+ -@@ -141,21 +149,23 @@ def __get_openssl_constructor(name): - return __get_builtin_constructor(name) - - --def __py_new(name, data=b'', **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) -+if not get_fips_mode(): -+ def __py_new(name, data=b'', **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) - + int + PyModule_AddIntConstant(PyObject *m, const char *name, long value) + { +-- +2.35.3 + + +From 3fc28233b7244bb891499a974c3f3cda42454760 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Fri, 13 Aug 2021 13:16:43 +0200 +Subject: [PATCH 02/10] _hashopenssl: Uncomment and use initialization function + list + +This simplifies backporting of future changes. + +We use this change instead of Python 3.10's: + bpo-1635741: Port _hashlib to multiphase initialization (GH-23358) +--- + Modules/_hashopenssl.c | 30 +++++------------------------- + 1 file changed, 5 insertions(+), 25 deletions(-) + +diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c +index 4db058c..56dfff9 100644 +--- a/Modules/_hashopenssl.c ++++ b/Modules/_hashopenssl.c +@@ -2227,7 +2227,6 @@ hashlib_init_hmactype(PyObject *module) + return 0; + } - def __hash_new(name, data=b'', **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) -+ if not get_fips_mode(): -+ if name in __block_openssl_constructor: -+ # Prefer our builtin blake2 implementation. -+ return __get_builtin_constructor(name)(data, **kwargs) - try: - return _hashlib.new(name, data, **kwargs) - except ValueError: -@@ -163,6 +173,8 @@ def __hash_new(name, data=b'', **kwargs): - # 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. -+ if get_fips_mode(): -+ raise - return __get_builtin_constructor(name)(data) +-#if 0 + static PyModuleDef_Slot hashlib_slots[] = { + /* OpenSSL 1.0.2 and LibreSSL */ + {Py_mod_exec, hashlib_openssl_legacy_init}, +@@ -2238,7 +2237,6 @@ static PyModuleDef_Slot hashlib_slots[] = { + {Py_mod_exec, hashlib_md_meth_names}, + {0, NULL} + }; +-#endif + + static struct PyModuleDef _hashlibmodule = { + PyModuleDef_HEAD_INIT, +@@ -2266,29 +2264,11 @@ PyInit__hashlib(void) + return NULL; + } +- if (hashlib_openssl_legacy_init(m) < 0) { +- Py_DECREF(m); +- return NULL; +- } +- if (hashlib_init_hashtable(m) < 0) { +- Py_DECREF(m); +- return NULL; +- } +- if (hashlib_init_evptype(m) < 0) { +- Py_DECREF(m); +- return NULL; +- } +- if (hashlib_init_evpxoftype(m) < 0) { +- Py_DECREF(m); +- return NULL; +- } +- if (hashlib_init_hmactype(m) < 0) { +- Py_DECREF(m); +- return NULL; +- } +- if (hashlib_md_meth_names(m) == -1) { +- Py_DECREF(m); +- return NULL; ++ for (int i=0; hashlib_slots[i].slot; i++) { ++ if (((int (*)(PyObject*))hashlib_slots[i].value)(m) < 0) { ++ Py_DECREF(m); ++ return NULL; ++ } + } -@@ -173,72 +185,14 @@ try: + return m; +-- +2.35.3 + + +From 309e06621a9a8b8220c8f83d588cc76e1fa2380d Mon Sep 17 00:00:00 2001 +From: Christian Heimes +Date: Sat, 27 Mar 2021 14:55:03 +0100 +Subject: [PATCH 03/10] bpo-40645: use C implementation of HMAC (GH-24920, + GH-25063, GH-26079) + +This backports the feature and 2 subsequent bugfixes +from: https://bugs.python.org/issue40645 + +Signed-off-by: Christian Heimes +Co-authored-by: Erlend Egeberg Aasland +Co-Authored-By: Pablo Galindo +--- + Lib/hashlib.py | 1 + + Lib/hmac.py | 86 ++++++---- + Lib/test/test_hmac.py | 114 +++++++------ + .../2021-03-19-10-22-17.bpo-40645.5pXhb-.rst | 2 + + Modules/_hashopenssl.c | 150 ++++++++++++++++-- + Modules/clinic/_hashopenssl.c.h | 38 +---- + 6 files changed, 265 insertions(+), 126 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2021-03-19-10-22-17.bpo-40645.5pXhb-.rst + +diff --git a/Lib/hashlib.py b/Lib/hashlib.py +index 58c340d..ffa3be0 100644 +--- a/Lib/hashlib.py ++++ b/Lib/hashlib.py +@@ -173,6 +173,7 @@ try: algorithms_available = algorithms_available.union( _hashlib.openssl_md_meth_names) except ImportError: -+ if get_fips_mode: -+ raise ++ _hashlib = None new = __py_new __get_hash = __get_builtin_constructor --try: -- # OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA -- from _hashlib import pbkdf2_hmac --except ImportError: -- _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) +diff --git a/Lib/hmac.py b/Lib/hmac.py +index 180bc37..8b4f920 100644 +--- a/Lib/hmac.py ++++ b/Lib/hmac.py +@@ -8,11 +8,12 @@ try: + import _hashlib as _hashopenssl + except ImportError: + _hashopenssl = None +- _openssl_md_meths = None ++ _functype = None + from _operator import _compare_digest as compare_digest + else: +- _openssl_md_meths = frozenset(_hashopenssl.openssl_md_meth_names) + compare_digest = _hashopenssl.compare_digest ++ _functype = type(_hashopenssl.openssl_sha256) # builtin type ++ + import hashlib as _hashlib + + trans_5C = bytes((x ^ 0x5C) for x in range(256)) +@@ -23,7 +24,6 @@ trans_36 = bytes((x ^ 0x36) for x in range(256)) + digest_size = None + -- 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. -- """ -- 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)) + class HMAC: + """RFC 2104 HMAC class. Also complies with RFC 4231. + +@@ -32,7 +32,7 @@ class HMAC: + blocksize = 64 # 512-bit HMAC; can be changed in subclasses. + + __slots__ = ( +- "_digest_cons", "_inner", "_outer", "block_size", "digest_size" ++ "_hmac", "_inner", "_outer", "block_size", "digest_size" + ) + + def __init__(self, key, msg=None, digestmod=''): +@@ -55,15 +55,30 @@ class HMAC: + if not digestmod: + raise TypeError("Missing required parameter 'digestmod'.") + ++ if _hashopenssl and isinstance(digestmod, (str, _functype)): ++ try: ++ self._init_hmac(key, msg, digestmod) ++ except _hashopenssl.UnsupportedDigestmodError: ++ self._init_old(key, msg, digestmod) ++ else: ++ self._init_old(key, msg, digestmod) ++ ++ def _init_hmac(self, key, msg, digestmod): ++ self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod) ++ self.digest_size = self._hmac.digest_size ++ self.block_size = self._hmac.block_size ++ ++ def _init_old(self, key, msg, digestmod): + if callable(digestmod): +- self._digest_cons = digestmod ++ digest_cons = digestmod + elif isinstance(digestmod, str): +- self._digest_cons = lambda d=b'': _hashlib.new(digestmod, d) ++ digest_cons = lambda d=b'': _hashlib.new(digestmod, d) + else: +- self._digest_cons = lambda d=b'': digestmod.new(d) ++ digest_cons = lambda d=b'': digestmod.new(d) + +- self._outer = self._digest_cons() +- self._inner = self._digest_cons() ++ self._hmac = None ++ self._outer = digest_cons() ++ self._inner = digest_cons() + self.digest_size = self._inner.digest_size + + if hasattr(self._inner, 'block_size'): +@@ -79,13 +94,13 @@ class HMAC: + RuntimeWarning, 2) + blocksize = self.blocksize + ++ if len(key) > blocksize: ++ key = digest_cons(key).digest() ++ + # self.blocksize is the default blocksize. self.block_size is + # effective block size as well as the public API attribute. + self.block_size = blocksize + +- if len(key) > blocksize: +- key = self._digest_cons(key).digest() - -- 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() + key = key.ljust(blocksize, b'\0') + self._outer.update(key.translate(trans_5C)) + self._inner.update(key.translate(trans_36)) +@@ -94,23 +109,15 @@ class HMAC: + + @property + def name(self): +- return "hmac-" + self._inner.name - -- if iterations < 1: -- raise ValueError(iterations) -- if dklen is None: -- dklen = outer.digest_size -- if dklen < 1: -- raise ValueError(dklen) +- @property +- def digest_cons(self): +- return self._digest_cons - -- dkey = b'' -- loop = 1 -- from_bytes = int.from_bytes -- while len(dkey) < dklen: -- prev = prf(salt + loop.to_bytes(4, 'big')) -- # endianness doesn't matter here as long to / from use the same -- rkey = int.from_bytes(prev, 'big') -- for i in range(iterations - 1): -- prev = prf(prev) -- # rkey = rkey ^ prev -- rkey ^= from_bytes(prev, 'big') -- loop += 1 -- dkey += rkey.to_bytes(inner.digest_size, 'big') +- @property +- def inner(self): +- return self._inner - -- return dkey[:dklen] -+# OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA -+from _hashlib import pbkdf2_hmac +- @property +- def outer(self): +- return self._outer ++ if self._hmac: ++ return self._hmac.name ++ else: ++ return f"hmac-{self._inner.name}" + + def update(self, msg): + """Feed data from msg into this hashing object.""" +- self._inner.update(msg) ++ inst = self._hmac or self._inner ++ inst.update(msg) + + def copy(self): + """Return a separate copy of this hashing object. +@@ -119,10 +126,14 @@ class HMAC: + """ + # Call __new__ directly to avoid the expensive __init__. + other = self.__class__.__new__(self.__class__) +- other._digest_cons = self._digest_cons + other.digest_size = self.digest_size +- other._inner = self._inner.copy() +- other._outer = self._outer.copy() ++ if self._hmac: ++ other._hmac = self._hmac.copy() ++ other._inner = other._outer = None ++ else: ++ other._hmac = None ++ other._inner = self._inner.copy() ++ other._outer = self._outer.copy() + return other + + def _current(self): +@@ -130,9 +141,12 @@ class HMAC: + + To be used only internally with digest() and hexdigest(). + """ +- h = self._outer.copy() +- h.update(self._inner.digest()) +- return h ++ if self._hmac: ++ return self._hmac ++ else: ++ h = self._outer.copy() ++ h.update(self._inner.digest()) ++ return h + + def digest(self): + """Return the hash value of this hashing object. +@@ -179,9 +193,11 @@ def digest(key, msg, digest): + A hashlib constructor returning a new hash object. *OR* + A module supporting PEP 247. + """ +- if (_hashopenssl is not None and +- isinstance(digest, str) and digest in _openssl_md_meths): +- return _hashopenssl.hmac_digest(key, msg, digest) ++ if _hashopenssl is not None and isinstance(digest, (str, _functype)): ++ try: ++ return _hashopenssl.hmac_digest(key, msg, digest) ++ except _hashopenssl.UnsupportedDigestmodError: ++ pass + + if callable(digest): + digest_cons = digest +diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py +index 6daf22c..adf52ad 100644 +--- a/Lib/test/test_hmac.py ++++ b/Lib/test/test_hmac.py +@@ -11,14 +11,21 @@ from test.support import hashlib_helper + from _operator import _compare_digest as operator_compare_digest try: - # OpenSSL's scrypt requires OpenSSL 1.1+ -@@ -259,4 +213,6 @@ for __func_name in __always_supported: - - # Cleanup locals() - del __always_supported, __func_name, __get_hash --del __py_new, __hash_new, __get_openssl_constructor -+del __hash_new, __get_openssl_constructor -+if not get_fips_mode(): -+ del __py_new -diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index 86f31a5..8235505 100644 ---- a/Lib/test/test_hashlib.py -+++ b/Lib/test/test_hashlib.py -@@ -1039,6 +1039,7 @@ class KDFTests(unittest.TestCase): - iterations=1, dklen=None) - self.assertEqual(out, self.pbkdf2_results['sha1'][0][0]) ++ import _hashlib as _hashopenssl + from _hashlib import HMAC as C_HMAC + from _hashlib import hmac_new as c_hmac_new + from _hashlib import compare_digest as openssl_compare_digest + except ImportError: ++ _hashopenssl = None + C_HMAC = None + c_hmac_new = None + openssl_compare_digest = None -+ @unittest.skip("The python implementation of pbkdf2_hmac has been removed") - @unittest.skipIf(builtin_hashlib is None, "test requires builtin_hashlib") - def test_pbkdf2_hmac_py(self): - self._test_pbkdf2_hmac(builtin_hashlib.pbkdf2_hmac, builtin_hashes) --- -2.31.1 - - -From 93696af7133bf08fd76fb759b24c7f82b90220da Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Thu, 25 Jul 2019 17:19:06 +0200 -Subject: [PATCH 02/13] Disable Python's hash implementations in FIPS mode, - forcing OpenSSL - ---- - Include/_hashopenssl.h | 66 ++++++++++++++++++++++++++++++++++ - Modules/_blake2/blake2b_impl.c | 5 +++ - Modules/_blake2/blake2module.c | 3 ++ - Modules/_blake2/blake2s_impl.c | 5 +++ - Modules/_hashopenssl.c | 35 +----------------- - setup.py | 28 ++++++++++----- - 6 files changed, 99 insertions(+), 43 deletions(-) - create mode 100644 Include/_hashopenssl.h - -diff --git a/Include/_hashopenssl.h b/Include/_hashopenssl.h -new file mode 100644 -index 0000000..a726c0d ---- /dev/null -+++ b/Include/_hashopenssl.h -@@ -0,0 +1,66 @@ -+#ifndef Py_HASHOPENSSL_H -+#define Py_HASHOPENSSL_H ++try: ++ import _sha256 as sha256_module ++except ImportError: ++ sha256_module = None ++ + + def ignore_warning(func): + @functools.wraps(func) +@@ -32,22 +39,27 @@ def ignore_warning(func): + + class TestVectorsTestCase(unittest.TestCase): + +- def asssert_hmac( +- self, key, data, digest, hashfunc, hashname, digest_size, block_size ++ def assert_hmac_internals( ++ self, h, digest, hashname, digest_size, block_size + ): +- h = hmac.HMAC(key, data, digestmod=hashfunc) + self.assertEqual(h.hexdigest().upper(), digest.upper()) + self.assertEqual(h.digest(), binascii.unhexlify(digest)) + self.assertEqual(h.name, f"hmac-{hashname}") + self.assertEqual(h.digest_size, digest_size) + self.assertEqual(h.block_size, block_size) + ++ def assert_hmac( ++ self, key, data, digest, hashfunc, hashname, digest_size, block_size ++ ): ++ h = hmac.HMAC(key, data, digestmod=hashfunc) ++ self.assert_hmac_internals( ++ h, digest, hashname, digest_size, block_size ++ ) ++ + h = hmac.HMAC(key, data, digestmod=hashname) +- self.assertEqual(h.hexdigest().upper(), digest.upper()) +- self.assertEqual(h.digest(), binascii.unhexlify(digest)) +- self.assertEqual(h.name, f"hmac-{hashname}") +- self.assertEqual(h.digest_size, digest_size) +- self.assertEqual(h.block_size, block_size) ++ self.assert_hmac_internals( ++ h, digest, hashname, digest_size, block_size ++ ) + + h = hmac.HMAC(key, digestmod=hashname) + h2 = h.copy() +@@ -56,11 +68,9 @@ class TestVectorsTestCase(unittest.TestCase): + self.assertEqual(h.hexdigest().upper(), digest.upper()) + + h = hmac.new(key, data, digestmod=hashname) +- self.assertEqual(h.hexdigest().upper(), digest.upper()) +- self.assertEqual(h.digest(), binascii.unhexlify(digest)) +- self.assertEqual(h.name, f"hmac-{hashname}") +- self.assertEqual(h.digest_size, digest_size) +- self.assertEqual(h.block_size, block_size) ++ self.assert_hmac_internals( ++ h, digest, hashname, digest_size, block_size ++ ) + + h = hmac.new(key, None, digestmod=hashname) + h.update(data) +@@ -81,23 +91,18 @@ class TestVectorsTestCase(unittest.TestCase): + hmac.digest(key, data, digest=hashfunc), + binascii.unhexlify(digest) + ) +- with unittest.mock.patch('hmac._openssl_md_meths', {}): +- self.assertEqual( +- hmac.digest(key, data, digest=hashname), +- binascii.unhexlify(digest) +- ) +- self.assertEqual( +- hmac.digest(key, data, digest=hashfunc), +- binascii.unhexlify(digest) +- ) + -+#include "Python.h" -+#include -+#include ++ h = hmac.HMAC.__new__(hmac.HMAC) ++ h._init_old(key, data, digestmod=hashname) ++ self.assert_hmac_internals( ++ h, digest, hashname, digest_size, block_size ++ ) + + if c_hmac_new is not None: + h = c_hmac_new(key, data, digestmod=hashname) +- self.assertEqual(h.hexdigest().upper(), digest.upper()) +- self.assertEqual(h.digest(), binascii.unhexlify(digest)) +- self.assertEqual(h.name, f"hmac-{hashname}") +- self.assertEqual(h.digest_size, digest_size) +- self.assertEqual(h.block_size, block_size) ++ self.assert_hmac_internals( ++ h, digest, hashname, digest_size, block_size ++ ) + + h = c_hmac_new(key, digestmod=hashname) + h2 = h.copy() +@@ -105,12 +110,24 @@ class TestVectorsTestCase(unittest.TestCase): + h.update(data) + self.assertEqual(h.hexdigest().upper(), digest.upper()) + ++ func = getattr(_hashopenssl, f"openssl_{hashname}") ++ h = c_hmac_new(key, data, digestmod=func) ++ self.assert_hmac_internals( ++ h, digest, hashname, digest_size, block_size ++ ) + -+/* LCOV_EXCL_START */ -+static PyObject * -+_setException(PyObject *exc) -+{ -+ unsigned long errcode; -+ const char *lib, *func, *reason; ++ h = hmac.HMAC.__new__(hmac.HMAC) ++ h._init_hmac(key, data, digestmod=hashname) ++ self.assert_hmac_internals( ++ h, digest, hashname, digest_size, block_size ++ ) + -+ errcode = ERR_peek_last_error(); -+ if (!errcode) { -+ PyErr_SetString(exc, "unknown reasons"); -+ return NULL; -+ } -+ ERR_clear_error(); + @hashlib_helper.requires_hashdigest('md5', openssl=True) + def test_md5_vectors(self): + # Test the HMAC module against test vectors from the RFC. + + def md5test(key, data, digest): +- self.asssert_hmac( ++ self.assert_hmac( + key, data, digest, + hashfunc=hashlib.md5, + hashname="md5", +@@ -150,7 +167,7 @@ class TestVectorsTestCase(unittest.TestCase): + @hashlib_helper.requires_hashdigest('sha1', openssl=True) + def test_sha_vectors(self): + def shatest(key, data, digest): +- self.asssert_hmac( ++ self.assert_hmac( + key, data, digest, + hashfunc=hashlib.sha1, + hashname="sha1", +@@ -191,7 +208,7 @@ class TestVectorsTestCase(unittest.TestCase): + def hmactest(key, data, hexdigests): + digest = hexdigests[hashfunc] + +- self.asssert_hmac( ++ self.assert_hmac( + key, data, digest, + hashfunc=hashfunc, + hashname=hash_name, +@@ -427,6 +444,15 @@ class ConstructorTestCase(unittest.TestCase): + ): + C_HMAC() + ++ @unittest.skipUnless(sha256_module is not None, 'need _sha256') ++ def test_with_sha256_module(self): ++ h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) ++ self.assertEqual(h.hexdigest(), self.expected) ++ self.assertEqual(h.name, "hmac-sha256") + -+ lib = ERR_lib_error_string(errcode); -+ func = ERR_func_error_string(errcode); -+ reason = ERR_reason_error_string(errcode); ++ digest = hmac.digest(b"key", b"hash this!", sha256_module.sha256) ++ self.assertEqual(digest, binascii.unhexlify(self.expected)) + -+ if (lib && func) { -+ PyErr_Format(exc, "[%s: %s] %s", lib, func, reason); -+ } -+ else if (lib) { -+ PyErr_Format(exc, "[%s] %s", lib, reason); + + class SanityTestCase(unittest.TestCase): + +@@ -447,21 +473,21 @@ class SanityTestCase(unittest.TestCase): + class CopyTestCase(unittest.TestCase): + + @hashlib_helper.requires_hashdigest('sha256') +- def test_attributes(self): ++ def test_attributes_old(self): + # Testing if attributes are of same type. +- h1 = hmac.HMAC(b"key", digestmod="sha256") ++ h1 = hmac.HMAC.__new__(hmac.HMAC) ++ h1._init_old(b"key", b"msg", digestmod="sha256") + h2 = h1.copy() +- self.assertTrue(h1._digest_cons == h2._digest_cons, +- "digest constructors don't match.") + self.assertEqual(type(h1._inner), type(h2._inner), + "Types of inner don't match.") + self.assertEqual(type(h1._outer), type(h2._outer), + "Types of outer don't match.") + + @hashlib_helper.requires_hashdigest('sha256') +- def test_realcopy(self): ++ def test_realcopy_old(self): + # Testing if the copy method created a real copy. +- h1 = hmac.HMAC(b"key", digestmod="sha256") ++ h1 = hmac.HMAC.__new__(hmac.HMAC) ++ h1._init_old(b"key", b"msg", digestmod="sha256") + h2 = h1.copy() + # Using id() in case somebody has overridden __eq__/__ne__. + self.assertTrue(id(h1) != id(h2), "No real copy of the HMAC instance.") +@@ -469,17 +495,15 @@ class CopyTestCase(unittest.TestCase): + "No real copy of the attribute 'inner'.") + self.assertTrue(id(h1._outer) != id(h2._outer), + "No real copy of the attribute 'outer'.") +- self.assertEqual(h1._inner, h1.inner) +- self.assertEqual(h1._outer, h1.outer) +- self.assertEqual(h1._digest_cons, h1.digest_cons) ++ self.assertIs(h1._hmac, None) + ++ @unittest.skipIf(_hashopenssl is None, "test requires _hashopenssl") + @hashlib_helper.requires_hashdigest('sha256') +- def test_properties(self): +- # deprecated properties +- h1 = hmac.HMAC(b"key", digestmod="sha256") +- self.assertEqual(h1._inner, h1.inner) +- self.assertEqual(h1._outer, h1.outer) +- self.assertEqual(h1._digest_cons, h1.digest_cons) ++ def test_realcopy_hmac(self): ++ h1 = hmac.HMAC.__new__(hmac.HMAC) ++ h1._init_hmac(b"key", b"msg", digestmod="sha256") ++ h2 = h1.copy() ++ self.assertTrue(id(h1._hmac) != id(h2._hmac)) + + @hashlib_helper.requires_hashdigest('sha256') + def test_equality(self): +diff --git a/Misc/NEWS.d/next/Library/2021-03-19-10-22-17.bpo-40645.5pXhb-.rst b/Misc/NEWS.d/next/Library/2021-03-19-10-22-17.bpo-40645.5pXhb-.rst +new file mode 100644 +index 0000000..a9ab1c0 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2021-03-19-10-22-17.bpo-40645.5pXhb-.rst +@@ -0,0 +1,2 @@ ++The :mod:`hmac` module now uses OpenSSL's HMAC implementation when digestmod ++argument is a hash name or builtin hash function. +diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c +index 56dfff9..ca9fea9 100644 +--- a/Modules/_hashopenssl.c ++++ b/Modules/_hashopenssl.c +@@ -260,6 +260,8 @@ typedef struct { + PyTypeObject *EVPXOFtype; + #endif + _Py_hashtable_t *hashtable; ++ PyObject *constructs; ++ PyObject *unsupported_digestmod_error; + } _hashlibstate; + + static inline _hashlibstate* +@@ -420,6 +422,48 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht) + return digest; + } + ++/* Get digest EVP from object ++ * ++ * * string ++ * * _hashopenssl builtin function ++ * ++ * on error returns NULL with exception set. ++ */ ++static PY_EVP_MD* ++py_digest_by_digestmod(PyObject *module, PyObject *digestmod, enum Py_hash_type py_ht) { ++ PY_EVP_MD* evp; ++ PyObject *name_obj = NULL; ++ const char *name; ++ ++ if (PyUnicode_Check(digestmod)) { ++ name_obj = digestmod; ++ } else { ++ _hashlibstate *state = get_hashlib_state(module); ++ // borrowed ref ++ name_obj = PyDict_GetItem(state->constructs, digestmod); + } -+ else { -+ PyErr_SetString(exc, reason); ++ if (name_obj == NULL) { ++ _hashlibstate *state = get_hashlib_state(module); ++ PyErr_Clear(); ++ PyErr_Format( ++ state->unsupported_digestmod_error, ++ "Unsupported digestmod %R", digestmod); ++ return NULL; + } -+ return NULL; -+} -+/* LCOV_EXCL_STOP */ + ++ name = PyUnicode_AsUTF8(name_obj); ++ if (name == NULL) { ++ return NULL; ++ } + -+__attribute__((__unused__)) -+static int -+_Py_hashlib_fips_error(char *name) { -+ int result = FIPS_mode(); -+ if (result == 0) { -+ // "If the library was built without support of the FIPS Object Module, -+ // then the function will return 0 with an error code of -+ // CRYPTO_R_FIPS_MODE_NOT_SUPPORTED (0x0f06d065)." -+ // But 0 is also a valid result value. -+ -+ unsigned long errcode = ERR_peek_last_error(); -+ if (errcode) { -+ _setException(PyExc_ValueError); -+ return 1; -+ } -+ return 0; ++ evp = py_digest_by_name(module, name, py_ht); ++ if (evp == NULL) { ++ return NULL; + } -+ PyErr_Format(PyExc_ValueError, "%s is not available in FIPS mode", -+ name); -+ return 1; -+} + -+#define FAIL_RETURN_IN_FIPS_MODE(name) do { \ -+ if (_Py_hashlib_fips_error(name)) return NULL; \ -+} while (0) ++ return evp; ++} + -+#endif // !Py_HASHOPENSSL_H -diff --git a/Modules/_blake2/blake2b_impl.c b/Modules/_blake2/blake2b_impl.c -index 7fb1296..67620af 100644 ---- a/Modules/_blake2/blake2b_impl.c -+++ b/Modules/_blake2/blake2b_impl.c -@@ -14,6 +14,7 @@ - */ + static EVPobject * + newEVPobject(PyTypeObject *type) + { +@@ -1238,7 +1282,6 @@ pbkdf2_hmac_impl(PyObject *module, const char *hash_name, - #include "Python.h" -+#include "_hashopenssl.h" - #include "pystrhex.h" + PY_EVP_MD *digest = py_digest_by_name(module, hash_name, Py_ht_pbkdf2); + if (digest == NULL) { +- PyErr_SetString(PyExc_ValueError, "unsupported hash type"); + goto end; + } - #include "../hashlib.h" -@@ -96,6 +97,8 @@ py_blake2b_new_impl(PyTypeObject *type, PyObject *data, int digest_size, - BLAKE2bObject *self = NULL; - Py_buffer buf; +@@ -1443,25 +1486,21 @@ _hashlib.hmac_digest as _hashlib_hmac_singleshot -+ FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ - self = new_BLAKE2bObject(type); - if (self == NULL) { - goto error; -@@ -274,6 +277,8 @@ _blake2_blake2b_update(BLAKE2bObject *self, PyObject *data) - { - Py_buffer buf; + key: Py_buffer + msg: Py_buffer +- digest: str ++ digest: object -+ FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ - GET_BUFFER_VIEW_OR_ERROUT(data, &buf); + Single-shot HMAC. + [clinic start generated code]*/ - if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) -diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c -index ff142c9..bc67529 100644 ---- a/Modules/_blake2/blake2module.c -+++ b/Modules/_blake2/blake2module.c -@@ -9,6 +9,7 @@ - */ + static PyObject * + _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, +- Py_buffer *msg, const char *digest) +-/*[clinic end generated code: output=15658ede5ab98185 input=019dffc571909a46]*/ ++ Py_buffer *msg, PyObject *digest) ++/*[clinic end generated code: output=82f19965d12706ac input=0a0790cc3db45c2e]*/ + { + unsigned char md[EVP_MAX_MD_SIZE] = {0}; + unsigned int md_len = 0; + unsigned char *result; + PY_EVP_MD *evp; - #include "Python.h" -+#include "_hashopenssl.h" +- evp = py_digest_by_name(module, digest, Py_ht_mac); +- if (evp == NULL) { +- return NULL; +- } + if (key->len > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "key is too long."); +@@ -1473,7 +1512,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, + return NULL; + } - #include "impl/blake2.h" - -@@ -57,6 +58,8 @@ PyInit__blake2(void) - PyObject *m; - PyObject *d; - -+ FAIL_RETURN_IN_FIPS_MODE("blake2"); -+ - m = PyModule_Create(&blake2_module); - if (m == NULL) +- evp = py_digest_by_name(module, digest, Py_ht_mac); ++ evp = py_digest_by_digestmod(module, digest, Py_ht_mac); + if (evp == NULL) { return NULL; -diff --git a/Modules/_blake2/blake2s_impl.c b/Modules/_blake2/blake2s_impl.c -index e3e90d0..57c0f3f 100644 ---- a/Modules/_blake2/blake2s_impl.c -+++ b/Modules/_blake2/blake2s_impl.c -@@ -14,6 +14,7 @@ - */ + } +@@ -1505,15 +1544,15 @@ _hashlib.hmac_new - #include "Python.h" -+#include "_hashopenssl.h" - #include "pystrhex.h" + key: Py_buffer + msg as msg_obj: object(c_default="NULL") = b'' +- digestmod: str(c_default="NULL") = None ++ digestmod: object(c_default="NULL") = None - #include "../hashlib.h" -@@ -96,6 +97,8 @@ py_blake2s_new_impl(PyTypeObject *type, PyObject *data, int digest_size, - BLAKE2sObject *self = NULL; - Py_buffer buf; + Return a new hmac object. + [clinic start generated code]*/ -+ FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ - self = new_BLAKE2sObject(type); - if (self == NULL) { - goto error; -@@ -274,6 +277,8 @@ _blake2_blake2s_update(BLAKE2sObject *self, PyObject *data) + static PyObject * + _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, +- const char *digestmod) +-/*[clinic end generated code: output=9a35673be0cbea1b input=a0878868eb190134]*/ ++ PyObject *digestmod) ++/*[clinic end generated code: output=c20d9e4d9ed6d219 input=5f4071dcc7f34362]*/ { - Py_buffer buf; - -+ FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ - GET_BUFFER_VIEW_OR_ERROUT(data, &buf); - - if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) -diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index ff3a1ae..3d788f5 100644 ---- a/Modules/_hashopenssl.c -+++ b/Modules/_hashopenssl.c -@@ -23,6 +23,7 @@ - #include "Python.h" - #include "hashlib.h" - #include "pystrhex.h" -+#include "_hashopenssl.h" - - /* EVP is the preferred interface to hashing in OpenSSL */ - #include -@@ -30,9 +31,6 @@ - #include - /* We use the object interface to discover what hashes OpenSSL supports. */ - #include --#include -- --#include // FIPS_mode() - - #ifndef OPENSSL_THREADS - # error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL" -@@ -124,37 +122,6 @@ class _hashlib.HMAC "HMACobject *" "((_hashlibstate *)PyModule_GetState(module)) - /*[clinic end generated code: output=da39a3ee5e6b4b0d input=7df1bcf6f75cb8ef]*/ - + PyTypeObject *type = get_hashlib_state(module)->HMACtype; + PY_EVP_MD *digest; +@@ -1527,14 +1566,14 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, + return NULL; + } --/* LCOV_EXCL_START */ --static PyObject * --_setException(PyObject *exc) --{ -- unsigned long errcode; -- const char *lib, *func, *reason; -- -- errcode = ERR_peek_last_error(); -- if (!errcode) { -- PyErr_SetString(exc, "unknown reasons"); -- return NULL; -- } -- ERR_clear_error(); -- -- lib = ERR_lib_error_string(errcode); -- func = ERR_func_error_string(errcode); -- reason = ERR_reason_error_string(errcode); -- -- if (lib && func) { -- PyErr_Format(exc, "[%s: %s] %s", lib, func, reason); -- } -- else if (lib) { -- PyErr_Format(exc, "[%s] %s", lib, reason); -- } -- else { -- PyErr_SetString(exc, reason); -- } -- return NULL; --} --/* LCOV_EXCL_STOP */ -- - /* {Py_tp_new, NULL} doesn't block __new__ */ - static PyObject * - _disabled_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) -diff --git a/setup.py b/setup.py -index 04eb6b2..f72d7ca 100644 ---- a/setup.py -+++ b/setup.py -@@ -2306,7 +2306,7 @@ class PyBuildExt(build_ext): - sources=sources, - depends=depends)) +- if ((digestmod == NULL) || !strlen(digestmod)) { ++ if (digestmod == NULL) { + PyErr_SetString( + PyExc_TypeError, "Missing required parameter 'digestmod'."); + return NULL; + } -- def detect_openssl_hashlib(self): -+ def detect_openssl_args(self): - # Detect SSL support for the socket module (via _ssl) - config_vars = sysconfig.get_config_vars() +- digest = py_digest_by_name(module, digestmod, Py_ht_mac); +- if (!digest) { ++ digest = py_digest_by_digestmod(module, digestmod, Py_ht_mac); ++ if (digest == NULL) { + return NULL; + } -@@ -2327,7 +2327,7 @@ class PyBuildExt(build_ext): - if not openssl_libs: - # libssl and libcrypto not found - self.missing.extend(['_ssl', '_hashlib']) -- return None, None -+ raise ValueError('Cannot build for RHEL without OpenSSL') +@@ -2117,6 +2156,8 @@ hashlib_traverse(PyObject *m, visitproc visit, void *arg) + #ifdef PY_OPENSSL_HAS_SHAKE + Py_VISIT(state->EVPXOFtype); + #endif ++ Py_VISIT(state->constructs); ++ Py_VISIT(state->unsupported_digestmod_error); + return 0; + } - # Find OpenSSL includes - ssl_incs = find_file( -@@ -2335,7 +2335,7 @@ class PyBuildExt(build_ext): - ) - if ssl_incs is None: - self.missing.extend(['_ssl', '_hashlib']) -- return None, None -+ raise ValueError('Cannot build for RHEL without OpenSSL') +@@ -2129,10 +2170,14 @@ hashlib_clear(PyObject *m) + #ifdef PY_OPENSSL_HAS_SHAKE + Py_CLEAR(state->EVPXOFtype); + #endif ++ Py_CLEAR(state->constructs); ++ Py_CLEAR(state->unsupported_digestmod_error); ++ + if (state->hashtable != NULL) { + _Py_hashtable_destroy(state->hashtable); + state->hashtable = NULL; + } ++ + return 0; + } - # OpenSSL 1.0.2 uses Kerberos for KRB5 ciphers - krb5_h = find_file( -@@ -2345,12 +2345,23 @@ class PyBuildExt(build_ext): - if krb5_h: - ssl_incs.extend(krb5_h) +@@ -2227,6 +2272,79 @@ hashlib_init_hmactype(PyObject *module) + return 0; + } ++static int ++hashlib_init_constructors(PyObject *module) ++{ ++ /* Create dict from builtin openssl_hash functions to name ++ * {_hashlib.openssl_sha256: "sha256", ...} ++ */ ++ PyModuleDef *mdef; ++ PyMethodDef *fdef; ++ PyObject *proxy; ++ PyObject *func, *name_obj; ++ _hashlibstate *state = get_hashlib_state(module); ++ ++ mdef = PyModule_GetDef(module); ++ if (mdef == NULL) { ++ return -1; ++ } + -+ ssl_args = { -+ 'include_dirs': openssl_includes, -+ 'library_dirs': openssl_libdirs, -+ 'libraries': ['ssl', 'crypto'], ++ state->constructs = PyDict_New(); ++ if (state->constructs == NULL) { ++ return -1; ++ } ++ ++ for (fdef = mdef->m_methods; fdef->ml_name != NULL; fdef++) { ++ if (strncmp(fdef->ml_name, "openssl_", 8)) { ++ continue; ++ } ++ name_obj = PyUnicode_FromString(fdef->ml_name + 8); ++ if (name_obj == NULL) { ++ return -1; ++ } ++ func = PyObject_GetAttrString(module, fdef->ml_name); ++ if (func == NULL) { ++ Py_DECREF(name_obj); ++ return -1; ++ } ++ int rc = PyDict_SetItem(state->constructs, func, name_obj); ++ Py_DECREF(func); ++ Py_DECREF(name_obj); ++ if (rc < 0) { ++ return -1; + } ++ } + -+ return ssl_args ++ proxy = PyDictProxy_New(state->constructs); ++ if (proxy == NULL) { ++ return -1; ++ } + -+ def detect_openssl_hashlib(self): ++ int rc = _PyModule_AddObjectRef(module, "_constructors", proxy); ++ Py_DECREF(proxy); ++ if (rc < 0) { ++ return -1; ++ } ++ return 0; ++} + -+ config_vars = sysconfig.get_config_vars() ++static int ++hashlib_exception(PyObject *module) ++{ ++ _hashlibstate *state = get_hashlib_state(module); ++ state->unsupported_digestmod_error = PyErr_NewException( ++ "_hashlib.UnsupportedDigestmodError", PyExc_ValueError, NULL); ++ if (state->unsupported_digestmod_error == NULL) { ++ return -1; ++ } ++ if (_PyModule_AddObjectRef(module, "UnsupportedDigestmodError", ++ state->unsupported_digestmod_error) < 0) { ++ return -1; ++ } ++ return 0; ++} + - if config_vars.get("HAVE_X509_VERIFY_PARAM_SET1_HOST"): - self.add(Extension( - '_ssl', ['_ssl.c'], -- include_dirs=openssl_includes, -- library_dirs=openssl_libdirs, -- libraries=openssl_libs, -+ **self.detect_openssl_args(), - depends=[ - 'socketmodule.h', - '_ssl/debughelpers.c', -@@ -2363,9 +2374,7 @@ class PyBuildExt(build_ext): ++ + static PyModuleDef_Slot hashlib_slots[] = { + /* OpenSSL 1.0.2 and LibreSSL */ + {Py_mod_exec, hashlib_openssl_legacy_init}, +@@ -2235,6 +2353,8 @@ static PyModuleDef_Slot hashlib_slots[] = { + {Py_mod_exec, hashlib_init_evpxoftype}, + {Py_mod_exec, hashlib_init_hmactype}, + {Py_mod_exec, hashlib_md_meth_names}, ++ {Py_mod_exec, hashlib_init_constructors}, ++ {Py_mod_exec, hashlib_exception}, + {0, NULL} + }; - self.add(Extension('_hashlib', ['_hashopenssl.c'], - depends=['hashlib.h'], -- include_dirs=openssl_includes, -- library_dirs=openssl_libdirs, -- libraries=openssl_libs)) -+ **self.detect_openssl_args()) ) +diff --git a/Modules/clinic/_hashopenssl.c.h b/Modules/clinic/_hashopenssl.c.h +index 68aa765..4466ec4 100644 +--- a/Modules/clinic/_hashopenssl.c.h ++++ b/Modules/clinic/_hashopenssl.c.h +@@ -1106,7 +1106,7 @@ PyDoc_STRVAR(_hashlib_hmac_singleshot__doc__, - def detect_hash_builtins(self): - # By default we always compile these even when OpenSSL is available -@@ -2422,6 +2431,7 @@ class PyBuildExt(build_ext): - '_blake2/blake2b_impl.c', - '_blake2/blake2s_impl.c' - ], -+ **self.detect_openssl_args(), - depends=blake2_deps - )) + static PyObject * + _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, +- Py_buffer *msg, const char *digest); ++ Py_buffer *msg, PyObject *digest); + + static PyObject * + _hashlib_hmac_singleshot(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +@@ -1117,7 +1117,7 @@ _hashlib_hmac_singleshot(PyObject *module, PyObject *const *args, Py_ssize_t nar + PyObject *argsbuf[3]; + Py_buffer key = {NULL, NULL}; + Py_buffer msg = {NULL, NULL}; +- const char *digest; ++ PyObject *digest; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 0, argsbuf); + if (!args) { +@@ -1137,19 +1137,7 @@ _hashlib_hmac_singleshot(PyObject *module, PyObject *const *args, Py_ssize_t nar + _PyArg_BadArgument("hmac_digest", "argument 'msg'", "contiguous buffer", args[1]); + goto exit; + } +- if (!PyUnicode_Check(args[2])) { +- _PyArg_BadArgument("hmac_digest", "argument 'digest'", "str", args[2]); +- goto exit; +- } +- Py_ssize_t digest_length; +- digest = PyUnicode_AsUTF8AndSize(args[2], &digest_length); +- if (digest == NULL) { +- goto exit; +- } +- if (strlen(digest) != (size_t)digest_length) { +- PyErr_SetString(PyExc_ValueError, "embedded null character"); +- goto exit; +- } ++ digest = args[2]; + return_value = _hashlib_hmac_singleshot_impl(module, &key, &msg, digest); + + exit: +@@ -1176,7 +1164,7 @@ PyDoc_STRVAR(_hashlib_hmac_new__doc__, + + static PyObject * + _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, +- const char *digestmod); ++ PyObject *digestmod); + + static PyObject * + _hashlib_hmac_new(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +@@ -1188,7 +1176,7 @@ _hashlib_hmac_new(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + Py_buffer key = {NULL, NULL}; + PyObject *msg_obj = NULL; +- const char *digestmod = NULL; ++ PyObject *digestmod = NULL; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 3, 0, argsbuf); + if (!args) { +@@ -1210,19 +1198,7 @@ _hashlib_hmac_new(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO + goto skip_optional_pos; + } + } +- if (!PyUnicode_Check(args[2])) { +- _PyArg_BadArgument("hmac_new", "argument 'digestmod'", "str", args[2]); +- goto exit; +- } +- Py_ssize_t digestmod_length; +- digestmod = PyUnicode_AsUTF8AndSize(args[2], &digestmod_length); +- if (digestmod == NULL) { +- goto exit; +- } +- if (strlen(digestmod) != (size_t)digestmod_length) { +- PyErr_SetString(PyExc_ValueError, "embedded null character"); +- goto exit; +- } ++ digestmod = args[2]; + skip_optional_pos: + return_value = _hashlib_hmac_new_impl(module, &key, msg_obj, digestmod); +@@ -1442,4 +1418,4 @@ exit: + #ifndef _HASHLIB_GET_FIPS_MODE_METHODDEF + #define _HASHLIB_GET_FIPS_MODE_METHODDEF + #endif /* !defined(_HASHLIB_GET_FIPS_MODE_METHODDEF) */ +-/*[clinic end generated code: output=b6b280e46bf0b139 input=a9049054013a1b77]*/ ++/*[clinic end generated code: output=7ff9aad0bd53e7ce input=a9049054013a1b77]*/ -- -2.31.1 +2.35.3 -From 0b9b72c27e24e159ed3180e9a7a2a9efa24de7e8 Mon Sep 17 00:00:00 2001 +From 2656f4998c17d8a63b5b45462a2dae5b1b3d520f Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Thu, 12 Dec 2019 16:58:31 +0100 -Subject: [PATCH 03/13] Expose all hashes available to OpenSSL +Subject: [PATCH 04/10] Expose blake2b and blake2s hashes from OpenSSL +These aren't as powerful as Python's own implementation, but they can be +used under FIPS. --- - Include/_hashopenssl.h | 11 ++-- - Modules/_blake2/blake2b_impl.c | 4 +- - Modules/_blake2/blake2module.c | 2 +- - Modules/_blake2/blake2s_impl.c | 4 +- - Modules/_hashopenssl.c | 43 +++++++++++++ + Lib/test/test_hashlib.py | 6 ++ + Modules/_hashopenssl.c | 37 +++++++++++ Modules/clinic/_hashopenssl.c.h | 106 +++++++++++++++++++++++++++++++- - 6 files changed, 158 insertions(+), 12 deletions(-) + 3 files changed, 148 insertions(+), 1 deletion(-) -diff --git a/Include/_hashopenssl.h b/Include/_hashopenssl.h -index a726c0d..47ed003 100644 ---- a/Include/_hashopenssl.h -+++ b/Include/_hashopenssl.h -@@ -39,7 +39,7 @@ _setException(PyObject *exc) - - __attribute__((__unused__)) - static int --_Py_hashlib_fips_error(char *name) { -+_Py_hashlib_fips_error(PyObject *exc, char *name) { - int result = FIPS_mode(); - if (result == 0) { - // "If the library was built without support of the FIPS Object Module, -@@ -49,18 +49,17 @@ _Py_hashlib_fips_error(char *name) { - - unsigned long errcode = ERR_peek_last_error(); - if (errcode) { -- _setException(PyExc_ValueError); -+ _setException(exc); - return 1; - } - return 0; - } -- PyErr_Format(PyExc_ValueError, "%s is not available in FIPS mode", -- name); -+ PyErr_Format(exc, "%s is not available in FIPS mode", name); - return 1; - } - --#define FAIL_RETURN_IN_FIPS_MODE(name) do { \ -- if (_Py_hashlib_fips_error(name)) return NULL; \ -+#define FAIL_RETURN_IN_FIPS_MODE(exc, name) do { \ -+ if (_Py_hashlib_fips_error(exc, name)) return NULL; \ - } while (0) - - #endif // !Py_HASHOPENSSL_H -diff --git a/Modules/_blake2/blake2b_impl.c b/Modules/_blake2/blake2b_impl.c -index 67620af..9e125dc 100644 ---- a/Modules/_blake2/blake2b_impl.c -+++ b/Modules/_blake2/blake2b_impl.c -@@ -97,7 +97,7 @@ py_blake2b_new_impl(PyTypeObject *type, PyObject *data, int digest_size, - BLAKE2bObject *self = NULL; - Py_buffer buf; - -- FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); - - self = new_BLAKE2bObject(type); - if (self == NULL) { -@@ -277,7 +277,7 @@ _blake2_blake2b_update(BLAKE2bObject *self, PyObject *data) - { - Py_buffer buf; - -- FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); - - GET_BUFFER_VIEW_OR_ERROUT(data, &buf); - -diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c -index bc67529..79a9eed 100644 ---- a/Modules/_blake2/blake2module.c -+++ b/Modules/_blake2/blake2module.c -@@ -58,7 +58,7 @@ PyInit__blake2(void) - PyObject *m; - PyObject *d; - -- FAIL_RETURN_IN_FIPS_MODE("blake2"); -+ FAIL_RETURN_IN_FIPS_MODE(PyExc_ImportError, "blake2"); - - m = PyModule_Create(&blake2_module); - if (m == NULL) -diff --git a/Modules/_blake2/blake2s_impl.c b/Modules/_blake2/blake2s_impl.c -index 57c0f3f..b59624d 100644 ---- a/Modules/_blake2/blake2s_impl.c -+++ b/Modules/_blake2/blake2s_impl.c -@@ -97,7 +97,7 @@ py_blake2s_new_impl(PyTypeObject *type, PyObject *data, int digest_size, - BLAKE2sObject *self = NULL; - Py_buffer buf; - -- FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); - - self = new_BLAKE2sObject(type); - if (self == NULL) { -@@ -277,7 +277,7 @@ _blake2_blake2s_update(BLAKE2sObject *self, PyObject *data) - { - Py_buffer buf; - -- FAIL_RETURN_IN_FIPS_MODE("_blake2"); -+ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); - - GET_BUFFER_VIEW_OR_ERROUT(data, &buf); - +diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py +index f845c7a..7aaeb76 100644 +--- a/Lib/test/test_hashlib.py ++++ b/Lib/test/test_hashlib.py +@@ -363,6 +363,12 @@ class HashLibTestCase(unittest.TestCase): + # 2 is for hashlib.name(...) and hashlib.new(name, ...) + self.assertGreaterEqual(len(constructors), 2) + for hash_object_constructor in constructors: ++ ++ # OpenSSL's blake2s & blake2d don't support `key` ++ _name = hash_object_constructor.__name__ ++ if 'key' in kwargs and _name.startswith('openssl_blake2'): ++ return ++ + m = hash_object_constructor(data, **kwargs) + computed = m.hexdigest() if not shake else m.hexdigest(length) + self.assertEqual( diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index 3d788f5..dc130f6 100644 +index ca9fea9..9d98d20 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c -@@ -259,6 +259,12 @@ py_digest_by_name(const char *name) - else if (!strcmp(name, "blake2b512")) { - digest = EVP_blake2b512(); - } -+ else if (!strcmp(name, "blake2s")) { -+ digest = EVP_blake2s256(); -+ } -+ else if (!strcmp(name, "blake2b")) { -+ digest = EVP_blake2b512(); -+ } - #endif - } - -@@ -952,6 +958,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj, +@@ -1138,6 +1138,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj, } @@ -727,7 +982,7 @@ index 3d788f5..dc130f6 100644 +/*[clinic end generated code: output=7a838b1643cde13e input=4ad7fd54268f3689]*/ + +{ -+ return EVP_fast_new(module, data_obj, EVP_blake2b512(), usedforsecurity); ++ return py_evp_fromname(module, Py_hash_blake2b, data_obj, usedforsecurity); +} + +/*[clinic input] @@ -744,14 +999,14 @@ index 3d788f5..dc130f6 100644 +/*[clinic end generated code: output=4eda6b40757471da input=1ed39481ffa4e26a]*/ + +{ -+ return EVP_fast_new(module, data_obj, EVP_blake2s256(), usedforsecurity); ++ return py_evp_fromname(module, Py_hash_blake2s, data_obj, usedforsecurity); +} + + #ifdef PY_OPENSSL_HAS_SHA3 /*[clinic input] -@@ -1938,6 +1979,8 @@ static struct PyMethodDef EVP_functions[] = { +@@ -2135,6 +2170,8 @@ static struct PyMethodDef EVP_functions[] = { _HASHLIB_OPENSSL_SHA256_METHODDEF _HASHLIB_OPENSSL_SHA384_METHODDEF _HASHLIB_OPENSSL_SHA512_METHODDEF @@ -761,7 +1016,7 @@ index 3d788f5..dc130f6 100644 _HASHLIB_OPENSSL_SHA3_256_METHODDEF _HASHLIB_OPENSSL_SHA3_384_METHODDEF diff --git a/Modules/clinic/_hashopenssl.c.h b/Modules/clinic/_hashopenssl.c.h -index 68aa765..2957ae2 100644 +index 4466ec4..54c22b2 100644 --- a/Modules/clinic/_hashopenssl.c.h +++ b/Modules/clinic/_hashopenssl.c.h @@ -540,6 +540,110 @@ exit: @@ -875,874 +1130,518 @@ index 68aa765..2957ae2 100644 #if defined(PY_OPENSSL_HAS_SHA3) PyDoc_STRVAR(_hashlib_openssl_sha3_224__doc__, -@@ -1442,4 +1546,4 @@ exit: +@@ -1418,4 +1522,4 @@ exit: #ifndef _HASHLIB_GET_FIPS_MODE_METHODDEF #define _HASHLIB_GET_FIPS_MODE_METHODDEF #endif /* !defined(_HASHLIB_GET_FIPS_MODE_METHODDEF) */ --/*[clinic end generated code: output=b6b280e46bf0b139 input=a9049054013a1b77]*/ -+/*[clinic end generated code: output=4f8cc45bf0337f8e input=a9049054013a1b77]*/ +-/*[clinic end generated code: output=7ff9aad0bd53e7ce input=a9049054013a1b77]*/ ++/*[clinic end generated code: output=fab05055e982f112 input=a9049054013a1b77]*/ -- -2.31.1 +2.35.3 -From 23e9a37ced6523d2e15c97f716d4dcb6605b1b9a Mon Sep 17 00:00:00 2001 +From 652264a57ab6564bfe775d88502776df95cd897d Mon Sep 17 00:00:00 2001 From: Petr Viktorin -Date: Thu, 25 Jul 2019 18:13:45 +0200 -Subject: [PATCH 04/13] Fix tests +Date: Thu, 1 Aug 2019 17:57:05 +0200 +Subject: [PATCH 05/10] Use a stronger hash in multiprocessing handshake +Adapted from patch by David Malcolm, +https://bugs.python.org/issue17258 --- - Lib/test/test_hashlib.py | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index 8235505..a838bce 100644 ---- a/Lib/test/test_hashlib.py -+++ b/Lib/test/test_hashlib.py -@@ -354,6 +354,11 @@ class HashLibTestCase(unittest.TestCase): - # 2 is for hashlib.name(...) and hashlib.new(name, ...) - self.assertGreaterEqual(len(constructors), 2) - for hash_object_constructor in constructors: -+ if ( -+ kwargs -+ and hash_object_constructor.__name__.startswith('openssl_') -+ ): -+ return - m = hash_object_constructor(data, **kwargs) - computed = m.hexdigest() if not shake else m.hexdigest(length) - self.assertEqual( --- -2.31.1 - - -From 20828756a8df0a693b09167d03bf2724bcfddc51 Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Fri, 26 Jul 2019 15:41:10 +0200 -Subject: [PATCH 05/13] Implement hmac.new using new built-in module, - _hmacopenssl - -Make _hmacopenssl.HMAC subclassable; subclass it as hmac.HMAC under FIPS + Lib/multiprocessing/connection.py | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py +index 510e4b5..b68f2fb 100644 +--- a/Lib/multiprocessing/connection.py ++++ b/Lib/multiprocessing/connection.py +@@ -42,6 +42,10 @@ BUFSIZE = 8192 + # A very generous timeout when it comes to local connections... + CONNECTION_TIMEOUT = 20. + ++# The hmac module implicitly defaults to using MD5. ++# Support using a stronger algorithm for the challenge/response code: ++HMAC_DIGEST_NAME='sha256' ++ + _mmap_counter = itertools.count() + + default_family = 'AF_INET' +@@ -741,7 +745,7 @@ def deliver_challenge(connection, authkey): + "Authkey must be bytes, not {0!s}".format(type(authkey))) + message = os.urandom(MESSAGE_LENGTH) + connection.send_bytes(CHALLENGE + message) +- digest = hmac.new(authkey, message, 'md5').digest() ++ digest = hmac.new(authkey, message, HMAC_DIGEST_NAME).digest() + response = connection.recv_bytes(256) # reject large message + if response == digest: + connection.send_bytes(WELCOME) +@@ -757,7 +761,7 @@ def answer_challenge(connection, authkey): + message = connection.recv_bytes(256) # reject large message + assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message + message = message[len(CHALLENGE):] +- digest = hmac.new(authkey, message, 'md5').digest() ++ digest = hmac.new(authkey, message, HMAC_DIGEST_NAME).digest() + connection.send_bytes(digest) + response = connection.recv_bytes(256) # reject large message + if response != WELCOME: +-- +2.35.3 + + +From 4a8637f114196b1ab19435ea64c19c7acf77776c Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Thu, 25 Jul 2019 17:19:06 +0200 +Subject: [PATCH 06/10] Disable Python's hash implementations in FIPS mode, + forcing OpenSSL -This removes the _hmacopenssl.new function. --- - Lib/hmac.py | 37 +++ - Lib/test/test_hmac.py | 28 ++ - Modules/_hmacopenssl.c | 459 ++++++++++++++++++++++++++++++++ - Modules/clinic/_hmacopenssl.c.h | 104 ++++++++ - setup.py | 4 + - 5 files changed, 632 insertions(+) - create mode 100644 Modules/_hmacopenssl.c - create mode 100644 Modules/clinic/_hmacopenssl.c.h + Lib/hashlib.py | 11 +++++++---- + Lib/test/test_hashlib.py | 17 ++++++++++++----- + Modules/_blake2/blake2b_impl.c | 4 ++++ + Modules/_blake2/blake2module.c | 3 +++ + Modules/_blake2/blake2s_impl.c | 4 ++++ + Modules/hashlib.h | 23 +++++++++++++++++++++++ + setup.py | 27 ++++++++++++++++----------- + 7 files changed, 69 insertions(+), 20 deletions(-) -diff --git a/Lib/hmac.py b/Lib/hmac.py -index 180bc37..482e443 100644 ---- a/Lib/hmac.py -+++ b/Lib/hmac.py -@@ -14,6 +14,8 @@ else: - _openssl_md_meths = frozenset(_hashopenssl.openssl_md_meth_names) - compare_digest = _hashopenssl.compare_digest - import hashlib as _hashlib -+import _hashlib as _hashlibopenssl -+import _hmacopenssl +diff --git a/Lib/hashlib.py b/Lib/hashlib.py +index ffa3be0..3e3f4dd 100644 +--- a/Lib/hashlib.py ++++ b/Lib/hashlib.py +@@ -70,14 +70,17 @@ __all__ = __always_supported + ('new', 'algorithms_guaranteed', - trans_5C = bytes((x ^ 0x5C) for x in range(256)) - trans_36 = bytes((x ^ 0x36) for x in range(256)) -@@ -48,6 +50,11 @@ class HMAC: - msg argument. Passing it as a keyword argument is - recommended, though not required for legacy API reasons. - """ -+ if _hashlib.get_fips_mode(): -+ raise ValueError( -+ 'This class is not available in FIPS mode. ' -+ + 'Use hmac.new().' -+ ) + __builtin_constructor_cache = {} - if not isinstance(key, (bytes, bytearray)): - raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) -@@ -110,6 +117,8 @@ class HMAC: +-# Prefer our blake2 implementation ++# Prefer our blake2 implementation (unless in FIPS mode) + # OpenSSL 1.1.0 comes with a limited implementation of blake2b/s. The OpenSSL + # implementations neither support keyed blake2 (blake2 MAC) nor advanced + # features like salt, personalization, or tree hashing. OpenSSL hash-only + # variants are available as 'blake2b512' and 'blake2s256', though. +-__block_openssl_constructor = { +- 'blake2b', 'blake2s', +-} ++import _hashlib ++if _hashlib.get_fips_mode(): ++ __block_openssl_constructor = set() ++else: ++ __block_openssl_constructor = {'blake2b', 'blake2s'} ++ - def update(self, msg): - """Feed data from msg into this hashing object.""" -+ if _hashlib.get_fips_mode(): -+ raise ValueError('hmac.HMAC is not available in FIPS mode') - self._inner.update(msg) + def __get_builtin_constructor(name): + cache = __builtin_constructor_cache +diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py +index 7aaeb76..fa4a8d7 100644 +--- a/Lib/test/test_hashlib.py ++++ b/Lib/test/test_hashlib.py +@@ -35,14 +35,15 @@ else: + m.strip() for m in builtin_hashes.strip('"').lower().split(",") + } - def copy(self): -@@ -150,6 +159,34 @@ class HMAC: - h = self._current() - return h.hexdigest() +-# hashlib with and without OpenSSL backend for PBKDF2 +-# only import builtin_hashlib when all builtin hashes are available. +-# Otherwise import prints noise on stderr ++# RHEL: `_hashlib` is always importable and `hashlib` can't be imported ++# without it. + openssl_hashlib = import_fresh_module('hashlib', fresh=['_hashlib']) +-if builtin_hashes == default_builtin_hashes: ++try: + builtin_hashlib = import_fresh_module('hashlib', blocked=['_hashlib']) +-else: ++except ImportError: + builtin_hashlib = None ++else: ++ raise AssertionError('hashlib is importablee without _hashlib') -+def _get_openssl_name(digestmod): -+ if isinstance(digestmod, str): -+ return digestmod.lower() -+ elif callable(digestmod): -+ digestmod = digestmod(b'') -+ -+ if not isinstance(digestmod, _hashlibopenssl.HASH): -+ raise TypeError( -+ 'Only OpenSSL hashlib hashes are accepted in FIPS mode.') -+ -+ return digestmod.name.lower().replace('_', '-') -+ -+class HMAC_openssl(_hmacopenssl.HMAC): -+ def __new__(cls, key, msg = None, digestmod = None): -+ if not isinstance(key, (bytes, bytearray)): -+ raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) -+ -+ name = _get_openssl_name(digestmod) -+ result = _hmacopenssl.HMAC.__new__(cls, key, digestmod=name) -+ if msg: -+ result.update(msg) -+ return result -+ -+ -+if _hashlib.get_fips_mode(): -+ HMAC = HMAC_openssl -+ -+ - def new(key, msg=None, digestmod=''): - """Create a new hashing object and return it. + try: + from _hashlib import HASH, HASHXOF, openssl_md_meth_names, get_fips_mode +@@ -118,6 +119,12 @@ class HashLibTestCase(unittest.TestCase): + 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) ++ except ImportError: ++ if get_fips_mode() and module_name == '_blake2': ++ # blake2b & blake2s disabled under FIPS ++ return None ++ else: ++ raise + return None -diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py -index 6daf22c..544ec7c 100644 ---- a/Lib/test/test_hmac.py -+++ b/Lib/test/test_hmac.py -@@ -322,6 +322,7 @@ class TestVectorsTestCase(unittest.TestCase): - def test_sha512_rfc4231(self): - self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128) + def __init__(self, *args, **kwargs): +diff --git a/Modules/_blake2/blake2b_impl.c b/Modules/_blake2/blake2b_impl.c +index 7fb1296..bc01cd5 100644 +--- a/Modules/_blake2/blake2b_impl.c ++++ b/Modules/_blake2/blake2b_impl.c +@@ -96,6 +96,8 @@ py_blake2b_new_impl(PyTypeObject *type, PyObject *data, int digest_size, + BLAKE2bObject *self = NULL; + Py_buffer buf; -+ @unittest.skipIf(hashlib.get_fips_mode(), 'MockCrazyHash unacceptable in FIPS mode.') - @hashlib_helper.requires_hashdigest('sha256') - def test_legacy_block_size_warnings(self): - class MockCrazyHash(object): -@@ -371,6 +372,14 @@ class ConstructorTestCase(unittest.TestCase): - except Exception: - self.fail("Standard constructor call raised exception.") ++ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); ++ + self = new_BLAKE2bObject(type); + if (self == NULL) { + goto error; +@@ -274,6 +276,8 @@ _blake2_blake2b_update(BLAKE2bObject *self, PyObject *data) + { + Py_buffer buf; -+ def test_normal_digestmod(self): -+ # Standard constructor call. -+ failed = 0 -+ try: -+ h = hmac.HMAC(b"key", digestmod='sha1') -+ except Exception: -+ self.fail("Standard constructor call raised exception.") ++ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); + - @hashlib_helper.requires_hashdigest('sha256') - def test_with_str_key(self): - # Pass a key of type str, which is an error, because it expects a key -@@ -446,6 +455,7 @@ class SanityTestCase(unittest.TestCase): + GET_BUFFER_VIEW_OR_ERROUT(data, &buf); - class CopyTestCase(unittest.TestCase): + if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) +diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c +index ff142c9..144ec03 100644 +--- a/Modules/_blake2/blake2module.c ++++ b/Modules/_blake2/blake2module.c +@@ -9,6 +9,7 @@ + */ -+ @unittest.skipIf(hashlib.get_fips_mode(), "Internal attributes unavailable in FIPS mode") - @hashlib_helper.requires_hashdigest('sha256') - def test_attributes(self): - # Testing if attributes are of same type. -@@ -458,6 +468,8 @@ class CopyTestCase(unittest.TestCase): - self.assertEqual(type(h1._outer), type(h2._outer), - "Types of outer don't match.") + #include "Python.h" ++#include "../hashlib.h" -+ -+ @unittest.skipIf(hashlib.get_fips_mode(), "Internal attributes unavailable in FIPS mode") - @hashlib_helper.requires_hashdigest('sha256') - def test_realcopy(self): - # Testing if the copy method created a real copy. -@@ -473,6 +485,7 @@ class CopyTestCase(unittest.TestCase): - self.assertEqual(h1._outer, h1.outer) - self.assertEqual(h1._digest_cons, h1.digest_cons) + #include "impl/blake2.h" -+ @unittest.skipIf(hashlib.get_fips_mode(), "Internal attributes unavailable in FIPS mode") - @hashlib_helper.requires_hashdigest('sha256') - def test_properties(self): - # deprecated properties -@@ -481,6 +494,21 @@ class CopyTestCase(unittest.TestCase): - self.assertEqual(h1._outer, h1.outer) - self.assertEqual(h1._digest_cons, h1.digest_cons) - -+ def test_realcopy_by_digest(self): -+ # Testing if the copy method created a real copy. -+ h1 = hmac.HMAC(b"key", digestmod="sha1") -+ h2 = h1.copy() -+ # Using id() in case somebody has overridden __eq__/__ne__. -+ self.assertTrue(id(h1) != id(h2), "No real copy of the HMAC instance.") -+ old_digest = h1.digest() -+ assert h1.digest() == h2.digest() -+ h1.update(b'hi') -+ assert h1.digest() != h2.digest() -+ assert h2.digest() == old_digest -+ new_digest = h1.digest() -+ h2.update(b'hi') -+ assert h1.digest() == h2.digest() == new_digest -+ - @hashlib_helper.requires_hashdigest('sha256') - def test_equality(self): - # Testing if the copy has the same digests. -diff --git a/Modules/_hmacopenssl.c b/Modules/_hmacopenssl.c -new file mode 100644 -index 0000000..c31d233 ---- /dev/null -+++ b/Modules/_hmacopenssl.c -@@ -0,0 +1,459 @@ -+/* Module that wraps all OpenSSL MHAC algorithm */ -+ -+/* Copyright (C) 2019 Red Hat, Inc. Red Hat, Inc. and/or its affiliates -+ * -+ * Based on _hashopenssl.c, which is: -+ * Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org) -+ * Licensed to PSF under a Contributor Agreement. -+ * -+ * Derived from a skeleton of shamodule.c containing work performed by: -+ * -+ * Andrew Kuchling (amk@amk.ca) -+ * Greg Stein (gstein@lyra.org) -+ * -+ */ -+ -+#define PY_SSIZE_T_CLEAN -+ -+#include "Python.h" -+#include "structmember.h" -+#include "hashlib.h" -+#include "pystrhex.h" -+#include "_hashopenssl.h" -+ -+ -+ -+typedef struct hmacopenssl_state { -+ PyTypeObject *HmacType; -+} hmacopenssl_state; -+ -+#include -+ -+typedef struct { -+ PyObject_HEAD -+ PyObject *name; /* name of the hash algorithm */ -+ HMAC_CTX *ctx; /* OpenSSL hmac context */ -+ PyThread_type_lock lock; /* HMAC context lock */ -+} HmacObject; -+ -+#include "clinic/_hmacopenssl.c.h" -+/*[clinic input] -+module _hmacopenssl -+class _hmacopenssl.HMAC "HmacObject *" "((hmacopenssl_state *)PyModule_GetState(module))->HmacType" -+[clinic start generated code]*/ -+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=9fe07a087adc2cf9]*/ -+ -+ -+static PyObject * -+Hmac_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) -+{ -+ static char *kwarg_names[] = {"key", "digestmod", NULL}; -+ Py_buffer key = {NULL, NULL}; -+ char *digestmod = NULL; -+ -+ int ret = PyArg_ParseTupleAndKeywords( -+ args, kwds, "y*|$s:_hmacopenssl.HMAC", kwarg_names, -+ &key, &digestmod); -+ if (ret == 0) { -+ return NULL; -+ } -+ -+ if (digestmod == NULL) { -+ PyErr_SetString(PyExc_ValueError, "digestmod must be specified"); -+ return NULL; -+ } -+ -+ /* name must be lowercase */ -+ for (int i=0; digestmod[i]; i++) { -+ if ( -+ ((digestmod[i] < 'a') || (digestmod[i] > 'z')) -+ && ((digestmod[i] < '0') || (digestmod[i] > '9')) -+ && digestmod[i] != '-' -+ ) { -+ PyErr_SetString(PyExc_ValueError, "digestmod must be lowercase"); -+ return NULL; -+ } -+ } -+ -+ const EVP_MD *digest = EVP_get_digestbyname(digestmod); -+ if (!digest) { -+ PyErr_SetString(PyExc_ValueError, "unknown hash function"); -+ return NULL; -+ } -+ -+ PyObject *name = NULL; -+ HMAC_CTX *ctx = NULL; -+ HmacObject *retval = NULL; -+ -+ name = PyUnicode_FromFormat("hmac-%s", digestmod); -+ if (name == NULL) { -+ goto error; -+ } +@@ -57,6 +58,8 @@ PyInit__blake2(void) + PyObject *m; + PyObject *d; + ++ FAIL_RETURN_IN_FIPS_MODE(PyExc_ImportError, "blake2"); + -+ ctx = HMAC_CTX_new(); -+ if (ctx == NULL) { -+ _setException(PyExc_ValueError); -+ goto error; -+ } + m = PyModule_Create(&blake2_module); + if (m == NULL) + return NULL; +diff --git a/Modules/_blake2/blake2s_impl.c b/Modules/_blake2/blake2s_impl.c +index e3e90d0..e45f8f6 100644 +--- a/Modules/_blake2/blake2s_impl.c ++++ b/Modules/_blake2/blake2s_impl.c +@@ -96,6 +96,8 @@ py_blake2s_new_impl(PyTypeObject *type, PyObject *data, int digest_size, + BLAKE2sObject *self = NULL; + Py_buffer buf; + ++ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); + -+ int r = HMAC_Init_ex( -+ ctx, -+ (const char*)key.buf, -+ key.len, -+ digest, -+ NULL /*impl*/); -+ if (r == 0) { -+ _setException(PyExc_ValueError); -+ goto error; -+ } + self = new_BLAKE2sObject(type); + if (self == NULL) { + goto error; +@@ -274,6 +276,8 @@ _blake2_blake2s_update(BLAKE2sObject *self, PyObject *data) + { + Py_buffer buf; + ++ FAIL_RETURN_IN_FIPS_MODE(PyExc_ValueError, "_blake2"); + -+ PyBuffer_Release(&key); -+ key.buf = NULL; + GET_BUFFER_VIEW_OR_ERROUT(data, &buf); + + if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) +diff --git a/Modules/hashlib.h b/Modules/hashlib.h +index 56ae7a5..45fb403 100644 +--- a/Modules/hashlib.h ++++ b/Modules/hashlib.h +@@ -1,5 +1,11 @@ + /* Common code for use by all hashlib related modules. */ + ++// RHEL: use OpenSSL to turn off unsupported modules under FIPS mode ++// EVP_default_properties_is_fips_enabled() on OpenSSL >= 3.0.0 ++#include ++// FIPS_mode() on OpenSSL < 3.0.0 ++#include + -+ retval = (HmacObject *)subtype->tp_alloc(subtype, 0); -+ if (retval == NULL) { -+ goto error; + /* + * Given a PyObject* obj, fill in the Py_buffer* viewp with the result + * of PyObject_GetBuffer. Sets an exception and issues the erraction +@@ -57,3 +63,20 @@ + * to allow the user to optimize based on the platform they're using. */ + #define HASHLIB_GIL_MINSIZE 2048 + ++__attribute__((__unused__)) ++static int ++_Py_hashlib_fips_error(PyObject *exc, char *name) { ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ if (EVP_default_properties_is_fips_enabled(NULL)) { ++#else ++ if (FIPS_mode()) { ++#endif ++ PyErr_Format(exc, "%s is not available in FIPS mode", name); ++ return 1; + } -+ -+ retval->name = name; -+ retval->ctx = ctx; -+ retval->lock = NULL; -+ -+ return (PyObject*)retval; -+ -+error: -+ if (ctx) HMAC_CTX_free(ctx); -+ if (name) Py_DECREF(name); -+ if (retval) PyObject_Del(name); -+ if (key.buf) PyBuffer_Release(&key); -+ return NULL; ++ return 0; +} + -+/*[clinic input] -+_hmacopenssl.HMAC.copy ++#define FAIL_RETURN_IN_FIPS_MODE(exc, name) do { \ ++ if (_Py_hashlib_fips_error(exc, name)) return NULL; \ ++} while (0) +diff --git a/setup.py b/setup.py +index 0bec170..479f4b5 100644 +--- a/setup.py ++++ b/setup.py +@@ -2315,7 +2315,7 @@ class PyBuildExt(build_ext): + sources=sources, + depends=depends)) + +- def detect_openssl_hashlib(self): ++ def detect_openssl_args(self): + # Detect SSL support for the socket module (via _ssl) + config_vars = sysconfig.get_config_vars() + +@@ -2335,16 +2335,14 @@ class PyBuildExt(build_ext): + openssl_libs = split_var('OPENSSL_LIBS', '-l') + if not openssl_libs: + # libssl and libcrypto not found +- self.missing.extend(['_ssl', '_hashlib']) +- return None, None ++ raise ValueError('Cannot build for RHEL without OpenSSL') + + # Find OpenSSL includes + ssl_incs = find_file( + 'openssl/ssl.h', self.inc_dirs, openssl_includes + ) + if ssl_incs is None: +- self.missing.extend(['_ssl', '_hashlib']) +- return None, None ++ raise ValueError('Cannot build for RHEL without OpenSSL') + + # OpenSSL 1.0.2 uses Kerberos for KRB5 ciphers + krb5_h = find_file( +@@ -2354,12 +2352,20 @@ class PyBuildExt(build_ext): + if krb5_h: + ssl_incs.extend(krb5_h) + ++ return { ++ 'include_dirs': openssl_includes, ++ 'library_dirs': openssl_libdirs, ++ 'libraries': openssl_libs, ++ } + -+Return a copy (“clone”) of the HMAC object. -+[clinic start generated code]*/ ++ def detect_openssl_hashlib(self): + -+static PyObject * -+_hmacopenssl_HMAC_copy_impl(HmacObject *self) -+/*[clinic end generated code: output=fe5ee41faf30dcf0 input=f5ed20feec42d8d0]*/ -+{ -+ HmacObject *retval; ++ config_vars = sysconfig.get_config_vars() + -+ HMAC_CTX *ctx = HMAC_CTX_new(); -+ if (ctx == NULL) { -+ return _setException(PyExc_ValueError); -+ } -+ -+ int r = HMAC_CTX_copy(ctx, self->ctx); -+ if (r == 0) { -+ HMAC_CTX_free(ctx); -+ return _setException(PyExc_ValueError); -+ } -+ -+ retval = (HmacObject *)Py_TYPE(self)->tp_alloc(Py_TYPE(self), 0); -+ if (retval == NULL) { -+ HMAC_CTX_free(ctx); -+ return NULL; -+ } -+ retval->ctx = ctx; -+ Py_INCREF(self->name); -+ retval->name = self->name; -+ -+ retval->lock = NULL; -+ -+ return (PyObject *)retval; -+} -+ -+static void -+_hmac_dealloc(HmacObject *self) -+{ -+ if (self->lock != NULL) { -+ PyThread_free_lock(self->lock); -+ } -+ HMAC_CTX_free(self->ctx); -+ Py_CLEAR(self->name); -+ Py_TYPE(self)->tp_free(self); -+} -+ -+static PyObject * -+_hmac_repr(HmacObject *self) -+{ -+ return PyUnicode_FromFormat("<%U HMAC object @ %p>", self->name, self); -+} -+ -+/*[clinic input] -+_hmacopenssl.HMAC.update -+ -+ msg: Py_buffer -+ -+Update the HMAC object with msg. -+[clinic start generated code]*/ -+ -+static PyObject * -+_hmacopenssl_HMAC_update_impl(HmacObject *self, Py_buffer *msg) -+/*[clinic end generated code: output=0efeee663a98cee5 input=0683d64f35808cb9]*/ -+{ -+ if (self->lock == NULL && msg->len >= HASHLIB_GIL_MINSIZE) { -+ self->lock = PyThread_allocate_lock(); -+ /* fail? lock = NULL and we fail over to non-threaded code. */ -+ } -+ -+ int r; -+ -+ if (self->lock != NULL) { -+ Py_BEGIN_ALLOW_THREADS -+ PyThread_acquire_lock(self->lock, 1); -+ r = HMAC_Update(self->ctx, (const unsigned char*)msg->buf, msg->len); -+ PyThread_release_lock(self->lock); -+ Py_END_ALLOW_THREADS -+ } else { -+ r = HMAC_Update(self->ctx, (const unsigned char*)msg->buf, msg->len); -+ } -+ -+ if (r == 0) { -+ _setException(PyExc_ValueError); -+ return NULL; -+ } -+ Py_RETURN_NONE; -+} -+ -+static unsigned int -+_digest_size(HmacObject *self) -+{ -+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx); -+ if (md == NULL) { -+ _setException(PyExc_ValueError); -+ return 0; -+ } -+ return EVP_MD_size(md); -+} -+ -+static int -+_digest(HmacObject *self, unsigned char *buf, unsigned int len) -+{ -+ HMAC_CTX *temp_ctx = HMAC_CTX_new(); -+ if (temp_ctx == NULL) { -+ PyErr_NoMemory(); -+ return 0; -+ } -+ int r = HMAC_CTX_copy(temp_ctx, self->ctx); -+ if (r == 0) { -+ _setException(PyExc_ValueError); -+ return 0; -+ } -+ r = HMAC_Final(temp_ctx, buf, &len); -+ HMAC_CTX_free(temp_ctx); -+ if (r == 0) { -+ _setException(PyExc_ValueError); -+ return 0; -+ } -+ return 1; -+} -+ -+/*[clinic input] -+_hmacopenssl.HMAC.digest -+ -+Return the digest of the bytes passed to the update() method so far. -+[clinic start generated code]*/ -+ -+static PyObject * -+_hmacopenssl_HMAC_digest_impl(HmacObject *self) -+/*[clinic end generated code: output=3aa6dbfc46ec4957 input=bf769a10b1d9edd9]*/ -+{ -+ unsigned int digest_size = _digest_size(self); -+ if (digest_size == 0) { -+ return _setException(PyExc_ValueError); -+ } -+ unsigned char buf[digest_size]; /* FIXME: C99 feature */ -+ int r = _digest(self, buf, digest_size); -+ if (r == 0) { -+ return NULL; -+ } -+ return PyBytes_FromStringAndSize((const char *)buf, digest_size); -+} -+ -+/*[clinic input] -+_hmacopenssl.HMAC.hexdigest -+ -+Return hexadecimal digest of the bytes passed to the update() method so far. -+ -+This may be used to exchange the value safely in email or other non-binary -+environments. -+[clinic start generated code]*/ -+ -+static PyObject * -+_hmacopenssl_HMAC_hexdigest_impl(HmacObject *self) -+/*[clinic end generated code: output=630f6fa89f9f1e48 input=b8e60ec8b811c4cd]*/ -+{ -+ unsigned int digest_size = _digest_size(self); -+ if (digest_size == 0) { -+ return _setException(PyExc_ValueError); -+ } -+ unsigned char buf[digest_size]; /* FIXME: C99 feature */ -+ int r = _digest(self, buf, digest_size); -+ if (r == 0) { -+ return NULL; -+ } -+ return _Py_strhex((const char *)buf, digest_size); -+} -+ -+ -+ -+static PyObject * -+_hmacopenssl_get_digest_size(HmacObject *self, void *closure) -+{ -+ unsigned int digest_size = _digest_size(self); -+ if (digest_size == 0) { -+ return _setException(PyExc_ValueError); -+ } -+ return PyLong_FromLong(digest_size); -+} -+ -+static PyObject * -+_hmacopenssl_get_block_size(HmacObject *self, void *closure) -+{ -+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx); -+ if (md == NULL) { -+ return _setException(PyExc_ValueError); -+ } -+ return PyLong_FromLong(EVP_MD_block_size(md)); -+} -+ -+static PyMethodDef Hmac_methods[] = { -+ _HMACOPENSSL_HMAC_UPDATE_METHODDEF -+ _HMACOPENSSL_HMAC_DIGEST_METHODDEF -+ _HMACOPENSSL_HMAC_HEXDIGEST_METHODDEF -+ _HMACOPENSSL_HMAC_COPY_METHODDEF -+ {NULL, NULL} /* sentinel */ -+}; -+ -+static PyGetSetDef Hmac_getset[] = { -+ {"digest_size", (getter)_hmacopenssl_get_digest_size, NULL, NULL, NULL}, -+ {"block_size", (getter)_hmacopenssl_get_block_size, NULL, NULL, NULL}, -+ {NULL} /* Sentinel */ -+}; -+ -+static PyMemberDef Hmac_members[] = { -+ {"name", T_OBJECT, offsetof(HmacObject, name), READONLY, PyDoc_STR("HMAC name")}, -+ {NULL} /* Sentinel */ -+}; -+ -+PyDoc_STRVAR(hmactype_doc, -+"The object used to calculate HMAC of a message.\n\ -+\n\ -+Methods:\n\ -+\n\ -+update() -- updates the current digest with an additional string\n\ -+digest() -- return the current digest value\n\ -+hexdigest() -- return the current digest as a string of hexadecimal digits\n\ -+copy() -- return a copy of the current hash object\n\ -+\n\ -+Attributes:\n\ -+\n\ -+name -- the name, including the hash algorithm used by this object\n\ -+digest_size -- number of bytes in digest() output\n"); -+ -+static PyType_Slot HmacType_slots[] = { -+ {Py_tp_doc, (char*)hmactype_doc}, -+ {Py_tp_repr, (reprfunc)_hmac_repr}, -+ {Py_tp_dealloc,(destructor)_hmac_dealloc}, -+ {Py_tp_methods, Hmac_methods}, -+ {Py_tp_getset, Hmac_getset}, -+ {Py_tp_members, Hmac_members}, -+ {Py_tp_new, Hmac_new}, -+ {0, NULL} -+}; -+ -+PyType_Spec HmacType_spec = { -+ "_hmacopenssl.HMAC", /* name */ -+ sizeof(HmacObject), /* basicsize */ -+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, -+ .slots = HmacType_slots, -+}; -+ -+ -+static int -+hmacopenssl_traverse(PyObject *self, visitproc visit, void *arg) -+{ -+ hmacopenssl_state *state; -+ -+ state = PyModule_GetState(self); -+ -+ if (state) { -+ Py_VISIT(state->HmacType); -+ } -+ -+ return 0; -+} -+ -+static int -+hmacopenssl_clear(PyObject *self) -+{ -+ hmacopenssl_state *state; -+ -+ state = PyModule_GetState(self); -+ -+ if (state) { -+ Py_CLEAR(state->HmacType); -+ } -+ -+ return 0; -+} -+ -+ -+ -+/* Initialize this module. */ -+ -+static int -+hmacopenssl_exec(PyObject *m) { -+ /* TODO build EVP_functions openssl_* entries dynamically based -+ * on what hashes are supported rather than listing many -+ * and having some unsupported. Only init appropriate -+ * constants. */ -+ PyObject *temp = NULL; -+ hmacopenssl_state *state; -+ -+ temp = PyType_FromSpec(&HmacType_spec); -+ if (temp == NULL) { -+ goto fail; -+ } -+ -+ if (PyModule_AddObject(m, "HMAC", temp) == -1) { -+ goto fail; -+ } -+ -+ state = PyModule_GetState(m); -+ -+ state->HmacType = (PyTypeObject *)temp; -+ Py_INCREF(temp); -+ -+ -+ return 0; -+ -+fail: -+ Py_XDECREF(temp); -+ return -1; -+} -+ -+static PyModuleDef_Slot hmacopenssl_slots[] = { -+ {Py_mod_exec, hmacopenssl_exec}, -+ {0, NULL}, -+}; -+ -+static struct PyModuleDef _hmacopenssl_def = { -+ PyModuleDef_HEAD_INIT, /* m_base */ -+ .m_name = "_hmacopenssl", -+ .m_slots = hmacopenssl_slots, -+ .m_size = sizeof(hmacopenssl_state), -+ .m_traverse = hmacopenssl_traverse, -+ .m_clear = hmacopenssl_clear -+}; -+ -+ -+PyMODINIT_FUNC -+PyInit__hmacopenssl(void) -+{ -+ return PyModuleDef_Init(&_hmacopenssl_def); -+} -diff --git a/Modules/clinic/_hmacopenssl.c.h b/Modules/clinic/_hmacopenssl.c.h -new file mode 100644 -index 0000000..a2af550 ---- /dev/null -+++ b/Modules/clinic/_hmacopenssl.c.h -@@ -0,0 +1,104 @@ -+/*[clinic input] -+preserve -+[clinic start generated code]*/ -+ -+PyDoc_STRVAR(_hmacopenssl_HMAC_copy__doc__, -+"copy($self, /)\n" -+"--\n" -+"\n" -+"Return a copy (“clone”) of the HMAC object."); -+ -+#define _HMACOPENSSL_HMAC_COPY_METHODDEF \ -+ {"copy", (PyCFunction)_hmacopenssl_HMAC_copy, METH_NOARGS, _hmacopenssl_HMAC_copy__doc__}, -+ -+static PyObject * -+_hmacopenssl_HMAC_copy_impl(HmacObject *self); -+ -+static PyObject * -+_hmacopenssl_HMAC_copy(HmacObject *self, PyObject *Py_UNUSED(ignored)) -+{ -+ return _hmacopenssl_HMAC_copy_impl(self); -+} -+ -+PyDoc_STRVAR(_hmacopenssl_HMAC_update__doc__, -+"update($self, /, msg)\n" -+"--\n" -+"\n" -+"Update the HMAC object with msg."); -+ -+#define _HMACOPENSSL_HMAC_UPDATE_METHODDEF \ -+ {"update", (PyCFunction)(void(*)(void))_hmacopenssl_HMAC_update, METH_FASTCALL|METH_KEYWORDS, _hmacopenssl_HMAC_update__doc__}, -+ -+static PyObject * -+_hmacopenssl_HMAC_update_impl(HmacObject *self, Py_buffer *msg); -+ -+static PyObject * -+_hmacopenssl_HMAC_update(HmacObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -+{ -+ PyObject *return_value = NULL; -+ static const char * const _keywords[] = {"msg", NULL}; -+ static _PyArg_Parser _parser = {NULL, _keywords, "update", 0}; -+ PyObject *argsbuf[1]; -+ Py_buffer msg = {NULL, NULL}; -+ -+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); -+ if (!args) { -+ goto exit; -+ } -+ if (PyObject_GetBuffer(args[0], &msg, PyBUF_SIMPLE) != 0) { -+ goto exit; -+ } -+ if (!PyBuffer_IsContiguous(&msg, 'C')) { -+ _PyArg_BadArgument("update", "argument 'msg'", "contiguous buffer", args[0]); -+ goto exit; -+ } -+ return_value = _hmacopenssl_HMAC_update_impl(self, &msg); -+ -+exit: -+ /* Cleanup for msg */ -+ if (msg.obj) { -+ PyBuffer_Release(&msg); -+ } -+ -+ return return_value; -+} -+ -+PyDoc_STRVAR(_hmacopenssl_HMAC_digest__doc__, -+"digest($self, /)\n" -+"--\n" -+"\n" -+"Return the digest of the bytes passed to the update() method so far."); -+ -+#define _HMACOPENSSL_HMAC_DIGEST_METHODDEF \ -+ {"digest", (PyCFunction)_hmacopenssl_HMAC_digest, METH_NOARGS, _hmacopenssl_HMAC_digest__doc__}, -+ -+static PyObject * -+_hmacopenssl_HMAC_digest_impl(HmacObject *self); -+ -+static PyObject * -+_hmacopenssl_HMAC_digest(HmacObject *self, PyObject *Py_UNUSED(ignored)) -+{ -+ return _hmacopenssl_HMAC_digest_impl(self); -+} -+ -+PyDoc_STRVAR(_hmacopenssl_HMAC_hexdigest__doc__, -+"hexdigest($self, /)\n" -+"--\n" -+"\n" -+"Return hexadecimal digest of the bytes passed to the update() method so far.\n" -+"\n" -+"This may be used to exchange the value safely in email or other non-binary\n" -+"environments."); -+ -+#define _HMACOPENSSL_HMAC_HEXDIGEST_METHODDEF \ -+ {"hexdigest", (PyCFunction)_hmacopenssl_HMAC_hexdigest, METH_NOARGS, _hmacopenssl_HMAC_hexdigest__doc__}, -+ -+static PyObject * -+_hmacopenssl_HMAC_hexdigest_impl(HmacObject *self); -+ -+static PyObject * -+_hmacopenssl_HMAC_hexdigest(HmacObject *self, PyObject *Py_UNUSED(ignored)) -+{ -+ return _hmacopenssl_HMAC_hexdigest_impl(self); -+} -+/*[clinic end generated code: output=e0c910f3c9ed523e input=a9049054013a1b77]*/ -diff --git a/setup.py b/setup.py -index f72d7ca..11fca20 100644 ---- a/setup.py -+++ b/setup.py -@@ -2376,6 +2376,10 @@ class PyBuildExt(build_ext): + if config_vars.get("HAVE_X509_VERIFY_PARAM_SET1_HOST"): + self.add(Extension( + '_ssl', ['_ssl.c'], +- include_dirs=openssl_includes, +- library_dirs=openssl_libdirs, +- libraries=openssl_libs, ++ **self.detect_openssl_args(), + depends=[ + 'socketmodule.h', + '_ssl/debughelpers.c', +@@ -2372,9 +2378,7 @@ class PyBuildExt(build_ext): + + self.add(Extension('_hashlib', ['_hashopenssl.c'], depends=['hashlib.h'], - **self.detect_openssl_args()) ) +- include_dirs=openssl_includes, +- library_dirs=openssl_libdirs, +- libraries=openssl_libs)) ++ **self.detect_openssl_args()) ) -+ self.add(Extension('_hmacopenssl', ['_hmacopenssl.c'], -+ depends = ['hashlib.h'], -+ **self.detect_openssl_args()) ) -+ def detect_hash_builtins(self): # By default we always compile these even when OpenSSL is available - # (issue #14693). It's harmless and the object code is tiny +@@ -2431,6 +2435,7 @@ class PyBuildExt(build_ext): + '_blake2/blake2b_impl.c', + '_blake2/blake2s_impl.c' + ], ++ **self.detect_openssl_args(), # for FIPS_mode verification + depends=blake2_deps + )) + -- -2.31.1 +2.35.3 -From bda4c9de583ee2272812c25d506bea294a54dee8 Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Thu, 1 Aug 2019 17:57:05 +0200 -Subject: [PATCH 06/13] Use a stronger hash in multiprocessing handshake +From 165bcd0377075dbac9fa3f988ed5189668597ab6 Mon Sep 17 00:00:00 2001 +From: Charalampos Stratakis +Date: Fri, 29 Jan 2021 14:16:21 +0100 +Subject: [PATCH 07/10] Use python's fall back crypto implementations only if + we are not in FIPS mode -Adapted from patch by David Malcolm, -https://bugs.python.org/issue17258 --- - Lib/multiprocessing/connection.py | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) + Lib/hashlib.py | 69 +++------------------------------------- + Lib/test/test_hashlib.py | 23 +++++++++++++- + 2 files changed, 27 insertions(+), 65 deletions(-) -diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py -index 510e4b5..b68f2fb 100644 ---- a/Lib/multiprocessing/connection.py -+++ b/Lib/multiprocessing/connection.py -@@ -42,6 +42,10 @@ BUFSIZE = 8192 - # A very generous timeout when it comes to local connections... - CONNECTION_TIMEOUT = 20. +diff --git a/Lib/hashlib.py b/Lib/hashlib.py +index 3e3f4dd..b842f5f 100644 +--- a/Lib/hashlib.py ++++ b/Lib/hashlib.py +@@ -67,7 +67,6 @@ algorithms_available = set(__always_supported) + __all__ = __always_supported + ('new', 'algorithms_guaranteed', + 'algorithms_available', 'pbkdf2_hmac') -+# The hmac module implicitly defaults to using MD5. -+# Support using a stronger algorithm for the challenge/response code: -+HMAC_DIGEST_NAME='sha256' +- + __builtin_constructor_cache = {} + + # Prefer our blake2 implementation (unless in FIPS mode) +@@ -83,6 +82,8 @@ else: + + + def __get_builtin_constructor(name): ++ if _hashlib.get_fips_mode(): ++ raise ValueError('unsupported hash type ' + name + '(in FIPS mode)') + cache = __builtin_constructor_cache + constructor = cache.get(name) + if constructor is not None: +@@ -176,79 +177,19 @@ try: + algorithms_available = algorithms_available.union( + _hashlib.openssl_md_meth_names) + except ImportError: +- _hashlib = None +- new = __py_new +- __get_hash = __get_builtin_constructor ++ raise # importing _hashlib should never fail on RHEL + + try: + # OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA + from _hashlib import pbkdf2_hmac + except ImportError: +- _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. +- """ +- 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, 'big')) +- # endianness doesn't matter here as long to / from use the same +- rkey = int.from_bytes(prev, 'big') +- for i in range(iterations - 1): +- prev = prf(prev) +- # rkey = rkey ^ prev +- rkey ^= from_bytes(prev, 'big') +- loop += 1 +- dkey += rkey.to_bytes(inner.digest_size, 'big') +- +- return dkey[:dklen] ++ raise # importing _hashlib should never fail on RHEL + + try: + # OpenSSL's scrypt requires OpenSSL 1.1+ + from _hashlib import scrypt + except ImportError: +- pass ++ raise # importing _hashlib should never fail on RHEL + + + for __func_name in __always_supported: +diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py +index fa4a8d7..ec6c883 100644 +--- a/Lib/test/test_hashlib.py ++++ b/Lib/test/test_hashlib.py +@@ -171,7 +171,13 @@ class HashLibTestCase(unittest.TestCase): + constructors.add(constructor) + + def add_builtin_constructor(name): +- constructor = getattr(hashlib, "__get_builtin_constructor")(name) ++ try: ++ constructor = getattr(hashlib, "__get_builtin_constructor")(name) ++ except ValueError: ++ if get_fips_mode(): ++ return ++ else: ++ raise + self.constructors_to_test[name].add(constructor) + + _md5 = self._conditional_import_module('_md5') +@@ -266,6 +272,20 @@ class HashLibTestCase(unittest.TestCase): + def test_new_upper_to_lower(self): + self.assertEqual(hashlib.new("SHA256").name, "sha256") + ++ @unittest.skipUnless(get_fips_mode(), "Builtin constructor only usable in FIPS mode") ++ def test_get_builtin_constructor_fips(self): ++ get_builtin_constructor = getattr(hashlib, ++ '__get_builtin_constructor') ++ with self.assertRaises(ValueError): ++ get_builtin_constructor('md5') ++ with self.assertRaises(ValueError): ++ get_builtin_constructor('sha256') ++ with self.assertRaises(ValueError): ++ get_builtin_constructor('blake2s') ++ with self.assertRaises(ValueError): ++ get_builtin_constructor('test') + - _mmap_counter = itertools.count() ++ @unittest.skipIf(get_fips_mode(), "No builtin constructors in FIPS mode") + def test_get_builtin_constructor(self): + get_builtin_constructor = getattr(hashlib, + '__get_builtin_constructor') +@@ -1061,6 +1081,7 @@ class KDFTests(unittest.TestCase): + iterations=1, dklen=None) + self.assertEqual(out, self.pbkdf2_results['sha1'][0][0]) - default_family = 'AF_INET' -@@ -741,7 +745,7 @@ def deliver_challenge(connection, authkey): - "Authkey must be bytes, not {0!s}".format(type(authkey))) - message = os.urandom(MESSAGE_LENGTH) - connection.send_bytes(CHALLENGE + message) -- digest = hmac.new(authkey, message, 'md5').digest() -+ digest = hmac.new(authkey, message, HMAC_DIGEST_NAME).digest() - response = connection.recv_bytes(256) # reject large message - if response == digest: - connection.send_bytes(WELCOME) -@@ -757,7 +761,7 @@ def answer_challenge(connection, authkey): - message = connection.recv_bytes(256) # reject large message - assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message - message = message[len(CHALLENGE):] -- digest = hmac.new(authkey, message, 'md5').digest() -+ digest = hmac.new(authkey, message, HMAC_DIGEST_NAME).digest() - connection.send_bytes(digest) - response = connection.recv_bytes(256) # reject large message - if response != WELCOME: ++ @unittest.skip("The python implementation of pbkdf2_hmac has been removed") + @unittest.skipIf(builtin_hashlib is None, "test requires builtin_hashlib") + def test_pbkdf2_hmac_py(self): + self._test_pbkdf2_hmac(builtin_hashlib.pbkdf2_hmac, builtin_hashes) -- -2.31.1 +2.35.3 -From 381d423df59d59f953ed817e1611dd929393dea9 Mon Sep 17 00:00:00 2001 +From f4383a6e0be8b75db2380fdcf0174b09709b613f Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Wed, 31 Jul 2019 15:43:43 +0200 -Subject: [PATCH 07/13] Add initial tests for various hashes under FIPS mode +Subject: [PATCH 08/10] Test equivalence of hashes for the various digests with + usedforsecurity=True/False --- - Lib/test/test_fips.py | 31 +++++++++++++++++++++++++++++++ - 1 file changed, 31 insertions(+) + Lib/test/test_fips.py | 24 +++++++++++++++++++++ + Lib/test/test_hashlib.py | 46 ++++++++++++++++++++++++++++++---------- + 2 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 Lib/test/test_fips.py diff --git a/Lib/test/test_fips.py b/Lib/test/test_fips.py new file mode 100644 -index 0000000..fe4ea72 +index 0000000..1f99dd7 --- /dev/null +++ b/Lib/test/test_fips.py -@@ -0,0 +1,31 @@ +@@ -0,0 +1,24 @@ +import unittest -+import hmac, _hmacopenssl +import hashlib, _hashlib + + + +class HashlibFipsTests(unittest.TestCase): + -+ @unittest.skipUnless(hashlib.get_fips_mode(), "Test only when FIPS is enabled") ++ @unittest.skipUnless(_hashlib.get_fips_mode(), "Test only when FIPS is enabled") + def test_fips_imports(self): + """blake2s and blake2b should fail to import in FIPS mode + """ @@ -1751,147 +1650,16 @@ index 0000000..fe4ea72 + with self.assertRaises(ValueError, msg='blake2b not available in FIPS'): + m = hashlib.blake2b() + -+ @unittest.skipIf(hashlib.get_fips_mode(), "blake2 hashes are not available under FIPS") ++ @unittest.skipIf(_hashlib.get_fips_mode(), "blake2 hashes are not available under FIPS") + def test_blake2_hashes(self): + self.assertEqual(hashlib.blake2b(b'abc').hexdigest(), _hashlib.openssl_blake2b(b'abc').hexdigest()) + self.assertEqual(hashlib.blake2s(b'abc').hexdigest(), _hashlib.openssl_blake2s(b'abc').hexdigest()) + -+ def test_hmac_digests(self): -+ self.assertEqual(_hmacopenssl.HMAC(b'My hovercraft is full of eels', digestmod='sha384').hexdigest(), -+ hmac.new(b'My hovercraft is full of eels', digestmod='sha384').hexdigest()) -+ -+ -+ + +if __name__ == "__main__": + unittest.main() --- -2.31.1 - - -From 31c0ebb0612e0f058a45058b5c92d1cfa672eca2 Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Mon, 5 Aug 2019 18:23:57 +0200 -Subject: [PATCH 08/13] Make hashlib tests pass in FIPS mode - ---- - Lib/test/test_hashlib.py | 27 ++++++++++++++++++++++++--- - 1 file changed, 24 insertions(+), 3 deletions(-) - -diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index a838bce..6f60ad4 100644 ---- a/Lib/test/test_hashlib.py -+++ b/Lib/test/test_hashlib.py -@@ -44,6 +44,12 @@ if builtin_hashes == default_builtin_hashes: - else: - builtin_hashlib = None - -+ -+if hashlib.get_fips_mode(): -+ FIPS_DISABLED = {'md5', 'MD5', 'blake2b', 'blake2s'} -+else: -+ FIPS_DISABLED = set() -+ - try: - from _hashlib import HASH, HASHXOF, openssl_md_meth_names - except ImportError: -@@ -106,6 +112,7 @@ class HashLibTestCase(unittest.TestCase): - # Issue #14693: fallback modules are always compiled under POSIX - _warn_on_extension_import = os.name == 'posix' or COMPILED_WITH_PYDEBUG - -+ - def _conditional_import_module(self, module_name): - """Import a module and return a reference to it or None on failure.""" - try: -@@ -113,6 +120,9 @@ class HashLibTestCase(unittest.TestCase): - 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) -+ except ImportError as error: -+ if not hashlib.get_fips_mode(): -+ raise - return None - - def __init__(self, *args, **kwargs): -@@ -211,7 +221,7 @@ class HashLibTestCase(unittest.TestCase): - c.hexdigest() - - def test_algorithms_guaranteed(self): -- self.assertEqual(hashlib.algorithms_guaranteed, -+ self.assertEqual(hashlib.algorithms_guaranteed - FIPS_DISABLED, - set(_algo for _algo in self.supported_hash_names - if _algo.islower())) - -@@ -250,6 +260,12 @@ class HashLibTestCase(unittest.TestCase): - def test_new_upper_to_lower(self): - self.assertEqual(hashlib.new("SHA256").name, "sha256") - -+ @unittest.skipUnless(hashlib.get_fips_mode(), "Builtin constructor only unavailable in FIPS mode") -+ def test_get_builtin_constructor_fips(self): -+ with self.assertRaises(AttributeError): -+ hashlib.__get_builtin_constructor -+ -+ @unittest.skipIf(hashlib.get_fips_mode(), "No builtin constructors in FIPS mode") - def test_get_builtin_constructor(self): - get_builtin_constructor = getattr(hashlib, - '__get_builtin_constructor') -@@ -380,7 +396,8 @@ class HashLibTestCase(unittest.TestCase): - self.assertRaises(TypeError, hash_object_constructor, 'spam') - - def test_no_unicode(self): -- self.check_no_unicode('md5') -+ if not hashlib.get_fips_mode(): -+ self.check_no_unicode('md5') - self.check_no_unicode('sha1') - self.check_no_unicode('sha224') - self.check_no_unicode('sha256') -@@ -421,7 +438,8 @@ class HashLibTestCase(unittest.TestCase): - self.assertIn(name.split("_")[0], repr(m)) - - def test_blocksize_name(self): -- self.check_blocksize_name('md5', 64, 16) -+ if not hashlib.get_fips_mode(): -+ self.check_blocksize_name('md5', 64, 16) - self.check_blocksize_name('sha1', 64, 20) - self.check_blocksize_name('sha224', 64, 28) - self.check_blocksize_name('sha256', 64, 32) -@@ -463,18 +481,21 @@ class HashLibTestCase(unittest.TestCase): - self.check_blocksize_name('blake2b', 128, 64) - self.check_blocksize_name('blake2s', 64, 32) - -+ @unittest.skipIf(hashlib.get_fips_mode(), "md5 unacceptable in FIPS mode") - def test_case_md5_0(self): - self.check( - 'md5', b'', 'd41d8cd98f00b204e9800998ecf8427e', - usedforsecurity=False - ) - -+ @unittest.skipIf(hashlib.get_fips_mode(), "md5 unacceptable in FIPS mode") - def test_case_md5_1(self): - self.check( - 'md5', b'abc', '900150983cd24fb0d6963f7d28e17f72', - usedforsecurity=False - ) - -+ @unittest.skipIf(hashlib.get_fips_mode(), "md5 unacceptable in FIPS mode") - def test_case_md5_2(self): - self.check( - 'md5', --- -2.31.1 - - -From 42e85e3a54a806e9460ae67598f473b4a839d223 Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Mon, 26 Aug 2019 19:09:39 +0200 -Subject: [PATCH 09/13] Test the usedforsecurity flag - ---- - Lib/test/test_hashlib.py | 66 +++++++++++++++++++++++++--------------- - 1 file changed, 42 insertions(+), 24 deletions(-) - diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index 6f60ad4..f306ba3 100644 +index ec6c883..0fd036f 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -20,6 +20,7 @@ import warnings @@ -1902,46 +1670,32 @@ index 6f60ad4..f306ba3 100644 # Were we compiled --with-pydebug or with #define Py_DEBUG? COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount') -@@ -46,8 +47,10 @@ else: - - - if hashlib.get_fips_mode(): -- FIPS_DISABLED = {'md5', 'MD5', 'blake2b', 'blake2s'} -+ FIPS_UNAVAILABLE = {'blake2b', 'blake2s'} -+ FIPS_DISABLED = {'md5', 'MD5', *FIPS_UNAVAILABLE} - else: -+ FIPS_UNAVAILABLE = set() - FIPS_DISABLED = set() +@@ -55,6 +56,11 @@ except ImportError: + def get_fips_mode(): + return 0 ++if get_fips_mode(): ++ FIPS_DISABLED = {'md5'} ++else: ++ FIPS_DISABLED = set() ++ try: -@@ -98,6 +101,14 @@ def read_vectors(hash_name): + import _blake2 + except ImportError: +@@ -98,6 +104,11 @@ def read_vectors(hash_name): parts[0] = bytes.fromhex(parts[0]) yield parts -+def _is_openssl_constructor(constructor): -+ if getattr(constructor, '__name__', '').startswith('openssl_'): -+ return True ++def _is_blake2_constructor(constructor): + if isinstance(constructor, partial): -+ if constructor.func.__name__.startswith('openssl_'): -+ return True -+ return False ++ constructor = constructor.func ++ return getattr(constructor, '__name__', '').startswith('openssl_blake2') + class HashLibTestCase(unittest.TestCase): supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1', -@@ -128,8 +139,8 @@ class HashLibTestCase(unittest.TestCase): - def __init__(self, *args, **kwargs): - algorithms = set() - for algorithm in self.supported_hash_names: -- algorithms.add(algorithm.lower()) -- -+ if algorithm not in FIPS_UNAVAILABLE: -+ algorithms.add(algorithm.lower()) - _blake2 = self._conditional_import_module('_blake2') - if _blake2: - algorithms.update({'blake2b', 'blake2s'}) -@@ -138,15 +149,21 @@ class HashLibTestCase(unittest.TestCase): - for algorithm in algorithms: +@@ -142,15 +153,21 @@ class HashLibTestCase(unittest.TestCase): + continue self.constructors_to_test[algorithm] = set() + def _add_constructor(algorithm, constructor): @@ -1964,7 +1718,7 @@ index 6f60ad4..f306ba3 100644 _hashlib = self._conditional_import_module('_hashlib') self._hashlib = _hashlib -@@ -158,13 +175,7 @@ class HashLibTestCase(unittest.TestCase): +@@ -162,13 +179,7 @@ class HashLibTestCase(unittest.TestCase): for algorithm, constructors in self.constructors_to_test.items(): constructor = getattr(_hashlib, 'openssl_'+algorithm, None) if constructor: @@ -1978,86 +1732,34 @@ index 6f60ad4..f306ba3 100644 + _add_constructor(algorithm, constructor) def add_builtin_constructor(name): - constructor = getattr(hashlib, "__get_builtin_constructor")(name) -@@ -221,7 +232,7 @@ class HashLibTestCase(unittest.TestCase): - c.hexdigest() - - def test_algorithms_guaranteed(self): -- self.assertEqual(hashlib.algorithms_guaranteed - FIPS_DISABLED, -+ self.assertEqual(hashlib.algorithms_guaranteed, - set(_algo for _algo in self.supported_hash_names - if _algo.islower())) - -@@ -326,10 +337,9 @@ class HashLibTestCase(unittest.TestCase): + try: +@@ -346,6 +357,8 @@ class HashLibTestCase(unittest.TestCase): 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, usedforsecurity=False).name -- ) -+ if h.name not in FIPS_DISABLED: ++ if not h.name.startswith('blake2') and h.name not in FIPS_DISABLED: + 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 -@@ -371,9 +381,11 @@ class HashLibTestCase(unittest.TestCase): - self.assertGreaterEqual(len(constructors), 2) + self.assertEqual( + h.name, + hashlib.new(h.name, usedforsecurity=False).name +@@ -392,8 +405,10 @@ class HashLibTestCase(unittest.TestCase): for hash_object_constructor in constructors: - if ( -- kwargs -- and hash_object_constructor.__name__.startswith('openssl_') -+ (kwargs.keys() - {'usedforsecurity'}) -+ and _is_openssl_constructor(hash_object_constructor) - ): -+ # Don't check openssl constructors with -+ # any extra keys (except usedforsecurity) - return - m = hash_object_constructor(data, **kwargs) - computed = m.hexdigest() if not shake else m.hexdigest(length) -@@ -481,21 +493,18 @@ class HashLibTestCase(unittest.TestCase): - self.check_blocksize_name('blake2b', 128, 64) - self.check_blocksize_name('blake2s', 64, 32) - -- @unittest.skipIf(hashlib.get_fips_mode(), "md5 unacceptable in FIPS mode") - def test_case_md5_0(self): - self.check( - 'md5', b'', 'd41d8cd98f00b204e9800998ecf8427e', - usedforsecurity=False - ) -- @unittest.skipIf(hashlib.get_fips_mode(), "md5 unacceptable in FIPS mode") - def test_case_md5_1(self): - self.check( - 'md5', b'abc', '900150983cd24fb0d6963f7d28e17f72', - usedforsecurity=False - ) + # OpenSSL's blake2s & blake2d don't support `key` +- _name = hash_object_constructor.__name__ +- if 'key' in kwargs and _name.startswith('openssl_blake2'): ++ if ( ++ 'key' in kwargs ++ and _is_blake2_constructor(hash_object_constructor) ++ ): + return -- @unittest.skipIf(hashlib.get_fips_mode(), "md5 unacceptable in FIPS mode") - def test_case_md5_2(self): - self.check( - 'md5', -@@ -507,12 +516,12 @@ class HashLibTestCase(unittest.TestCase): - @unittest.skipIf(sys.maxsize < _4G + 5, 'test cannot run on 32-bit systems') - @bigmemtest(size=_4G + 5, memuse=1, dry_run=False) - def test_case_md5_huge(self, size): -- self.check('md5', b'A'*size, 'c9af2dff37468ce5dfee8f2cfc0a9c6d') -+ self.check('md5', b'A'*size, 'c9af2dff37468ce5dfee8f2cfc0a9c6d', usedforsecurity=False) - - @unittest.skipIf(sys.maxsize < _4G - 1, 'test cannot run on 32-bit systems') - @bigmemtest(size=_4G - 1, memuse=1, dry_run=False) - def test_case_md5_uintmax(self, size): -- self.check('md5', b'A'*size, '28138d306ff1b8281f1a9067e1a1a2b3') -+ self.check('md5', b'A'*size, '28138d306ff1b8281f1a9067e1a1a2b3', usedforsecurity=False) - - # use the three examples from Federal Information Processing Standards - # Publication 180-1, Secure Hash Standard, 1995 April 17 -@@ -958,6 +967,15 @@ class HashLibTestCase(unittest.TestCase): + m = hash_object_constructor(data, **kwargs) +@@ -974,6 +989,15 @@ class HashLibTestCase(unittest.TestCase): ): HASHXOF() -+ @unittest.skipUnless(hashlib.get_fips_mode(), 'Needs FIPS mode.') ++ @unittest.skipUnless(get_fips_mode(), 'Needs FIPS mode.') + def test_usedforsecurity_repeat(self): + """Make sure usedforsecurity flag isn't copied to other contexts""" + for i in range(3): @@ -2070,225 +1772,68 @@ index 6f60ad4..f306ba3 100644 class KDFTests(unittest.TestCase): -- -2.31.1 +2.35.3 -From 7ea94845a8c8f5b2081237e69ff41993da1e5a5e Mon Sep 17 00:00:00 2001 +From 5ecf11d53225bbe04e35970a834bcc90cd944391 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 26 Aug 2019 19:39:48 +0200 -Subject: [PATCH 10/13] Don't re-export get_fips_mode from hashlib +Subject: [PATCH 09/10] Guard against Python HMAC in FIPS mode -Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1745685 --- - Lib/hashlib.py | 22 +++++++++------------- - Lib/hmac.py | 6 +++--- - Lib/test/test_fips.py | 4 ++-- - Lib/test/test_hashlib.py | 16 +++++++++------- - Lib/test/test_hmac.py | 9 +++++---- - Lib/test/test_urllib2_localnet.py | 1 + - 6 files changed, 29 insertions(+), 29 deletions(-) + Lib/hmac.py | 13 +++++++++---- + Lib/test/test_hmac.py | 10 ++++++++++ + 2 files changed, 19 insertions(+), 4 deletions(-) -diff --git a/Lib/hashlib.py b/Lib/hashlib.py -index 1fd80c7..6121d25 100644 ---- a/Lib/hashlib.py -+++ b/Lib/hashlib.py -@@ -53,6 +53,8 @@ More condensed: - - """ - -+import _hashlib -+ - # This tuple and __get_builtin_constructor() must be modified if a new - # always available algorithm is added. - __always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', -@@ -77,13 +79,7 @@ __block_openssl_constructor = { - 'blake2b', 'blake2s', - } - --try: -- from _hashlib import get_fips_mode --except ImportError: -- def get_fips_mode(): -- return 0 -- --if not get_fips_mode(): -+if not _hashlib.get_fips_mode(): - __builtin_constructor_cache = {} - - def __get_builtin_constructor(name): -@@ -131,7 +127,7 @@ if not get_fips_mode(): - - - def __get_openssl_constructor(name): -- if not get_fips_mode(): -+ if not _hashlib.get_fips_mode(): - if name in __block_openssl_constructor: - # Prefer our builtin blake2 implementation. - return __get_builtin_constructor(name) -@@ -149,7 +145,7 @@ def __get_openssl_constructor(name): - return __get_builtin_constructor(name) - - --if not get_fips_mode(): -+if not _hashlib.get_fips_mode(): - def __py_new(name, data=b'', **kwargs): - """new(name, data=b'', **kwargs) - Return a new hashing object using the - named algorithm; optionally initialized with data (which must be -@@ -162,7 +158,7 @@ def __hash_new(name, data=b'', **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 not get_fips_mode(): -+ if not _hashlib.get_fips_mode(): - if name in __block_openssl_constructor: - # Prefer our builtin blake2 implementation. - return __get_builtin_constructor(name)(data, **kwargs) -@@ -173,7 +169,7 @@ def __hash_new(name, data=b'', **kwargs): - # 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. -- if get_fips_mode(): -+ if _hashlib.get_fips_mode(): - raise - return __get_builtin_constructor(name)(data) - -@@ -185,7 +181,7 @@ try: - algorithms_available = algorithms_available.union( - _hashlib.openssl_md_meth_names) - except ImportError: -- if get_fips_mode: -+ if _hashlib.get_fips_mode: - raise - new = __py_new - __get_hash = __get_builtin_constructor -@@ -214,5 +210,5 @@ for __func_name in __always_supported: - # Cleanup locals() - del __always_supported, __func_name, __get_hash - del __hash_new, __get_openssl_constructor --if not get_fips_mode(): -+if not _hashlib.get_fips_mode(): - del __py_new diff --git a/Lib/hmac.py b/Lib/hmac.py -index 482e443..ff46632 100644 +index 8b4f920..20ef96c 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py -@@ -50,7 +50,7 @@ class HMAC: - msg argument. Passing it as a keyword argument is - recommended, though not required for legacy API reasons. - """ -- if _hashlib.get_fips_mode(): -+ if _hashlibopenssl.get_fips_mode(): - raise ValueError( - 'This class is not available in FIPS mode. ' - + 'Use hmac.new().' -@@ -117,7 +117,7 @@ class HMAC: - - def update(self, msg): - """Feed data from msg into this hashing object.""" -- if _hashlib.get_fips_mode(): -+ if _hashlibopenssl.get_fips_mode(): - raise ValueError('hmac.HMAC is not available in FIPS mode') - self._inner.update(msg) - -@@ -183,7 +183,7 @@ class HMAC_openssl(_hmacopenssl.HMAC): - return result - - --if _hashlib.get_fips_mode(): -+if _hashlibopenssl.get_fips_mode(): - HMAC = HMAC_openssl - +@@ -16,8 +16,9 @@ else: -diff --git a/Lib/test/test_fips.py b/Lib/test/test_fips.py -index fe4ea72..6b50f8b 100644 ---- a/Lib/test/test_fips.py -+++ b/Lib/test/test_fips.py -@@ -6,7 +6,7 @@ import hashlib, _hashlib + import hashlib as _hashlib - class HashlibFipsTests(unittest.TestCase): +-trans_5C = bytes((x ^ 0x5C) for x in range(256)) +-trans_36 = bytes((x ^ 0x36) for x in range(256)) ++if not _hashopenssl.get_fips_mode(): ++ trans_5C = bytes((x ^ 0x5C) for x in range(256)) ++ trans_36 = bytes((x ^ 0x36) for x in range(256)) -- @unittest.skipUnless(hashlib.get_fips_mode(), "Test only when FIPS is enabled") -+ @unittest.skipUnless(_hashlib.get_fips_mode(), "Test only when FIPS is enabled") - def test_fips_imports(self): - """blake2s and blake2b should fail to import in FIPS mode + # The size of the digests returned by HMAC depends on the underlying + # hashing module used. Use digest_size from the instance of HMAC instead. +@@ -48,17 +49,18 @@ class HMAC: + msg argument. Passing it as a keyword argument is + recommended, though not required for legacy API reasons. """ -@@ -15,7 +15,7 @@ class HashlibFipsTests(unittest.TestCase): - with self.assertRaises(ValueError, msg='blake2b not available in FIPS'): - m = hashlib.blake2b() - -- @unittest.skipIf(hashlib.get_fips_mode(), "blake2 hashes are not available under FIPS") -+ @unittest.skipIf(_hashlib.get_fips_mode(), "blake2 hashes are not available under FIPS") - def test_blake2_hashes(self): - self.assertEqual(hashlib.blake2b(b'abc').hexdigest(), _hashlib.openssl_blake2b(b'abc').hexdigest()) - self.assertEqual(hashlib.blake2s(b'abc').hexdigest(), _hashlib.openssl_blake2s(b'abc').hexdigest()) -diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index f306ba3..03cfb6b 100644 ---- a/Lib/test/test_hashlib.py -+++ b/Lib/test/test_hashlib.py -@@ -46,7 +46,9 @@ else: - builtin_hashlib = None - - --if hashlib.get_fips_mode(): -+from _hashlib import get_fips_mode as _get_fips_mode -+ -+if _get_fips_mode(): - FIPS_UNAVAILABLE = {'blake2b', 'blake2s'} - FIPS_DISABLED = {'md5', 'MD5', *FIPS_UNAVAILABLE} - else: -@@ -132,7 +134,7 @@ class HashLibTestCase(unittest.TestCase): - if self._warn_on_extension_import and module_name in builtin_hashes: - warnings.warn('Did a C extension fail to compile? %s' % error) - except ImportError as error: -- if not hashlib.get_fips_mode(): -+ if not _get_fips_mode(): - raise - return None - -@@ -271,12 +273,12 @@ class HashLibTestCase(unittest.TestCase): - def test_new_upper_to_lower(self): - self.assertEqual(hashlib.new("SHA256").name, "sha256") - -- @unittest.skipUnless(hashlib.get_fips_mode(), "Builtin constructor only unavailable in FIPS mode") -+ @unittest.skipUnless(_get_fips_mode(), "Builtin constructor only unavailable in FIPS mode") - def test_get_builtin_constructor_fips(self): - with self.assertRaises(AttributeError): - hashlib.__get_builtin_constructor - -- @unittest.skipIf(hashlib.get_fips_mode(), "No builtin constructors in FIPS mode") -+ @unittest.skipIf(_get_fips_mode(), "No builtin constructors in FIPS mode") - def test_get_builtin_constructor(self): - get_builtin_constructor = getattr(hashlib, - '__get_builtin_constructor') -@@ -408,7 +410,7 @@ class HashLibTestCase(unittest.TestCase): - self.assertRaises(TypeError, hash_object_constructor, 'spam') - - def test_no_unicode(self): -- if not hashlib.get_fips_mode(): -+ if not _get_fips_mode(): - self.check_no_unicode('md5') - self.check_no_unicode('sha1') - self.check_no_unicode('sha224') -@@ -450,7 +452,7 @@ class HashLibTestCase(unittest.TestCase): - self.assertIn(name.split("_")[0], repr(m)) - - def test_blocksize_name(self): -- if not hashlib.get_fips_mode(): -+ if not _get_fips_mode(): - self.check_blocksize_name('md5', 64, 16) - self.check_blocksize_name('sha1', 64, 20) - self.check_blocksize_name('sha224', 64, 28) -@@ -967,7 +969,7 @@ class HashLibTestCase(unittest.TestCase): - ): - HASHXOF() +- + if not isinstance(key, (bytes, bytearray)): + raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) -- @unittest.skipUnless(hashlib.get_fips_mode(), 'Needs FIPS mode.') -+ @unittest.skipUnless(_get_fips_mode(), 'Needs FIPS mode.') - def test_usedforsecurity_repeat(self): - """Make sure usedforsecurity flag isn't copied to other contexts""" - for i in range(3): + if not digestmod: + raise TypeError("Missing required parameter 'digestmod'.") + +- if _hashopenssl and isinstance(digestmod, (str, _functype)): ++ if _hashopenssl.get_fips_mode() or (_hashopenssl and isinstance(digestmod, (str, _functype))): + try: + self._init_hmac(key, msg, digestmod) + except _hashopenssl.UnsupportedDigestmodError: ++ if _hashopenssl.get_fips_mode(): ++ raise + self._init_old(key, msg, digestmod) + else: + self._init_old(key, msg, digestmod) +@@ -69,6 +71,9 @@ class HMAC: + self.block_size = self._hmac.block_size + + def _init_old(self, key, msg, digestmod): ++ if _hashopenssl.get_fips_mode(): ++ # In FIPS mode, use OpenSSL anyway: raise the appropriate error ++ return self._init_hmac(key, msg, digestmod) + if callable(digestmod): + digest_cons = digestmod + elif isinstance(digestmod, str): diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py -index 544ec7c..2d44849 100644 +index adf52ad..41e6a14 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -5,6 +5,7 @@ import hashlib @@ -2299,275 +1844,332 @@ index 544ec7c..2d44849 100644 from test.support import hashlib_helper -@@ -322,7 +323,7 @@ class TestVectorsTestCase(unittest.TestCase): +@@ -339,6 +340,7 @@ class TestVectorsTestCase(unittest.TestCase): def test_sha512_rfc4231(self): self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128) -- @unittest.skipIf(hashlib.get_fips_mode(), 'MockCrazyHash unacceptable in FIPS mode.') -+ @unittest.skipIf(get_fips_mode(), 'MockCrazyHash unacceptable in FIPS mode.') ++ #unittest.skipIf(get_fips_mode(), 'MockCrazyHash unacceptable in FIPS mode.') @hashlib_helper.requires_hashdigest('sha256') def test_legacy_block_size_warnings(self): class MockCrazyHash(object): -@@ -455,7 +456,7 @@ class SanityTestCase(unittest.TestCase): +@@ -351,6 +353,11 @@ class TestVectorsTestCase(unittest.TestCase): + def digest(self): + return self._x.digest() + ++ if get_fips_mode(): ++ with self.assertRaises(ValueError): ++ hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash) ++ return ++ + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + with self.assertRaises(RuntimeWarning): +@@ -444,6 +451,7 @@ class ConstructorTestCase(unittest.TestCase): + ): + C_HMAC() + ++ @unittest.skipIf(get_fips_mode(), "_sha256 unavailable in FIPS mode") + @unittest.skipUnless(sha256_module is not None, 'need _sha256') + def test_with_sha256_module(self): + h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) +@@ -472,6 +480,7 @@ class SanityTestCase(unittest.TestCase): class CopyTestCase(unittest.TestCase): -- @unittest.skipIf(hashlib.get_fips_mode(), "Internal attributes unavailable in FIPS mode") -+ @unittest.skipIf(get_fips_mode(), "Internal attributes unavailable in FIPS mode") ++ @unittest.skipIf(get_fips_mode(), "_init_old unavailable in FIPS mode") @hashlib_helper.requires_hashdigest('sha256') - def test_attributes(self): + def test_attributes_old(self): # Testing if attributes are of same type. -@@ -469,7 +470,7 @@ class CopyTestCase(unittest.TestCase): +@@ -483,6 +492,7 @@ class CopyTestCase(unittest.TestCase): + self.assertEqual(type(h1._outer), type(h2._outer), "Types of outer don't match.") - -- @unittest.skipIf(hashlib.get_fips_mode(), "Internal attributes unavailable in FIPS mode") -+ @unittest.skipIf(get_fips_mode(), "Internal attributes unavailable in FIPS mode") ++ @unittest.skipIf(get_fips_mode(), "_init_old unavailable in FIPS mode") @hashlib_helper.requires_hashdigest('sha256') - def test_realcopy(self): + def test_realcopy_old(self): # Testing if the copy method created a real copy. -@@ -485,7 +486,7 @@ class CopyTestCase(unittest.TestCase): - self.assertEqual(h1._outer, h1.outer) - self.assertEqual(h1._digest_cons, h1.digest_cons) - -- @unittest.skipIf(hashlib.get_fips_mode(), "Internal attributes unavailable in FIPS mode") -+ @unittest.skipIf(get_fips_mode(), "Internal attributes unavailable in FIPS mode") - @hashlib_helper.requires_hashdigest('sha256') - def test_properties(self): - # deprecated properties -diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py -index ed426b0..faec684 100644 ---- a/Lib/test/test_urllib2_localnet.py -+++ b/Lib/test/test_urllib2_localnet.py -@@ -7,6 +7,7 @@ import http.server - import threading - import unittest - import hashlib -+from _hashlib import get_fips_mode - - from test import support - from test.support import hashlib_helper -- -2.31.1 +2.35.3 -From 62c667aed616986e8b6df9883ccebf9326f041ee Mon Sep 17 00:00:00 2001 -From: Christian Heimes -Date: Wed, 20 Nov 2019 10:59:25 +0100 -Subject: [PATCH 11/13] Use FIPS compliant CSPRNG +From 532ce8649bf743c029aa5ddb25d74604d9798da9 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 25 Aug 2021 16:44:43 +0200 +Subject: [PATCH 10/10] Disable hash-based PYCs in FIPS mode -Kernel's getrandom() source is not yet FIPS compliant. Use OpenSSL's -DRBG in FIPS mode and disable os.getrandom() function. +If FIPS mode is on, we can't use siphash-based HMAC +(_Py_KeyedHash), so: -Signed-off-by: Christian Heimes +- Unchecked hash PYCs can be imported, but not created +- Checked hash PYCs can not be imported nor created +- The default mode is timestamp-based PYCs, even if + SOURCE_DATE_EPOCH is set. + +If FIPS mode is off, there are no changes in behavior. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1835169 --- - Lib/test/test_os.py | 6 ++++++ - Makefile.pre.in | 2 +- - Modules/posixmodule.c | 8 +++++++ - Python/bootstrap_hash.c | 48 +++++++++++++++++++++++++++++++++++++++++ - 4 files changed, 63 insertions(+), 1 deletion(-) + Lib/py_compile.py | 2 ++ + Lib/test/support/__init__.py | 14 +++++++++++++ + Lib/test/test_cmd_line_script.py | 2 ++ + Lib/test/test_compileall.py | 11 +++++++++- + Lib/test/test_imp.py | 2 ++ + .../test_importlib/source/test_file_loader.py | 6 ++++++ + Lib/test/test_py_compile.py | 11 ++++++++-- + Lib/test/test_zipimport.py | 2 ++ + Python/import.c | 20 +++++++++++++++++++ + 9 files changed, 67 insertions(+), 3 deletions(-) -diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py -index 35933e9..f67a65d 100644 ---- a/Lib/test/test_os.py -+++ b/Lib/test/test_os.py -@@ -29,6 +29,7 @@ import types +diff --git a/Lib/py_compile.py b/Lib/py_compile.py +index bba3642..02db901 100644 +--- a/Lib/py_compile.py ++++ b/Lib/py_compile.py +@@ -70,7 +70,9 @@ class PycInvalidationMode(enum.Enum): + + + def _get_default_invalidation_mode(): ++ import _hashlib + if (os.environ.get('SOURCE_DATE_EPOCH') and not ++ _hashlib.get_fips_mode() and not + os.environ.get('RPM_BUILD_ROOT')): + return PycInvalidationMode.CHECKED_HASH + else: +diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py +index 86ac8f0..dc042f7 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -3294,3 +3294,17 @@ def clear_ignored_deprecations(*tokens: object) -> None: + if warnings.filters != new_filters: + warnings.filters[:] = new_filters + warnings._filters_mutated() ++ ++ ++def fails_in_fips_mode(expected_error): ++ import _hashlib ++ if _hashlib.get_fips_mode(): ++ def _decorator(func): ++ def _wrapper(self, *args, **kwargs): ++ with self.assertRaises(expected_error): ++ func(self, *args, **kwargs) ++ return _wrapper ++ else: ++ def _decorator(func): ++ return func ++ return _decorator +diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py +index 7cb1370..61df232 100644 +--- a/Lib/test/test_cmd_line_script.py ++++ b/Lib/test/test_cmd_line_script.py +@@ -282,6 +282,7 @@ class CmdLineTest(unittest.TestCase): + self._check_script(zip_name, run_name, zip_name, zip_name, '', + zipimport.zipimporter) + ++ @support.fails_in_fips_mode(ImportError) + def test_zipfile_compiled_checked_hash(self): + with support.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') +@@ -292,6 +293,7 @@ class CmdLineTest(unittest.TestCase): + self._check_script(zip_name, run_name, zip_name, zip_name, '', + zipimport.zipimporter) + ++ @support.fails_in_fips_mode(ImportError) + def test_zipfile_compiled_unchecked_hash(self): + with support.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') +diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py +index ab647d6..7d50f07 100644 +--- a/Lib/test/test_compileall.py ++++ b/Lib/test/test_compileall.py +@@ -758,14 +758,23 @@ class CommandLineTestsBase: + out = self.assertRunOK('badfilename') + self.assertRegex(out, b"Can't list 'badfilename'") + +- def test_pyc_invalidation_mode(self): ++ @support.fails_in_fips_mode(AssertionError) ++ def test_pyc_invalidation_mode_checked(self): + script_helper.make_script(self.pkgdir, 'f1', '') + pyc = importlib.util.cache_from_source( + os.path.join(self.pkgdir, 'f1.py')) ++ + self.assertRunOK('--invalidation-mode=checked-hash', self.pkgdir) + with open(pyc, 'rb') as fp: + data = fp.read() + self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b11) ++ ++ @support.fails_in_fips_mode(AssertionError) ++ def test_pyc_invalidation_mode_unchecked(self): ++ script_helper.make_script(self.pkgdir, 'f1', '') ++ pyc = importlib.util.cache_from_source( ++ os.path.join(self.pkgdir, 'f1.py')) ++ + self.assertRunOK('--invalidation-mode=unchecked-hash', self.pkgdir) + with open(pyc, 'rb') as fp: + data = fp.read() +diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py +index fe394dc..802f0e8 100644 +--- a/Lib/test/test_imp.py ++++ b/Lib/test/test_imp.py +@@ -343,6 +343,7 @@ class ImportTests(unittest.TestCase): + import _frozen_importlib + self.assertEqual(_frozen_importlib.__spec__.origin, "frozen") + ++ @support.fails_in_fips_mode(ImportError) + def test_source_hash(self): + self.assertEqual(_imp.source_hash(42, b'hi'), b'\xc6\xe7Z\r\x03:}\xab') + self.assertEqual(_imp.source_hash(43, b'hi'), b'\x85\x9765\xf8\x9a\x8b9') +@@ -362,6 +363,7 @@ class ImportTests(unittest.TestCase): + res = script_helper.assert_python_ok(*args) + self.assertEqual(res.out.strip().decode('utf-8'), expected) + ++ @support.fails_in_fips_mode(ImportError) + def test_find_and_load_checked_pyc(self): + # issue 34056 + with support.temp_cwd(): +diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py +index ab44722..480cc81 100644 +--- a/Lib/test/test_importlib/source/test_file_loader.py ++++ b/Lib/test/test_importlib/source/test_file_loader.py +@@ -17,6 +17,7 @@ import types import unittest - import uuid import warnings -+from _hashlib import get_fips_mode - from test import support - from test.support import socket_helper - from platform import win32_is_iot -@@ -1661,6 +1662,11 @@ class GetRandomTests(unittest.TestCase): - raise unittest.SkipTest("getrandom() syscall fails with ENOSYS") - else: - raise -+ except ValueError as exc: -+ if get_fips_mode() and exc.args[0] == "getrandom is not FIPS compliant": -+ raise unittest.SkipTest("Skip in FIPS mode") -+ else: -+ raise - - def test_getrandom_type(self): - data = os.getrandom(16) -diff --git a/Makefile.pre.in b/Makefile.pre.in -index c57fc96..7b94db1 100644 ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -116,7 +116,7 @@ PY_STDMODULE_CFLAGS= $(PY_CFLAGS) $(PY_CFLAGS_NODIST) $(PY_CPPFLAGS) $(CFLAGSFOR - PY_BUILTIN_MODULE_CFLAGS= $(PY_STDMODULE_CFLAGS) -DPy_BUILD_CORE_BUILTIN - PY_CORE_CFLAGS= $(PY_STDMODULE_CFLAGS) -DPy_BUILD_CORE - # Linker flags used for building the interpreter object files --PY_CORE_LDFLAGS=$(PY_LDFLAGS) $(PY_LDFLAGS_NODIST) -+PY_CORE_LDFLAGS=$(PY_LDFLAGS) $(PY_LDFLAGS_NODIST) -lcrypto - # Strict or non-strict aliasing flags used to compile dtoa.c, see above - CFLAGS_ALIASING=@CFLAGS_ALIASING@ - -diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c -index c984e2e..d1b0e39 100644 ---- a/Modules/posixmodule.c -+++ b/Modules/posixmodule.c -@@ -502,6 +502,9 @@ extern char *ctermid_r(char *); - # define MODNAME "posix" - #endif -+/* for FIPS check in os.getrandom() */ -+#include -+ - #if defined(__sun) - /* Something to implement in autoconf, not present in autoconf 2.69 */ - # define HAVE_STRUCT_STAT_ST_FSTYPE 1 -@@ -14256,6 +14259,11 @@ os_getrandom_impl(PyObject *module, Py_ssize_t size, int flags) - return posix_error(); - } - -+ if (FIPS_mode()) { -+ PyErr_SetString(PyExc_ValueError, "getrandom is not FIPS compliant"); ++from test import support + from test.support import make_legacy_pyc, unload + + from test.test_py_compile import without_source_date_epoch +@@ -239,6 +240,7 @@ class SimpleTest(abc.LoaderTests): + loader.load_module('bad name') + + @util.writes_bytecode_files ++ @support.fails_in_fips_mode(ImportError) + def test_checked_hash_based_pyc(self): + with util.create_modules('_temp') as mapping: + source = mapping['_temp'] +@@ -270,6 +272,7 @@ class SimpleTest(abc.LoaderTests): + ) + + @util.writes_bytecode_files ++ @support.fails_in_fips_mode(ImportError) + def test_overridden_checked_hash_based_pyc(self): + with util.create_modules('_temp') as mapping, \ + unittest.mock.patch('_imp.check_hash_based_pycs', 'never'): +@@ -295,6 +298,7 @@ class SimpleTest(abc.LoaderTests): + self.assertEqual(mod.state, 'old') + + @util.writes_bytecode_files ++ @support.fails_in_fips_mode(ImportError) + def test_unchecked_hash_based_pyc(self): + with util.create_modules('_temp') as mapping: + source = mapping['_temp'] +@@ -325,6 +329,7 @@ class SimpleTest(abc.LoaderTests): + ) + + @util.writes_bytecode_files ++ @support.fails_in_fips_mode(ImportError) + def test_overridden_unchecked_hash_based_pyc(self): + with util.create_modules('_temp') as mapping, \ + unittest.mock.patch('_imp.check_hash_based_pycs', 'always'): +@@ -434,6 +439,7 @@ class BadBytecodeTest: + del_source=del_source) + test('_temp', mapping, bc_path) + ++ @support.fails_in_fips_mode(ImportError) + def _test_partial_hash(self, test, *, del_source=False): + with util.create_modules('_temp') as mapping: + bc_path = self.manipulate_bytecode( +diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py +index b2d3dcf..7e4b0c5 100644 +--- a/Lib/test/test_py_compile.py ++++ b/Lib/test/test_py_compile.py +@@ -141,13 +141,16 @@ class PyCompileTestsBase: + importlib.util.cache_from_source(bad_coding))) + + def test_source_date_epoch(self): ++ import _hashlib + py_compile.compile(self.source_path, self.pyc_path) + self.assertTrue(os.path.exists(self.pyc_path)) + self.assertFalse(os.path.exists(self.cache_path)) + with open(self.pyc_path, 'rb') as fp: + flags = importlib._bootstrap_external._classify_pyc( + fp.read(), 'test', {}) +- if os.environ.get('SOURCE_DATE_EPOCH'): ++ if _hashlib.get_fips_mode(): ++ expected_flags = 0b00 ++ elif os.environ.get('SOURCE_DATE_EPOCH'): + expected_flags = 0b11 + else: + expected_flags = 0b00 +@@ -178,7 +181,8 @@ class PyCompileTestsBase: + # Specifying optimized bytecode should lead to a path reflecting that. + self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2)) + +- def test_invalidation_mode(self): ++ @support.fails_in_fips_mode(ImportError) ++ def test_invalidation_mode_checked(self): + py_compile.compile( + self.source_path, + invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, +@@ -187,6 +191,9 @@ class PyCompileTestsBase: + flags = importlib._bootstrap_external._classify_pyc( + fp.read(), 'test', {}) + self.assertEqual(flags, 0b11) ++ ++ @support.fails_in_fips_mode(ImportError) ++ def test_invalidation_mode_unchecked(self): + py_compile.compile( + self.source_path, + invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH, +diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py +index b7347a3..09ea990 100644 +--- a/Lib/test/test_zipimport.py ++++ b/Lib/test/test_zipimport.py +@@ -186,6 +186,7 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase): + TESTMOD + pyc_ext: (NOW, test_pyc)} + self.doTest(pyc_ext, files, TESTMOD) + ++ @support.fails_in_fips_mode(ImportError) + def testUncheckedHashBasedPyc(self): + source = b"state = 'old'" + source_hash = importlib.util.source_hash(source) +@@ -200,6 +201,7 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase): + self.assertEqual(mod.state, 'old') + self.doTest(None, files, TESTMOD, call=check) + ++ @support.fails_in_fips_mode(ImportError) + @unittest.mock.patch('_imp.check_hash_based_pycs', 'always') + def test_checked_hash_based_change_pyc(self): + source = b"state = 'old'" +diff --git a/Python/import.c b/Python/import.c +index 8358d70..1b7fb85 100644 +--- a/Python/import.c ++++ b/Python/import.c +@@ -2354,6 +2354,26 @@ static PyObject * + _imp_source_hash_impl(PyObject *module, long key, Py_buffer *source) + /*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/ + { ++ PyObject *_hashlib = PyImport_ImportModule("_hashlib"); ++ if (_hashlib == NULL) { + return NULL; + } -+ - bytes = PyBytes_FromStringAndSize(NULL, size); - if (bytes == NULL) { - PyErr_NoMemory(); -diff --git a/Python/bootstrap_hash.c b/Python/bootstrap_hash.c -index a212f69..6333cd4 100644 ---- a/Python/bootstrap_hash.c -+++ b/Python/bootstrap_hash.c -@@ -429,6 +429,50 @@ dev_urandom_close(void) - } - #endif /* !MS_WINDOWS */ - -+#include -+#include <_hashopenssl.h> -+ -+#if (OPENSSL_VERSION_NUMBER < 0x10101000L) || defined(LIBRESSL_VERSION_NUMBER) -+# error "py_openssl_drbg_urandom requires OpenSSL 1.1.1 for fork safety" -+#endif -+ -+static int -+py_openssl_drbg_urandom(char *buffer, Py_ssize_t size, int raise) -+{ -+ int res; -+ static int init = 0; -+ -+ if (!init) { -+ init = 1; -+ res = OPENSSL_init_crypto(OPENSSL_INIT_ATFORK, NULL); -+ if (res == 0) { -+ if (raise) { -+ _setException(PyExc_RuntimeError); -+ } -+ return 0; -+ } -+ } -+ -+ if (size > INT_MAX) { -+ if (raise) { -+ PyErr_Format(PyExc_OverflowError, -+ "RAND_bytes() size is limited to 2GB."); -+ } -+ return -1; -+ } -+ -+ res = RAND_bytes((unsigned char*)buffer, (int)size); -+ -+ if (res == 1) { -+ return 1; -+ } else { -+ if (raise) { -+ _setException(PyExc_RuntimeError); -+ } -+ return 0; ++ PyObject *fips_mode_obj = PyObject_CallMethod(_hashlib, "get_fips_mode", NULL); ++ Py_DECREF(_hashlib); ++ if (fips_mode_obj == NULL) { ++ return NULL; + } -+} -+ - - /* Fill buffer with pseudo-random bytes generated by a linear congruent - generator (LCG): -@@ -513,6 +557,10 @@ pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise) - return 0; - } - -+ if (FIPS_mode()) { -+ return py_openssl_drbg_urandom(buffer, size, raise); ++ int fips_mode = PyObject_IsTrue(fips_mode_obj); ++ Py_DECREF(fips_mode_obj); ++ if (fips_mode < 0) { ++ return NULL; + } -+ - #ifdef MS_WINDOWS - return win32_urandom((unsigned char *)buffer, size, raise); - #else --- -2.31.1 - - -From 58e51c67ce2afa268e7a44100d4ca9025b54117c Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Tue, 7 Apr 2020 15:16:45 +0200 -Subject: [PATCH 12/13] Pass kwargs (like usedforsecurity) through __hash_new - ---- - Lib/hashlib.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/Lib/hashlib.py b/Lib/hashlib.py -index 6121d25..00794ad 100644 ---- a/Lib/hashlib.py -+++ b/Lib/hashlib.py -@@ -171,7 +171,7 @@ def __hash_new(name, data=b'', **kwargs): - # the OpenSSL library prior to 0.9.8 doesn't provide them. - if _hashlib.get_fips_mode(): - raise -- return __get_builtin_constructor(name)(data) -+ return __get_builtin_constructor(name)(data, **kwargs) - - - try: --- -2.31.1 - - -From ea7b14d45e9a41182ca2411ad6730a9987ec49f1 Mon Sep 17 00:00:00 2001 -From: Charalampos Stratakis -Date: Fri, 24 Apr 2020 19:57:16 +0200 -Subject: [PATCH 13/13] Skip the test_with_digestmod_no_default under FIPS - -Also add a new test for testing the error values of -the digestmod parameter misuse under FIPS mode. ---- - Lib/test/test_hmac.py | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py -index 2d44849..e0a5b6a 100644 ---- a/Lib/test/test_hmac.py -+++ b/Lib/test/test_hmac.py -@@ -347,6 +347,7 @@ class TestVectorsTestCase(unittest.TestCase): - hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash) - self.fail('Expected warning about small block_size') - -+ @unittest.skipIf(get_fips_mode(), "digestmod misuse raises different errors under FIPS mode") - def test_with_digestmod_no_default(self): - """The digestmod parameter is required as of Python 3.8.""" - with self.assertRaisesRegex(TypeError, r'required.*digestmod'): -@@ -358,6 +359,18 @@ class TestVectorsTestCase(unittest.TestCase): - with self.assertRaisesRegex(TypeError, r'required.*digestmod'): - hmac.HMAC(key, msg=data, digestmod='') - -+ @unittest.skipIf(not get_fips_mode(), "test is run only under FIPS mode") -+ def test_with_digestmod_no_default_under_fips(self): -+ """Test the error values of digestmod misuse under FIPS mode.""" -+ with self.assertRaises(TypeError): -+ key = b"\x0b" * 16 -+ data = b"Hi There" -+ hmac.HMAC(key, data, digestmod=None) -+ with self.assertRaises(ValueError): -+ hmac.new(key, data) -+ with self.assertRaises(ValueError): -+ hmac.HMAC(key, msg=data, digestmod='') -+ - - class ConstructorTestCase(unittest.TestCase): - ++ if (fips_mode) { ++ PyErr_SetString( ++ PyExc_ImportError, ++ "hash-based PYC validation (siphash24) not available in FIPS mode"); ++ return NULL; ++ }; + union { + uint64_t x; + char data[sizeof(uint64_t)]; -- -2.31.1 +2.35.3 diff --git a/SOURCES/00378-support-expat-2-4-5.patch b/SOURCES/00378-support-expat-2-4-5.patch new file mode 100644 index 0000000..a69e111 --- /dev/null +++ b/SOURCES/00378-support-expat-2-4-5.patch @@ -0,0 +1,47 @@ +From db083095e3bdb93e4f8170d814664c482b1e94da Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Tue, 14 Jun 2022 06:38:43 +0200 +Subject: [PATCH] Fix test suite for Expat >= 2.4.5 + +--- + Lib/test/test_minidom.py | 17 +++++------------ + 1 file changed, 5 insertions(+), 12 deletions(-) + +diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py +index 9762025..5f52ed1 100644 +--- a/Lib/test/test_minidom.py ++++ b/Lib/test/test_minidom.py +@@ -1149,14 +1149,10 @@ class MinidomTest(unittest.TestCase): + + # Verify that character decoding errors raise exceptions instead + # of crashing +- if pyexpat.version_info >= (2, 4, 5): +- self.assertRaises(ExpatError, parseString, +- b'') +- self.assertRaises(ExpatError, parseString, +- b'Comment \xe7a va ? Tr\xe8s bien ?') +- else: +- self.assertRaises(UnicodeDecodeError, parseString, +- b'Comment \xe7a va ? Tr\xe8s bien ?') ++ self.assertRaises(ExpatError, parseString, ++ b'') ++ self.assertRaises(ExpatError, parseString, ++ b'Comment \xe7a va ? Tr\xe8s bien ?') + + doc.unlink() + +@@ -1617,10 +1613,7 @@ class MinidomTest(unittest.TestCase): + self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE) + + def testExceptionOnSpacesInXMLNSValue(self): +- if pyexpat.version_info >= (2, 4, 5): +- context = self.assertRaisesRegex(ExpatError, 'syntax error') +- else: +- context = self.assertRaisesRegex(ValueError, 'Unsupported syntax') ++ context = self.assertRaisesRegex(ExpatError, 'syntax error') + + with context: + parseString('') +-- +2.35.3 + diff --git a/SOURCES/00382-cve-2015-20107.patch b/SOURCES/00382-cve-2015-20107.patch new file mode 100644 index 0000000..619f636 --- /dev/null +++ b/SOURCES/00382-cve-2015-20107.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Fri, 3 Jun 2022 11:43:35 +0200 +Subject: [PATCH] 00382: CVE-2015-20107 + +Make mailcap refuse to match unsafe filenames/types/params (GH-91993) + +Upstream: https://github.com/python/cpython/issues/68966 + +Tracker bug: https://bugzilla.redhat.com/show_bug.cgi?id=2075390 +--- + Doc/library/mailcap.rst | 12 +++++++++ + Lib/mailcap.py | 26 +++++++++++++++++-- + Lib/test/test_mailcap.py | 8 ++++-- + ...2-04-27-18-25-30.gh-issue-68966.gjS8zs.rst | 4 +++ + 4 files changed, 46 insertions(+), 4 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst + +diff --git a/Doc/library/mailcap.rst b/Doc/library/mailcap.rst +index a22b5b9c9e..7aa3380fec 100644 +--- a/Doc/library/mailcap.rst ++++ b/Doc/library/mailcap.rst +@@ -60,6 +60,18 @@ standard. However, mailcap files are supported on most Unix systems. + use) to determine whether or not the mailcap line applies. :func:`findmatch` + will automatically check such conditions and skip the entry if the check fails. + ++ .. versionchanged:: 3.11 ++ ++ To prevent security issues with shell metacharacters (symbols that have ++ special effects in a shell command line), ``findmatch`` will refuse ++ to inject ASCII characters other than alphanumerics and ``@+=:,./-_`` ++ into the returned command line. ++ ++ If a disallowed character appears in *filename*, ``findmatch`` will always ++ return ``(None, None)`` as if no entry was found. ++ If such a character appears elsewhere (a value in *plist* or in *MIMEtype*), ++ ``findmatch`` will ignore all mailcap entries which use that value. ++ A :mod:`warning ` will be raised in either case. + + .. function:: getcaps() + +diff --git a/Lib/mailcap.py b/Lib/mailcap.py +index ae416a8e9f..444c6408b5 100644 +--- a/Lib/mailcap.py ++++ b/Lib/mailcap.py +@@ -2,6 +2,7 @@ + + import os + import warnings ++import re + + __all__ = ["getcaps","findmatch"] + +@@ -13,6 +14,11 @@ def lineno_sort_key(entry): + else: + return 1, 0 + ++_find_unsafe = re.compile(r'[^\xa1-\U0010FFFF\w@+=:,./-]').search ++ ++class UnsafeMailcapInput(Warning): ++ """Warning raised when refusing unsafe input""" ++ + + # Part 1: top-level interface. + +@@ -165,15 +171,22 @@ def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]): + entry to use. + + """ ++ if _find_unsafe(filename): ++ msg = "Refusing to use mailcap with filename %r. Use a safe temporary filename." % (filename,) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None, None + entries = lookup(caps, MIMEtype, key) + # XXX This code should somehow check for the needsterminal flag. + for e in entries: + if 'test' in e: + test = subst(e['test'], filename, plist) ++ if test is None: ++ continue + if test and os.system(test) != 0: + continue + command = subst(e[key], MIMEtype, filename, plist) +- return command, e ++ if command is not None: ++ return command, e + return None, None + + def lookup(caps, MIMEtype, key=None): +@@ -206,6 +219,10 @@ def subst(field, MIMEtype, filename, plist=[]): + elif c == 's': + res = res + filename + elif c == 't': ++ if _find_unsafe(MIMEtype): ++ msg = "Refusing to substitute MIME type %r into a shell command." % (MIMEtype,) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None + res = res + MIMEtype + elif c == '{': + start = i +@@ -213,7 +230,12 @@ def subst(field, MIMEtype, filename, plist=[]): + i = i+1 + name = field[start:i] + i = i+1 +- res = res + findparam(name, plist) ++ param = findparam(name, plist) ++ if _find_unsafe(param): ++ msg = "Refusing to substitute parameter %r (%s) into a shell command" % (param, name) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None ++ res = res + param + # XXX To do: + # %n == number of parts if type is multipart/* + # %F == list of alternating type and filename for parts +diff --git a/Lib/test/test_mailcap.py b/Lib/test/test_mailcap.py +index c08423c670..920283d9a2 100644 +--- a/Lib/test/test_mailcap.py ++++ b/Lib/test/test_mailcap.py +@@ -121,7 +121,8 @@ class HelperFunctionTest(unittest.TestCase): + (["", "audio/*", "foo.txt"], ""), + (["echo foo", "audio/*", "foo.txt"], "echo foo"), + (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"), +- (["echo %t", "audio/*", "foo.txt"], "echo audio/*"), ++ (["echo %t", "audio/*", "foo.txt"], None), ++ (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"), + (["echo \\%t", "audio/*", "foo.txt"], "echo %t"), + (["echo foo", "audio/*", "foo.txt", plist], "echo foo"), + (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3") +@@ -205,7 +206,10 @@ class FindmatchTest(unittest.TestCase): + ('"An audio fragment"', audio_basic_entry)), + ([c, "audio/*"], + {"filename": fname}, +- ("/usr/local/bin/showaudio audio/*", audio_entry)), ++ (None, None)), ++ ([c, "audio/wav"], ++ {"filename": fname}, ++ ("/usr/local/bin/showaudio audio/wav", audio_entry)), + ([c, "message/external-body"], + {"plist": plist}, + ("showexternal /dev/null default john python.org /tmp foo bar", message_entry)) +diff --git a/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst +new file mode 100644 +index 0000000000..da81a1f699 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst +@@ -0,0 +1,4 @@ ++The deprecated mailcap module now refuses to inject unsafe text (filenames, ++MIME types, parameters) into shell commands. Instead of using such text, it ++will warn and act as if a match was not found (or for test commands, as if ++the test failed). diff --git a/SOURCES/Python-3.9.13.tar.xz.asc b/SOURCES/Python-3.9.13.tar.xz.asc new file mode 100644 index 0000000..bbd8653 --- /dev/null +++ b/SOURCES/Python-3.9.13.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCgAdFiEE4/8oOcBIslwITevpsmmV4xAlBWgFAmKDr+sACgkQsmmV4xAl +BWib8A/+I+Gm2Gjf1lTFasrDIQb68gus7q9MjgjWG7HRY64gGqDBq6VcNrhVg+3g +lGL0Xr6QHkFCIJVlobDAL4UgmNkO0+I2fNhUybKPGT6BOVa4IXHkuWlJX0OBRjY+ +uOw7nCEyLzEA/FbwZXb+0PKJm74s3opjUbu9/9uY7QIqWIiD77UfQ61SDsnRLaQW +oEULPWFNLbdpMhTn7M/WVUwcxbyrCzjeFJ8rDiEbux3C1AhagTW49NTxOVW722yS +3mzjuYeyfXBIfaaU9ZHW6Z7B1hbuNVF0AvOcI3nKFUjHYs5hhchM7QnZhdFG6mMN +7REmBhssGkzWBtsWVbyChHhgVIqv81qUv6tywYMWaZtKfmrgzx2UNg9rx609c5gs +1dzXWBrh2PFWLUf8U1noSOEz/Q6/fbgdHFj4AUsr+c3zr74FNABbH5VOHS6QP79X +ic0a9+zBirrSVnLlsHkEO+aXju9ITcU/DUxPIUZxgmOImL4Vx1lsjYaw00csMzA3 +YItkoMwp4Hi7+Tvr/jGaTpKpmW+r00LyQfTfQmst7STDVY9EjlC3Mk2hzqgtFx5Z +hzb4EtMQNSjwPCvSXVWFFZWsLRu70n81uWfnXRBX7tRAWZoxC44jiOGjEhTJwzs4 +sZAhimk17t3agM0Jf0fTFMPly0mVLQMjbE7OK8GIgv/q4O5R5lc= +=RYbS +-----END PGP SIGNATURE----- diff --git a/SOURCES/Python-3.9.7.tar.xz.asc b/SOURCES/Python-3.9.7.tar.xz.asc deleted file mode 100644 index f94ac1c..0000000 --- a/SOURCES/Python-3.9.7.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCgAdFiEE4/8oOcBIslwITevpsmmV4xAlBWgFAmEtNmkACgkQsmmV4xAl -BWgFxA/+PbfnJKXOLH5s4cUARd5K1WAB7BvPKb5C3dxILvn7tp6nPFrMknhjGj5h -cAe/2uaAm6IsMXLm0hJLE1fZy1ht5M6U7e7gxSNWIjX/qKQYVKBlP7w9BK1BALE/ -YGyv3jDH7tgECkELGE0bePtuTLVr7HzdTmyKeI6BHHIklOeJBmYl/4JuUi2UxZKD -76dq0a7XTuk6ce5BUvsWsOoZ80+lms5mfVq5mNkiERdxPYvGrrxkqQz89xHU08qw -ISIBF8hX62zmRzzmuTQCPRGYsd00jPYrZ5d2aKMI36wTA/O2ICVSrzeQB6ixYOfP -ex2DbYRfMMghQ942Q9wM53TYhieibVMxEJD8MPlxleDe7O5jbe+djuwgKE9mnfCI -MZrOOuXOk4pY0l6CwgIyCWCuDNQYTip9HYqP4AMjwM1Rt1UjMsvjIds1bWgL3pEc -EV3r1f2RpuAL7kKpjzrw5qFgg87z4IVKp21Ikg2T5rvKZrSmMFyz/tr8Kuw7W0LP -KYKDKrh+J0haTpZyIOxYL+WQZ3GaXqQQaIyLI1rx2Qt9jbIt7/ieguKOx44hTLuc -sUE5I+q5Nv/nI4BsJiKiK/Oa7nDJX1xNNaaX/7p/MjAP5waGWowZutQtpSTMSm6U -ymmtmVg8xrt3zrEHJl4HQWhBvf0if1dsWvbtcP+iOdyFOw2Bn/c= -=+3CX ------END PGP SIGNATURE----- diff --git a/SPECS/python39.spec b/SPECS/python39.spec index 33dcf47..e17d1a9 100644 --- a/SPECS/python39.spec +++ b/SPECS/python39.spec @@ -13,7 +13,7 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well -%global general_version %{pybasever}.7 +%global general_version %{pybasever}.13 #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} @@ -390,6 +390,41 @@ Patch329: 00329-fips.patch # a nightmare because it's basically a binary file. Patch353: 00353-architecture-names-upstream-downstream.patch +# 00378 # +# Support expat 2.4.5 +# +# Curly brackets were never allowed in namespace URIs +# according to RFC 3986, and so-called namespace-validating +# XML parsers have the right to reject them a invalid URIs. +# +# libexpat >=2.4.5 has become strcter in that regard due to +# related security issues; with ET.XML instantiating a +# namespace-aware parser under the hood, this test has no +# future in CPython. +# +# References: +# - https://datatracker.ietf.org/doc/html/rfc3968 +# - https://www.w3.org/TR/xml-names/ +# +# Also, test_minidom.py: Support Expat >=2.4.5 +# +# The patch has diverged from upstream as the python test +# suite was relying on checking the expat version, whereas +# in RHEL fixes get backported instead of rebasing packages. +# +# Upstream: https://bugs.python.org/issue46811 +Patch378: 00378-support-expat-2-4-5.patch + +# 00382 # 9e275dcdf3934b827994ecc3247d583d5bab7985 +# CVE-2015-20107 +# +# Make mailcap refuse to match unsafe filenames/types/params (GH-91993) +# +# Upstream: https://github.com/python/cpython/issues/68966 +# +# Tracker bug: https://bugzilla.redhat.com/show_bug.cgi?id=2075390 +Patch382: 00382-cve-2015-20107.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -783,6 +818,9 @@ If you want to build an RPM against the python%{pyshortver} module, you need to %prep %autosetup -S git_am -N -n Python-%{upstream_version} +# Temporary workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1954999 +%{?!apply_patch:%define apply_patch(qp:m:) {%__apply_patch %**}} + # Apply patches up to 188 %apply_patch -q %{PATCH1} %apply_patch -q %{PATCH111} @@ -797,6 +835,8 @@ rm Lib/ensurepip/_bundled/*.whl %apply_patch -q %{PATCH328} %apply_patch -q %{PATCH329} %apply_patch -q %{PATCH353} +%apply_patch -q %{PATCH378} +%apply_patch -q %{PATCH382} # Remove all exe files to ensure we are not shipping prebuilt binaries # note that those are only used to create Microsoft Windows installers @@ -1541,7 +1581,6 @@ fi %{pylibdir}/pydoc_data %{dynload_dir}/_blake2.%{SOABI_optimized}.so -%{dynload_dir}/_hmacopenssl.%{SOABI_optimized}.so %{dynload_dir}/_asyncio.%{SOABI_optimized}.so %{dynload_dir}/_bisect.%{SOABI_optimized}.so @@ -1834,7 +1873,6 @@ fi # ...with debug builds of the built-in "extension" modules: %{dynload_dir}/_blake2.%{SOABI_debug}.so -%{dynload_dir}/_hmacopenssl.%{SOABI_debug}.so %{dynload_dir}/_asyncio.%{SOABI_debug}.so %{dynload_dir}/_bisect.%{SOABI_debug}.so @@ -1965,6 +2003,12 @@ fi # ====================================================== %changelog +* Tue Jun 14 2022 Charalampos Stratakis - 3.9.13-1 +- Update to 3.9.13 +- Security fix for CVE-2015-20107 +- Fix the test suite support for Expat >= 2.4.5 +Resolves: rhbz#2075390 + * Tue Sep 07 2021 Charalampos Stratakis - 3.9.7-1 - Update to 3.9.7 Resolves: rhbz#2003102