swift/test/unit/common/test_digest.py
Matthew Oliver 2d063cd61f formpost: deprecate sha1 signatures
We've known this would eventually be necessary for a while [1], and
way back in 2017 we started seeing SHA-1 collisions [2].

This patch follows the approach of soft deprecation of SHA1 in tempurl.
It's still a default digest, but we'll start with warning as the
middleware is loaded and exposing any deprecated digests
(if they're still allowed) in /info.

Further, because there is much shared code between formpost and tempurl, this
patch also goes and refactors shared code out into swift.common.digest.
Now that we have a digest, we also move digest related code:
 - get_hmac
 - extract_digest_and_algorithm

[1] https://www.schneier.com/blog/archives/2012/10/when_will_we_se.html
[2] https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html

Change-Id: I581cadd6bc79e623f1dae071025e4d375254c1d9
2022-07-26 10:39:58 +10:00

192 lines
8.4 KiB
Python

# Copyright (c) 2022 NVIDIA
#
# 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 hashlib
import unittest
from swift.common import digest
from test.debug_logger import debug_logger
class TestDigestUtils(unittest.TestCase):
"""Tests for swift.common.middleware.digest """
def setUp(self):
self.logger = debug_logger('test_digest_utils')
def test_get_hmac(self):
self.assertEqual(
digest.get_hmac('GET', '/path', 1, 'abc'),
'b17f6ff8da0e251737aa9e3ee69a881e3e092e2f')
def test_get_hmac_ip_range(self):
self.assertEqual(
digest.get_hmac('GET', '/path', 1, 'abc', ip_range='127.0.0.1'),
'b30dde4d2b8562b8496466c3b46b2b9ac5054461')
def test_get_hmac_ip_range_non_binary_type(self):
self.assertEqual(
digest.get_hmac(
u'GET', u'/path', 1, u'abc', ip_range=u'127.0.0.1'),
'b30dde4d2b8562b8496466c3b46b2b9ac5054461')
def test_get_hmac_digest(self):
self.assertEqual(
digest.get_hmac(u'GET', u'/path', 1, u'abc', digest='sha256'),
'64c5558394f86b042ce1e929b34907abd9d0a57f3e20cd3f93cffd83de0206a7')
self.assertEqual(
digest.get_hmac(
u'GET', u'/path', 1, u'abc', digest=hashlib.sha256),
'64c5558394f86b042ce1e929b34907abd9d0a57f3e20cd3f93cffd83de0206a7')
self.assertEqual(
digest.get_hmac(u'GET', u'/path', 1, u'abc', digest='sha512'),
'7e95af818aec1b69b53fc2cb6d69456ec64ebda6c17b8fc8b7303b78acc8ca'
'14fc4aed96c1614a8e9d6ff45a6237711d8be294cda679624825d79aa6959b'
'5229')
self.assertEqual(
digest.get_hmac(
u'GET', u'/path', 1, u'abc', digest=hashlib.sha512),
'7e95af818aec1b69b53fc2cb6d69456ec64ebda6c17b8fc8b7303b78acc8ca'
'14fc4aed96c1614a8e9d6ff45a6237711d8be294cda679624825d79aa6959b'
'5229')
def test_extract_digest_and_algorithm(self):
self.assertEqual(
digest.extract_digest_and_algorithm(
'b17f6ff8da0e251737aa9e3ee69a881e3e092e2f'),
('sha1', 'b17f6ff8da0e251737aa9e3ee69a881e3e092e2f'))
self.assertEqual(
digest.extract_digest_and_algorithm(
'sha1:sw3eTSuFYrhJZGbDtGsrmsUFRGE='),
('sha1', 'b30dde4d2b8562b8496466c3b46b2b9ac5054461'))
# also good with '=' stripped
self.assertEqual(
digest.extract_digest_and_algorithm(
'sha1:sw3eTSuFYrhJZGbDtGsrmsUFRGE'),
('sha1', 'b30dde4d2b8562b8496466c3b46b2b9ac5054461'))
self.assertEqual(
digest.extract_digest_and_algorithm(
'b963712313cd4236696fb4c4cf11fc56'
'ff4158e0bcbf1d4424df147783fd1045'),
('sha256', 'b963712313cd4236696fb4c4cf11fc56'
'ff4158e0bcbf1d4424df147783fd1045'))
self.assertEqual(
digest.extract_digest_and_algorithm(
'sha256:uWNxIxPNQjZpb7TEzxH8Vv9BWOC8vx1EJN8Ud4P9EEU='),
('sha256', 'b963712313cd4236696fb4c4cf11fc56'
'ff4158e0bcbf1d4424df147783fd1045'))
self.assertEqual(
digest.extract_digest_and_algorithm(
'sha256:uWNxIxPNQjZpb7TEzxH8Vv9BWOC8vx1EJN8Ud4P9EEU'),
('sha256', 'b963712313cd4236696fb4c4cf11fc56'
'ff4158e0bcbf1d4424df147783fd1045'))
self.assertEqual(
digest.extract_digest_and_algorithm(
'26df3d9d59da574d6f8d359cb2620b1b'
'86737215c38c412dfee0a410acea1ac4'
'285ad0c37229ca74e715c443979da17d'
'3d77a97d2ac79cc5e395b05bfa4bdd30'),
('sha512', '26df3d9d59da574d6f8d359cb2620b1b'
'86737215c38c412dfee0a410acea1ac4'
'285ad0c37229ca74e715c443979da17d'
'3d77a97d2ac79cc5e395b05bfa4bdd30'))
self.assertEqual(
digest.extract_digest_and_algorithm(
'sha512:Jt89nVnaV01vjTWcsmILG4ZzchXDjEEt/uCkEKzq'
'GsQoWtDDcinKdOcVxEOXnaF9PXepfSrHnMXjlbBb+kvdMA=='),
('sha512', '26df3d9d59da574d6f8d359cb2620b1b'
'86737215c38c412dfee0a410acea1ac4'
'285ad0c37229ca74e715c443979da17d'
'3d77a97d2ac79cc5e395b05bfa4bdd30'))
self.assertEqual(
digest.extract_digest_and_algorithm(
'sha512:Jt89nVnaV01vjTWcsmILG4ZzchXDjEEt_uCkEKzq'
'GsQoWtDDcinKdOcVxEOXnaF9PXepfSrHnMXjlbBb-kvdMA'),
('sha512', '26df3d9d59da574d6f8d359cb2620b1b'
'86737215c38c412dfee0a410acea1ac4'
'285ad0c37229ca74e715c443979da17d'
'3d77a97d2ac79cc5e395b05bfa4bdd30'))
with self.assertRaises(ValueError):
digest.extract_digest_and_algorithm('')
with self.assertRaises(ValueError):
digest.extract_digest_and_algorithm(
'exactly_forty_chars_but_not_hex_encoded!')
# Too short (md5)
with self.assertRaises(ValueError):
digest.extract_digest_and_algorithm(
'd41d8cd98f00b204e9800998ecf8427e')
# but you can slip it in via the prefix notation!
self.assertEqual(
digest.extract_digest_and_algorithm(
'md5:1B2M2Y8AsgTpgAmY7PhCfg'),
('md5', 'd41d8cd98f00b204e9800998ecf8427e'))
def test_get_allowed_digests(self):
# start with defaults
allowed, deprecated = digest.get_allowed_digests(
''.split(), self.logger)
self.assertEqual(allowed, {'sha256', 'sha512', 'sha1'})
self.assertEqual(deprecated, {'sha1'})
warning_lines = self.logger.get_lines_for_level('warning')
expected_warning_line = (
'The following digest algorithms are allowed by default but '
'deprecated: sha1. Support will be disabled by default in a '
'future release, and later removed entirely.')
self.assertIn(expected_warning_line, warning_lines)
self.logger.clear()
# now with a subset
allowed, deprecated = digest.get_allowed_digests(
'sha1 sha256'.split(), self.logger)
self.assertEqual(allowed, {'sha256', 'sha1'})
self.assertEqual(deprecated, {'sha1'})
warning_lines = self.logger.get_lines_for_level('warning')
expected_warning_line = (
'The following digest algorithms are configured but '
'deprecated: sha1. Support will be removed in a future release.')
self.assertIn(expected_warning_line, warning_lines)
self.logger.clear()
# Now also with an unsupported digest
allowed, deprecated = digest.get_allowed_digests(
'sha1 sha256 md5'.split(), self.logger)
self.assertEqual(allowed, {'sha256', 'sha1'})
self.assertEqual(deprecated, {'sha1'})
warning_lines = self.logger.get_lines_for_level('warning')
self.assertIn(expected_warning_line, warning_lines)
expected_unsupported_warning_line = (
'The following digest algorithms are configured but not '
'supported: md5')
self.assertIn(expected_unsupported_warning_line, warning_lines)
self.logger.clear()
# Now with no deprecated digests
allowed, deprecated = digest.get_allowed_digests(
'sha256 sha512'.split(), self.logger)
self.assertEqual(allowed, {'sha256', 'sha512'})
self.assertEqual(deprecated, set())
warning_lines = self.logger.get_lines_for_level('warning')
self.assertFalse(warning_lines)
self.logger.clear()
# no valid digest
# Now also with an unsupported digest
with self.assertRaises(ValueError):
digest.get_allowed_digests(['md5'], self.logger)
warning_lines = self.logger.get_lines_for_level('warning')
self.assertIn(expected_unsupported_warning_line, warning_lines)