Use hmac.compare_digest to compare signature
hmac.compare_digest must be used to compare sample signature to avoid timing-attack. We also provide a best-effort version of hmac.compare_digest for python version that doesn't have the C version. Closes bug #1332390 Change-Id: Ia313c98b7cccf0e8aed4fb933292bd7e6141b801
This commit is contained in:
parent
0b77c8a214
commit
8b4e4987a5
@ -60,15 +60,52 @@ def compute_signature(message, secret):
|
|||||||
return digest_maker.hexdigest()
|
return digest_maker.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def besteffort_compare_digest(first, second):
|
||||||
|
"""Returns True if both string inputs are equal, otherwise False.
|
||||||
|
|
||||||
|
This function should take a constant amount of time regardless of
|
||||||
|
how many characters in the strings match.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# NOTE(sileht): compare_digest method protected for timing-attacks
|
||||||
|
# exists since python >= 2.7.7 and python >= 3.3
|
||||||
|
# this a bit less-secure python fallback version
|
||||||
|
# taken from https://github.com/openstack/python-keystoneclient/blob/
|
||||||
|
# master/keystoneclient/middleware/memcache_crypt.py#L88
|
||||||
|
if len(first) != len(second):
|
||||||
|
return False
|
||||||
|
result = 0
|
||||||
|
if six.PY3 and isinstance(first, bytes) and isinstance(second, bytes):
|
||||||
|
for x, y in zip(first, second):
|
||||||
|
result |= x ^ y
|
||||||
|
else:
|
||||||
|
for x, y in zip(first, second):
|
||||||
|
result |= ord(x) ^ ord(y)
|
||||||
|
return result == 0
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(hmac, 'compare_digest'):
|
||||||
|
compare_digest = hmac.compare_digest
|
||||||
|
else:
|
||||||
|
compare_digest = besteffort_compare_digest
|
||||||
|
|
||||||
|
|
||||||
def verify_signature(message, secret):
|
def verify_signature(message, secret):
|
||||||
"""Check the signature in the message.
|
"""Check the signature in the message.
|
||||||
|
|
||||||
Message is verified against the value computed from the rest of the
|
Message is verified against the value computed from the rest of the
|
||||||
contents.
|
contents.
|
||||||
"""
|
"""
|
||||||
old_sig = message.get('message_signature')
|
old_sig = message.get('message_signature', '')
|
||||||
new_sig = compute_signature(message, secret)
|
new_sig = compute_signature(message, secret)
|
||||||
return new_sig == old_sig
|
|
||||||
|
if isinstance(old_sig, six.text_type):
|
||||||
|
try:
|
||||||
|
old_sig = old_sig.encode('ascii')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return compare_digest(new_sig, old_sig)
|
||||||
|
|
||||||
|
|
||||||
def meter_message_from_counter(sample, secret):
|
def meter_message_from_counter(sample, secret):
|
||||||
|
@ -74,6 +74,16 @@ class TestSignature(test.BaseTestCase):
|
|||||||
'message_signature': 'Not the same'}
|
'message_signature': 'Not the same'}
|
||||||
self.assertFalse(utils.verify_signature(data, 'not-so-secret'))
|
self.assertFalse(utils.verify_signature(data, 'not-so-secret'))
|
||||||
|
|
||||||
|
def test_verify_signature_invalid_encoding(self):
|
||||||
|
data = {'a': 'A', 'b': 'B',
|
||||||
|
'message_signature': ''}
|
||||||
|
self.assertFalse(utils.verify_signature(data, 'not-so-secret'))
|
||||||
|
|
||||||
|
def test_verify_signature_unicode(self):
|
||||||
|
data = {'a': 'A', 'b': 'B',
|
||||||
|
'message_signature': u''}
|
||||||
|
self.assertFalse(utils.verify_signature(data, 'not-so-secret'))
|
||||||
|
|
||||||
def test_verify_signature_nested(self):
|
def test_verify_signature_nested(self):
|
||||||
data = {'a': 'A',
|
data = {'a': 'A',
|
||||||
'b': 'B',
|
'b': 'B',
|
||||||
@ -100,3 +110,16 @@ class TestSignature(test.BaseTestCase):
|
|||||||
'not-so-secret')
|
'not-so-secret')
|
||||||
jsondata = jsonutils.loads(jsonutils.dumps(data))
|
jsondata = jsonutils.loads(jsonutils.dumps(data))
|
||||||
self.assertTrue(utils.verify_signature(jsondata, 'not-so-secret'))
|
self.assertTrue(utils.verify_signature(jsondata, 'not-so-secret'))
|
||||||
|
|
||||||
|
def test_besteffort_compare_digest(self):
|
||||||
|
hash1 = "f5ac3fe42b80b80f979825d177191bc5"
|
||||||
|
hash2 = "f5ac3fe42b80b80f979825d177191bc5"
|
||||||
|
hash3 = "1dece7821bf3fd70fe1309eaa37d52a2"
|
||||||
|
hash4 = b"f5ac3fe42b80b80f979825d177191bc5"
|
||||||
|
hash5 = b"f5ac3fe42b80b80f979825d177191bc5"
|
||||||
|
hash6 = b"1dece7821bf3fd70fe1309eaa37d52a2"
|
||||||
|
|
||||||
|
self.assertTrue(utils.besteffort_compare_digest(hash1, hash2))
|
||||||
|
self.assertFalse(utils.besteffort_compare_digest(hash1, hash3))
|
||||||
|
self.assertTrue(utils.besteffort_compare_digest(hash4, hash5))
|
||||||
|
self.assertFalse(utils.besteffort_compare_digest(hash4, hash6))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user