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:
@@ -18,6 +18,7 @@ Secret utilities.
|
|||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
|
||||||
|
|
||||||
@@ -44,3 +45,23 @@ try:
|
|||||||
constant_time_compare = hmac.compare_digest
|
constant_time_compare = hmac.compare_digest
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
constant_time_compare = _constant_time_compare
|
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
|
||||||
|
@@ -61,3 +61,50 @@ class SecretUtilsTest(testscenarios.TestWithScenarios,
|
|||||||
self.assertFalse(ctc(self.converter(u'abcd1234'),
|
self.assertFalse(ctc(self.converter(u'abcd1234'),
|
||||||
self.converter(u'1234abcd')))
|
self.converter(u'1234abcd')))
|
||||||
self.assertFalse(ctc('abcd1234', '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)
|
||||||
|
16
releasenotes/notes/add-md5-wrapper-7bf81c2464a7a224.yaml
Normal file
16
releasenotes/notes/add-md5-wrapper-7bf81c2464a7a224.yaml
Normal 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.
|
Reference in New Issue
Block a user