Replace md5 with oslo version

md5 is not an approved algorithm in FIPS mode, and trying to
instantiate a hashlib.md5() will fail when the system is running in
FIPS mode.

md5 is allowed when in a non-security context.  There is a plan to
add a keyword parameter (usedforsecurity) to hashlib.md5() to annotate
whether or not the instance is being used in a security context.

In the case where it is not, the instantiation of md5 will be allowed.
See https://bugs.python.org/issue9216 for more details.

Some downstream python versions already support this parameter.  To
support these versions, a new encapsulation of md5() has been added to
oslo_utils.  See https://review.opendev.org/#/c/750031/

This patch is to replace the instances of hashlib.md5() with this new
encapsulation, adding an annotation indicating whether the usage is
a security context or not.

Reviewers need to pay particular attention as to whether the keyword
parameter (usedforsecurity) is set correctly.

Change-Id: Idbef0f0896753765372c8dfac8ab15e6be49922f
Depends-On: https://review.opendev.org/#/c/760160
This commit is contained in:
Ade Lee 2020-10-06 14:35:00 -04:00
parent c3d8c2ed96
commit 54448e9d8b
5 changed files with 32 additions and 11 deletions

View File

@ -8,5 +8,5 @@ msgpack>=0.4.0 # Apache-2.0
fasteners>=0.7 # Apache-2.0 fasteners>=0.7 # Apache-2.0
tenacity>=3.2.1 # Apache-2.0 tenacity>=3.2.1 # Apache-2.0
futurist>=1.2.0 # Apache-2.0 futurist>=1.2.0 # Apache-2.0
oslo.utils>=3.15.0 # Apache-2.0 oslo.utils>=4.7.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0

View File

@ -14,13 +14,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import hashlib
import struct import struct
import time import time
import msgpack import msgpack
import sysv_ipc import sysv_ipc
from oslo_utils.secretutils import md5
import tooz import tooz
from tooz import coordination from tooz import coordination
from tooz import locking from tooz import locking
@ -35,7 +36,7 @@ else:
def ftok(name, project): def ftok(name, project):
# Similar to ftok & http://semanchuk.com/philip/sysv_ipc/#ftok_weakness # Similar to ftok & http://semanchuk.com/philip/sysv_ipc/#ftok_weakness
# but hopefully without as many weaknesses... # but hopefully without as many weaknesses...
h = hashlib.md5() h = md5(usedforsecurity=False)
if not isinstance(project, bytes): if not isinstance(project, bytes):
project = project.encode('ascii') project = project.encode('ascii')
h.update(project) h.update(project)

View File

@ -15,10 +15,10 @@
# under the License. # under the License.
import contextlib import contextlib
import hashlib
import logging import logging
from oslo_utils import encodeutils from oslo_utils import encodeutils
from oslo_utils.secretutils import md5
import psycopg2 import psycopg2
import tooz import tooz
@ -98,7 +98,7 @@ class PostgresLock(locking.Lock):
self._conn = None self._conn = None
self._parsed_url = parsed_url self._parsed_url = parsed_url
self._options = options self._options = options
h = hashlib.md5() h = md5(usedforsecurity=False)
h.update(name) h.update(name)
self.key = h.digest()[0:2] self.key = h.digest()[0:2]

View File

@ -16,6 +16,8 @@
import bisect import bisect
import hashlib import hashlib
from oslo_utils.secretutils import md5
import tooz import tooz
from tooz import utils from tooz import utils
@ -80,7 +82,10 @@ class HashRing(object):
""" """
for node in nodes: for node in nodes:
key = utils.to_binary(node, 'utf-8') key = utils.to_binary(node, 'utf-8')
key_hash = hashlib.new(self._hash_function, key) if self._hash_function == 'md5':
key_hash = md5(key, usedforsecurity=False)
else:
key_hash = hashlib.new(self._hash_function, key)
for r in range(self._partition_number * weight): for r in range(self._partition_number * weight):
key_hash.update(key) key_hash.update(key)
self._ring[self._hash2int(key_hash)] = node self._ring[self._hash2int(key_hash)] = node
@ -102,7 +107,10 @@ class HashRing(object):
raise UnknownNode(node) raise UnknownNode(node)
key = utils.to_binary(node, 'utf-8') key = utils.to_binary(node, 'utf-8')
key_hash = hashlib.new(self._hash_function, key) if self._hash_function == 'md5':
key_hash = md5(key, usedforsecurity=False)
else:
key_hash = hashlib.new(self._hash_function, key)
for r in range(self._partition_number * weight): for r in range(self._partition_number * weight):
key_hash.update(key) key_hash.update(key)
del self._ring[self._hash2int(key_hash)] del self._ring[self._hash2int(key_hash)]
@ -114,7 +122,11 @@ class HashRing(object):
return int(key.hexdigest(), 16) return int(key.hexdigest(), 16)
def _get_partition(self, data): def _get_partition(self, data):
hashed_key = self._hash2int(hashlib.new(self._hash_function, data)) if self._hash_function == 'md5':
hashed_key = self._hash2int(md5(data, usedforsecurity=False))
else:
hashed_key = self._hash2int(
hashlib.new(self._hash_function, data))
position = bisect.bisect(self._partitions, hashed_key) position = bisect.bisect(self._partitions, hashed_key)
return position if position < len(self._partitions) else 0 return position if position < len(self._partitions) else 0

View File

@ -31,7 +31,13 @@ class HashRingTestCase(testcase.TestCase):
# fake -> foo, bar, baz # fake -> foo, bar, baz
# fake-again -> bar, baz, foo # fake-again -> bar, baz, foo
@mock.patch.object(hashlib, 'new', autospec=True) try:
_ = hashlib.md5(usedforsecurity=False)
_fips_enabled = True
except TypeError:
_fips_enabled = False
@mock.patch.object(hashlib, 'md5', autospec=True)
def test_hash2int_returns_int(self, mock_md5): def test_hash2int_returns_int(self, mock_md5):
r1 = 32 * 'a' r1 = 32 * 'a'
r2 = 32 * 'b' r2 = 32 * 'b'
@ -44,8 +50,10 @@ class HashRingTestCase(testcase.TestCase):
self.assertIn(int(r1, 16), ring._ring) self.assertIn(int(r1, 16), ring._ring)
self.assertIn(int(r2, 16), ring._ring) self.assertIn(int(r2, 16), ring._ring)
if self._fips_enabled:
mock_md5.assert_called_with('md5', mock.ANY) mock_md5.assert_called_with(mock.ANY, usedforsecurity=False)
else:
mock_md5.assert_called_with(mock.ANY)
def test_create_ring(self): def test_create_ring(self):
nodes = {'foo', 'bar'} nodes = {'foo', 'bar'}