diff options
Diffstat (limited to 'SCons/Util.py')
-rw-r--r-- | SCons/Util.py | 78 |
1 files changed, 47 insertions, 31 deletions
diff --git a/SCons/Util.py b/SCons/Util.py index 8b79a3e17..cbd99b0b3 100644 --- a/SCons/Util.py +++ b/SCons/Util.py @@ -29,7 +29,6 @@ import os import pprint import re import sys -import inspect from collections import UserDict, UserList, UserString, OrderedDict from collections.abc import MappingView from contextlib import suppress @@ -1670,12 +1669,12 @@ def AddMethod(obj, function, name=None): # Default hash function and format. SCons-internal. -_DEFAULT_HASH_FORMATS = ['md5', 'sha1', 'sha256'] +DEFAULT_HASH_FORMATS = ['md5', 'sha1', 'sha256'] ALLOWED_HASH_FORMATS = [] _HASH_FUNCTION = None _HASH_FORMAT = None -def _attempt_init_of_python_3_9_hash_object(hash_function_object): +def _attempt_init_of_python_3_9_hash_object(hash_function_object, sys_used=sys): """Python 3.9 and onwards lets us initialize the hash function object with the key "usedforsecurity"=false. This lets us continue to use algorithms that have been deprecated either by FIPS or by Python itself, as the MD5 algorithm SCons @@ -1696,7 +1695,7 @@ def _attempt_init_of_python_3_9_hash_object(hash_function_object): # however, for our purposes checking the version is greater than or equal to 3.9 is good enough, as # the API is guaranteed to have support for the 'usedforsecurity' flag in 3.9. See # https://docs.python.org/3/library/hashlib.html#:~:text=usedforsecurity for the version support notes. - if (sys.version_info.major > 3) or (sys.version_info.major == 3 and sys.version_info.minor >= 9): + if (sys_used.version_info.major > 3) or (sys_used.version_info.major == 3 and sys_used.version_info.minor >= 9): return hash_function_object(usedforsecurity=False) # note that this can throw a ValueError in FIPS-enabled versions of Linux prior to 3.9 @@ -1704,7 +1703,7 @@ def _attempt_init_of_python_3_9_hash_object(hash_function_object): # the caller to diagnose the ValueError & potentially display the error to screen. return hash_function_object() -def _set_allowed_viable_default_hashes(hashlib_used): +def _set_allowed_viable_default_hashes(hashlib_used, sys_used=sys): """Checks if SCons has ability to call the default algorithms normally supported. This util class is sometimes called prior to setting the user-selected hash algorithm, @@ -1712,7 +1711,7 @@ def _set_allowed_viable_default_hashes(hashlib_used): and throw an exception in set_hash_format. A common case is using the SConf options, which can run prior to main, and thus ignore the options.hash_format variable. - This function checks the _DEFAULT_HASH_FORMATS and sets the ALLOWED_HASH_FORMATS + This function checks the DEFAULT_HASH_FORMATS and sets the ALLOWED_HASH_FORMATS to only the ones that can be called. In Python >= 3.9 this will always default to MD5 as in Python 3.9 there is an optional attribute "usedforsecurity" set for the method. @@ -1724,7 +1723,7 @@ def _set_allowed_viable_default_hashes(hashlib_used): # otherwise it keeps appending valid formats to the string ALLOWED_HASH_FORMATS = [] - for test_algorithm in _DEFAULT_HASH_FORMATS: + for test_algorithm in DEFAULT_HASH_FORMATS: _test_hash = getattr(hashlib_used, test_algorithm, None) # we know hashlib claims to support it... check to see if we can call it. if _test_hash is not None: @@ -1733,7 +1732,7 @@ def _set_allowed_viable_default_hashes(hashlib_used): # throw if it's a bad algorithm, otherwise it will append it to the known # good formats. try: - _attempt_init_of_python_3_9_hash_object(_test_hash) + _attempt_init_of_python_3_9_hash_object(_test_hash, sys_used) ALLOWED_HASH_FORMATS.append(test_algorithm) except ValueError as e: _last_error = e @@ -1761,8 +1760,27 @@ def get_hash_format(): """ return _HASH_FORMAT +def _attempt_get_hash_function(hash_name, hashlib_used=hashlib, sys_used=sys): + """Wrapper used to try to initialize a hash function given. -def set_hash_format(hash_format, hashlib_used=hashlib): + If successful, returns the name of the hash function back to the user. + + Otherwise returns None. + """ + try: + _fetch_hash = getattr(hashlib_used, hash_name, None) + if _fetch_hash is None: + return None + _attempt_init_of_python_3_9_hash_object(_fetch_hash, sys_used) + return hash_name + except ValueError: + # if attempt_init_of_python_3_9 throws, this is typically due to FIPS being enabled + # however, if we get to this point, the viable hash function check has either been + # bypassed or otherwise failed to properly restrict the user to only the supported + # functions. As such throw the UserError as an internal assertion-like error. + return None + +def set_hash_format(hash_format, hashlib_used=hashlib, sys_used=sys): """Sets the default hash format used by SCons. If `hash_format` is ``None`` or @@ -1782,7 +1800,7 @@ def set_hash_format(hash_format, hashlib_used=hashlib): # user can select something not supported by their OS but normally supported by # SCons, example, selecting MD5 in an OS with FIPS-mode turned on. Therefore we first # check if SCons supports it, and then if their local OS supports it. - if hash_format_lower in _DEFAULT_HASH_FORMATS: + if hash_format_lower in DEFAULT_HASH_FORMATS: raise UserError('While hash format "%s" is supported by SCons, the ' 'local system indicates only the following hash ' 'formats are supported by the hashlib library: %s' % @@ -1793,7 +1811,7 @@ def set_hash_format(hash_format, hashlib_used=hashlib): # the hash format isn't supported by SCons in any case. Warn the user, and # if we detect that SCons supports more algorithms than their local system # supports, warn the user about that too. - if ALLOWED_HASH_FORMATS == _DEFAULT_HASH_FORMATS: + if ALLOWED_HASH_FORMATS == DEFAULT_HASH_FORMATS: raise UserError('Hash format "%s" is not supported by SCons. Only ' 'the following hash formats are supported: %s' % (hash_format_lower, @@ -1805,23 +1823,14 @@ def set_hash_format(hash_format, hashlib_used=hashlib): 'is reporting; SCons supports: %s. Your local system only ' 'supports: %s' % (hash_format_lower, - ', '.join(_DEFAULT_HASH_FORMATS), + ', '.join(DEFAULT_HASH_FORMATS), ', '.join(ALLOWED_HASH_FORMATS)) ) # this is not expected to fail. If this fails it means the set_allowed_viable_default_hashes # function did not throw, or when it threw, the exception was caught and ignored, or # the global ALLOWED_HASH_FORMATS was changed by an external user. - try: - _HASH_FUNCTION = None - _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, hash_format_lower, None)) - _HASH_FUNCTION = hash_format_lower - except ValueError: - # if attempt_init_of_python_3_9 throws, this is typically due to FIPS being enabled - # however, if we get to this point, the viable hash function check has either been - # bypassed or otherwise failed to properly restrict the user to only the supported - # functions. As such throw the UserError as an internal assertion-like error. - pass + _HASH_FUNCTION = _attempt_get_hash_function(hash_format_lower, hashlib_used, sys_used) if _HASH_FUNCTION is None: from SCons.Errors import UserError # pylint: disable=import-outside-toplevel @@ -1838,12 +1847,7 @@ def set_hash_format(hash_format, hashlib_used=hashlib): # in FIPS-compliant systems this usually defaults to SHA1, unless that too has been # disabled. for choice in ALLOWED_HASH_FORMATS: - try: - _HASH_FUNCTION = None - _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, choice, None)) - _HASH_FUNCTION = choice - except ValueError: - continue + _HASH_FUNCTION = _attempt_get_hash_function(choice, hashlib_used, sys_used) if _HASH_FUNCTION is not None: break @@ -1864,7 +1868,19 @@ def set_hash_format(hash_format, hashlib_used=hashlib): set_hash_format(None) -def _get_hash_object(hash_format, hashlib_used=hashlib): +def get_current_hash_algorithm_used(): + """Returns the current hash algorithm name used. + + Where the python version >= 3.9, this is expected to return md5. + If python's version is <= 3.8, this returns md5 on non-FIPS-mode platforms, and + sha1 or sha256 on FIPS-mode Linux platforms. + + This function is primarily useful for testing, where one expects a value to be + one of N distinct hashes, and therefore the test needs to know which hash to select. + """ + return _HASH_FUNCTION + +def _get_hash_object(hash_format, hashlib_used=hashlib, sys_used=sys): """Allocates a hash object using the requested hash format. Args: @@ -1879,7 +1895,7 @@ def _get_hash_object(hash_format, hashlib_used=hashlib): raise UserError('There is no default hash function. Did you call ' 'a hashing function before SCons was initialized?') - return _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, _HASH_FUNCTION, None)) + return _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, _HASH_FUNCTION, None), sys_used) if not hasattr(hashlib, hash_format): from SCons.Errors import UserError # pylint: disable=import-outside-toplevel @@ -1888,7 +1904,7 @@ def _get_hash_object(hash_format, hashlib_used=hashlib): 'Hash format "%s" is not available in your Python interpreter.' % hash_format) - return _attempt_init_of_python_3_9_hash_object(getattr(hashlib, hash_format)) + return _attempt_init_of_python_3_9_hash_object(getattr(hashlib, hash_format), sys_used) def hash_signature(s, hash_format=None): |