diff --git a/doc/source/api/secretutils.rst b/doc/source/api/secretutils.rst new file mode 100644 index 0000000..fb88a0c --- /dev/null +++ b/doc/source/api/secretutils.rst @@ -0,0 +1,6 @@ +============= + secretutils +============= + +.. automodule:: oslo_utils.secretutils + :members: diff --git a/doc/source/index.rst b/doc/source/index.rst index eb4e8ee..a4b0ba8 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -26,6 +26,7 @@ API Documentation api/importutils api/netutils api/reflection + api/secretutils api/strutils api/timeutils api/units diff --git a/oslo_utils/secretutils.py b/oslo_utils/secretutils.py new file mode 100644 index 0000000..fd5c317 --- /dev/null +++ b/oslo_utils/secretutils.py @@ -0,0 +1,35 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import hmac + + +try: + constant_time_compare = hmac.compare_digest +except AttributeError: + def constant_time_compare(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. This function uses an + approach designed to prevent timing analysis by avoiding + content-based short circuiting behaviour, making it appropriate + for cryptography. + """ + if len(first) != len(second): + return False + result = 0 + for x, y in zip(first, second): + result |= ord(x) ^ ord(y) + return result == 0 diff --git a/oslo_utils/tests/test_secretutils.py b/oslo_utils/tests/test_secretutils.py new file mode 100644 index 0000000..916610b --- /dev/null +++ b/oslo_utils/tests/test_secretutils.py @@ -0,0 +1,52 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslotest import base as test_base +import testscenarios + +from oslo_utils import secretutils + + +class SecretUtilsTest(testscenarios.TestWithScenarios, + test_base.BaseTestCase): + + scenarios = [ + ('binary', {'converter': lambda text: text.encode('utf-8')}), + ('unicode', {'converter': lambda text: text}), + ] + + def test_constant_time_compare(self): + # make sure it works as a compare, the "constant time" aspect + # isn't appropriate to test in unittests + ctc = secretutils.constant_time_compare + self.assertTrue(ctc(self.converter(u'abcd'), + self.converter(u'abcd'))) + self.assertTrue(ctc(self.converter(u''), + self.converter(u''))) + self.assertFalse(ctc(self.converter(u'abcd'), + self.converter(u'efgh'))) + self.assertFalse(ctc(self.converter(u'abc'), + self.converter(u'abcd'))) + self.assertFalse(ctc(self.converter(u'abc'), + self.converter(u'abc\x00'))) + self.assertFalse(ctc(self.converter(u''), + self.converter(u'abc'))) + self.assertTrue(ctc(self.converter(u'abcd1234'), + self.converter(u'abcd1234'))) + self.assertFalse(ctc(self.converter(u'abcd1234'), + self.converter(u'ABCD234'))) + self.assertFalse(ctc(self.converter(u'abcd1234'), + self.converter(u'a'))) + self.assertFalse(ctc(self.converter(u'abcd1234'), + self.converter(u'1234abcd')))