From 78b17af113752b6fc3c867f40a3b06d64c6c45f5 Mon Sep 17 00:00:00 2001 From: fulder Date: Tue, 25 Aug 2020 11:36:46 +0200 Subject: [PATCH] Update input validation for keyId and secret --- httpsig/sign.py | 12 ++ httpsig/tests/test_signature.py | 216 ++++++++++++++++++++++++++++++++ httpsig/tests/test_verify.py | 18 ++- httpsig/utils.py | 6 +- httpsig/verify.py | 8 +- 5 files changed, 255 insertions(+), 5 deletions(-) diff --git a/httpsig/sign.py b/httpsig/sign.py index 94e2180..c70741f 100644 --- a/httpsig/sign.py +++ b/httpsig/sign.py @@ -93,6 +93,18 @@ def __init__(self, key_id, secret, algorithm=None, headers=None, sign_header='au if algorithm is None: algorithm = DEFAULT_SIGN_ALGORITHM + if not key_id: + raise ValueError("key_id can't be empty") + + if len(key_id) > 100000: + raise ValueError("key_id cant be larger than 100000 chars") + + if not secret: + raise ValueError("secret can't be empty") + + if len(secret) > 100000: + raise ValueError("secret cant be larger than 100000 chars") + super(HeaderSigner, self).__init__(secret=secret, algorithm=algorithm) self.headers = headers or ['date'] self.signature_template = build_signature_template( diff --git a/httpsig/tests/test_signature.py b/httpsig/tests/test_signature.py index b8b4c90..82d4503 100755 --- a/httpsig/tests/test_signature.py +++ b/httpsig/tests/test_signature.py @@ -106,3 +106,219 @@ def test_all(self): params['headers'], '(request-target) host date content-type digest content-length') self.assertEqual(params['signature'], 'Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0=') # noqa: E501 + + def test_empty_secret(self): + with self.assertRaises(ValueError) as e: + sign.HeaderSigner(key_id='Test', secret='', headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + self.assertEqual(str(e.exception), "secret can't be empty") + + def test_none_secret(self): + with self.assertRaises(ValueError) as e: + sign.HeaderSigner(key_id='Test', secret=None, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + self.assertEqual(str(e.exception), "secret can't be empty") + + def test_huge_secret(self): + with self.assertRaises(ValueError) as e: + sign.HeaderSigner(key_id='Test', secret='x' * 1000000, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + self.assertEqual(str(e.exception), "secret cant be larger than 100000 chars") + + def test_empty_key_id(self): + with self.assertRaises(ValueError) as e: + sign.HeaderSigner(key_id='', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + self.assertEqual(str(e.exception), "key_id can't be empty") + + def test_none_key_id(self): + with self.assertRaises(ValueError) as e: + sign.HeaderSigner(key_id=None, secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + self.assertEqual(str(e.exception), "key_id can't be empty") + + def test_huge_key_id(self): + with self.assertRaises(ValueError) as e: + sign.HeaderSigner(key_id='x' * 1000000, secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + self.assertEqual(str(e.exception), "key_id cant be larger than 100000 chars") + + def test_empty_method(self): + hs = sign.HeaderSigner(key_id='Test', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + unsigned = { + 'Host': self.header_host, + 'Date': self.header_date, + 'Content-Type': self.header_content_type, + 'Digest': self.header_digest, + 'Content-Length': self.header_content_length, + } + + with self.assertRaises(ValueError) as e: + hs.sign(unsigned, method='', path=self.test_path) + self.assertEqual(str(e.exception), 'method and path arguments required when using "(request-target)"') + + def test_none_method(self): + hs = sign.HeaderSigner(key_id='Test', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + unsigned = { + 'Host': self.header_host, + 'Date': self.header_date, + 'Content-Type': self.header_content_type, + 'Digest': self.header_digest, + 'Content-Length': self.header_content_length, + } + + with self.assertRaises(ValueError) as e: + hs.sign(unsigned, method=None, path=self.test_path) + self.assertEqual(str(e.exception), 'method and path arguments required when using "(request-target)"') + + def test_empty_path(self): + hs = sign.HeaderSigner(key_id='Test', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + unsigned = { + 'Host': self.header_host, + 'Date': self.header_date, + 'Content-Type': self.header_content_type, + 'Digest': self.header_digest, + 'Content-Length': self.header_content_length, + } + + with self.assertRaises(ValueError) as e: + hs.sign(unsigned, method=self.test_method, path='') + self.assertEqual(str(e.exception), 'method and path arguments required when using "(request-target)"') + + def test_none_path(self): + hs = sign.HeaderSigner(key_id='Test', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + unsigned = { + 'Host': self.header_host, + 'Date': self.header_date, + 'Content-Type': self.header_content_type, + 'Digest': self.header_digest, + 'Content-Length': self.header_content_length, + } + + with self.assertRaises(ValueError) as e: + hs.sign(unsigned, method=self.test_method, path=None) + self.assertEqual(str(e.exception), 'method and path arguments required when using "(request-target)"') + + def test_missing_header_host(self): + hs = sign.HeaderSigner(key_id='Test', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + unsigned = { + 'Date': self.header_date, + 'Content-Type': self.header_content_type, + 'Digest': self.header_digest, + 'Content-Length': self.header_content_length, + } + + with self.assertRaises(ValueError) as e: + hs.sign(unsigned, method=self.test_method, path=self.test_path) + self.assertEqual(str(e.exception), 'missing required header "host"') + + def test_missing_header_date(self): + hs = sign.HeaderSigner(key_id='Test', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + unsigned = { + 'Host': self.header_host, + 'Content-Type': self.header_content_type, + 'Digest': self.header_digest, + 'Content-Length': self.header_content_length, + } + + with self.assertRaises(ValueError) as e: + hs.sign(unsigned, method=self.test_method, path=self.test_path) + self.assertEqual(str(e.exception), 'missing required header "date"') + + def test_missing_header_digest(self): + hs = sign.HeaderSigner(key_id='Test', secret=self.key, headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'digest', + 'content-length' + ]) + unsigned = { + 'Host': self.header_host, + 'Date': self.header_date, + 'Content-Type': self.header_content_type, + 'Content-Length': self.header_content_length, + } + + with self.assertRaises(ValueError) as e: + hs.sign(unsigned, method=self.test_method, path=self.test_path) + self.assertEqual(str(e.exception), 'missing required header "digest"') diff --git a/httpsig/tests/test_verify.py b/httpsig/tests/test_verify.py index 6e6d9eb..0e7f8b3 100755 --- a/httpsig/tests/test_verify.py +++ b/httpsig/tests/test_verify.py @@ -130,8 +130,9 @@ def test_incorrect_headers(self): required_headers=["some-other-header"], host=HOST, method=METHOD, path=PATH, sign_header=self.sign_header) - with self.assertRaises(Exception): + with self.assertRaises(ValueError) as e: hv.verify() + self.assertEqual(str(e.exception), 'some-other-header is a required header(s)') def test_extra_auth_headers(self): HOST = "example.com" @@ -166,6 +167,21 @@ def test_extra_auth_headers(self): required_headers=['date', '(request-target)']) self.assertTrue(hv.verify()) + def test_empty_secret(self): + with self.assertRaises(ValueError) as e: + HeaderVerifier(secret='', headers={}) + self.assertEqual(str(e.exception), 'secret cant be empty') + + def test_none_secret(self): + with self.assertRaises(ValueError) as e: + HeaderVerifier(secret=None, headers={}) + self.assertEqual(str(e.exception), 'secret cant be empty') + + def test_huge_secret(self): + with self.assertRaises(ValueError) as e: + HeaderVerifier(secret='x' * 1000000, headers={}) + self.assertEqual(str(e.exception), 'secret cant be larger than 100000 chars') + class TestVerifyHMACSHA256(TestVerifyHMACSHA1): diff --git a/httpsig/utils.py b/httpsig/utils.py index 5f80ef0..70323ed 100644 --- a/httpsig/utils.py +++ b/httpsig/utils.py @@ -64,7 +64,7 @@ def generate_message(required_headers, headers, host=None, method=None, h = h.lower() if h == '(request-target)': if not method or not path: - raise Exception('method and path arguments required when ' + + raise ValueError('method and path arguments required when ' + 'using "(request-target)"') signable_list.append('%s: %s %s' % (h, method.lower(), path)) @@ -76,11 +76,11 @@ def generate_message(required_headers, headers, host=None, method=None, if 'host' in headers: host = headers[h] else: - raise Exception('missing required header "%s"' % h) + raise ValueError('missing required header "%s"' % h) signable_list.append('%s: %s' % (h, host)) else: if h not in headers: - raise Exception('missing required header "%s"' % h) + raise ValueError('missing required header "%s"' % h) signable_list.append('%s: %s' % (h, headers[h])) diff --git a/httpsig/verify.py b/httpsig/verify.py index 17e313d..6367dfd 100644 --- a/httpsig/verify.py +++ b/httpsig/verify.py @@ -67,6 +67,12 @@ def __init__(self, headers, secret, required_headers=None, method=None, :param sign_header: Optional. The header where the signature is. Default is 'authorization'. """ + if not secret: + raise ValueError("secret cant be empty") + + if len(secret) > 100000: + raise ValueError("secret cant be larger than 100000 chars") + required_headers = required_headers or ['date'] self.headers = CaseInsensitiveDict(headers) @@ -101,7 +107,7 @@ def verify(self): if len(set(self.required_headers) - set(auth_headers)) > 0: error_headers = ', '.join( set(self.required_headers) - set(auth_headers)) - raise Exception( + raise ValueError( '{} is a required header(s)'.format(error_headers)) signing_str = generate_message(