Add function to encapsule md5 for FIPS systems

For systems in FIPS mode, invocations of MD5 will fail.  This occurs
even in cases where the MD5 is used in a non-cryptographical context
eg. for an etag in swift.

There is a proposal in Python to allow developers to mark these
non-crypto cases as valid through a new usedforsecurity keyword.
See https://bugs.python.org/issue9216.

Some downstream versions of python already implement this keyword.
To permit OpenStack to run in FIPS enabled systems with these versions
of python, we add a simple encapsulation of hashlib.md5() here.

Once the issue is resolved in upstream python, we can remove this
function.

Change-Id: I09433fea6ad6e6849677a93b269e24dec5c05b69
This commit is contained in:
Ade Lee 2020-09-04 15:55:31 -04:00
parent 7c4a94c0c3
commit 603fa500c1
3 changed files with 84 additions and 0 deletions

View File

@ -18,6 +18,7 @@ Secret utilities.
.. versionadded:: 3.5
"""
import hashlib
import hmac
@ -44,3 +45,23 @@ try:
constant_time_compare = hmac.compare_digest
except AttributeError:
constant_time_compare = _constant_time_compare
try:
_ = hashlib.md5(usedforsecurity=False) # nosec
def md5(string=b'', usedforsecurity=True):
"""Return an md5 hashlib object using usedforsecurity parameter
For python distributions that support the usedforsecurity keyword
parameter, this passes the parameter through as expected.
See https://bugs.python.org/issue9216
"""
return hashlib.md5(string, usedforsecurity=usedforsecurity) # nosec
except TypeError:
def md5(string=b'', usedforsecurity=True):
"""Return an md5 hashlib object without usedforsecurity parameter
For python distributions that do not yet support this keyword
parameter, we drop the parameter
"""
return hashlib.md5(string) # nosec

View File

@ -61,3 +61,50 @@ class SecretUtilsTest(testscenarios.TestWithScenarios,
self.assertFalse(ctc(self.converter(u'abcd1234'),
self.converter(u'1234abcd')))
self.assertFalse(ctc('abcd1234', '1234abcd'))
_test_data = "Openstack forever".encode('utf-8')
_md5_digest = hashlib.md5(_test_data).digest()
def test_md5_with_data(self):
digest = secretutils.md5(self._test_data).digest()
self.assertEqual(digest, self._md5_digest)
digest = secretutils.md5(self._test_data,
usedforsecurity=True).digest()
self.assertEqual(digest, self._md5_digest)
digest = secretutils.md5(self._test_data,
usedforsecurity=False).digest()
self.assertEqual(digest, self._md5_digest)
def test_md5_without_data(self):
md5 = secretutils.md5()
md5.update(self._test_data)
digest = md5.digest()
self.assertEqual(digest, self._md5_digest)
md5 = secretutils.md5(usedforsecurity=True)
md5.update(self._test_data)
digest = md5.digest()
self.assertEqual(digest, self._md5_digest)
md5 = secretutils.md5(usedforsecurity=False)
md5.update(self._test_data)
digest = md5.digest()
self.assertEqual(digest, self._md5_digest)
def test_string_data_raises_type_error(self):
self.assertRaises(TypeError, hashlib.md5, 'foo')
self.assertRaises(TypeError, secretutils.md5, 'foo')
self.assertRaises(
TypeError, secretutils.md5, 'foo', usedforsecurity=True)
self.assertRaises(
TypeError, secretutils.md5, 'foo', usedforsecurity=False)
def test_none_data_raises_type_error(self):
self.assertRaises(TypeError, hashlib.md5, None)
self.assertRaises(TypeError, secretutils.md5, None)
self.assertRaises(
TypeError, secretutils.md5, None, usedforsecurity=True)
self.assertRaises(
TypeError, secretutils.md5, None, usedforsecurity=False)

View File

@ -0,0 +1,16 @@
---
features:
- |
A wrapper for hashlib.md5() has been added to allow OpenStack to run on
systems where FIPS is enabled. Under FIPS, md5 is disabled and calls to
hashlib.md5() will fail. In most cases in OpenStack, though, md5 is not
used within a security context.
In https://bugs.python.org/issue9216, a proposal has been made to allow
the addition of a keyword parameter usedforsecurity, which can be used to
designate non-security context uses. In this case, md5() operations would
be permitted. This feature is expected to be delivered in python 3.9.
Downstream python already supports this option, though. This wrapper
simply allows for this option to be supported where the underlying python
version supports it.