diff options
| author | Russell Bryant <rbryant@redhat.com> | 2012-03-06 13:18:58 -0500 |
|---|---|---|
| committer | Russell Bryant <rbryant@redhat.com> | 2012-03-06 16:59:46 -0500 |
| commit | 989d62fe8f606cb4fecaaaf1395e1cd9c3d81d67 (patch) | |
| tree | 0c487bb71db3244466ec3079ca5af3783a9560bd | |
| parent | 6621c79b06fc2848072e59d22d1224ae3a0c593a (diff) | |
Improve auth_str_equal().
This patch is to improve auth_str_equal() a bit. The whole point of
this function is to do a string comparison in constant time to help
protect against timing attacks. The original implementation had a bit
of a silly property in that it would exit early if the strings were not
of the same length. This would theoretically still allow someone to
discover the proper length of a password.
This patch moves the length verification to the end. It also makes it
so the main loop time to run is a function of the provided password
length instead of the length of the shorter of the two strings.
Change-Id: I6dbe076818b7e3e8a313544ebd5c5734b5a025e5
| -rw-r--r-- | keystone/common/utils.py | 22 | ||||
| -rw-r--r-- | keystone/contrib/ec2/core.py | 4 | ||||
| -rw-r--r-- | tests/test_utils.py | 1 |
3 files changed, 16 insertions, 11 deletions
diff --git a/keystone/common/utils.py b/keystone/common/utils.py index 254070c1..b7d4b9c0 100644 --- a/keystone/common/utils.py +++ b/keystone/common/utils.py @@ -243,21 +243,25 @@ def unixtime(dt_obj): return time.mktime(dt_obj.utctimetuple()) -def auth_str_equal(s1, s2): +def auth_str_equal(provided, known): """Constant-time string comparison. - :params s1: the first string - :params s2: the second string + :params provided: the first string + :params known: the second string :return: True if the strings are equal. This function takes two strings and compares them. It is intended to be used when doing a comparison for authentication purposes to help guard - against timing attacks. + against timing attacks. When using the function for this purpose, always + provide the user-provided password as the first argument. The time this + function will take is always a factor of the length of this string. """ - if len(s1) != len(s2): - return False result = 0 - for (a, b) in zip(s1, s2): - result |= ord(a) ^ ord(b) - return result == 0 + p_len = len(provided) + k_len = len(known) + for i in xrange(p_len): + a = ord(provided[i]) if i < p_len else 0 + b = ord(known[i]) if i < k_len else 0 + result |= a ^ b + return (p_len == k_len) & (result == 0) diff --git a/keystone/contrib/ec2/core.py b/keystone/contrib/ec2/core.py index 24e1aab4..60d1329f 100644 --- a/keystone/contrib/ec2/core.py +++ b/keystone/contrib/ec2/core.py @@ -105,7 +105,7 @@ class Ec2Controller(wsgi.Application): def check_signature(self, creds_ref, credentials): signer = utils.Ec2Signer(creds_ref['secret']) signature = signer.generate(credentials) - if utils.auth_str_equal(signature, credentials['signature']): + if utils.auth_str_equal(credentials['signature'], signature): return # NOTE(vish): Some libraries don't use the port when signing # requests, so try again without port. @@ -113,7 +113,7 @@ class Ec2Controller(wsgi.Application): hostname, _port = credentials['host'].split(':') credentials['host'] = hostname signature = signer.generate(credentials) - if not utils.auth_str_equal(signature, credentials.signature): + if not utils.auth_str_equal(credentials.signature, signature): # TODO(termie): proper exception msg = 'Invalid signature' raise webob.exc.HTTPUnauthorized(explanation=msg) diff --git a/tests/test_utils.py b/tests/test_utils.py index 1f4123b8..b551b815 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -65,4 +65,5 @@ class UtilsTestCase(test.TestCase): def test_auth_str_equal(self): self.assertTrue(utils.auth_str_equal('abc123', 'abc123')) self.assertFalse(utils.auth_str_equal('a', 'aaaaa')) + self.assertFalse(utils.auth_str_equal('aaaaa', 'a')) self.assertFalse(utils.auth_str_equal('ABC123', 'abc123')) |
