From 91313a6d43e6c641fe76c1defbd8702cb325f127 Mon Sep 17 00:00:00 2001 From: Andrey Kislyuk Date: Tue, 22 Aug 2017 19:19:26 -0700 Subject: [PATCH] Test suite update --- README.rst | 1 + requests_http_signature/__init__.py | 14 +++++++--- setup.cfg | 2 +- test/privkey.pem | 15 +++++++++++ test/pubkey.pem | 6 +++++ test/test.py | 40 ++++++++++++++++++++++++++--- 6 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 test/privkey.pem create mode 100644 test/pubkey.pem diff --git a/README.rst b/README.rst index bdce508..849dd32 100644 --- a/README.rst +++ b/README.rst @@ -52,6 +52,7 @@ When verifying, the ``key_resolver()`` callback should provide the public key as Links ----- * `IETF HTTP Signatures draft `_ +* https://github.com/joyent/node-http-signature * `Project home page (GitHub) `_ * `Documentation (Read the Docs) `_ * `Package distribution (PyPI) `_ diff --git a/requests_http_signature/__init__.py b/requests_http_signature/__init__.py index eb4f46e..e14ce80 100644 --- a/requests_http_signature/__init__.py +++ b/requests_http_signature/__init__.py @@ -33,7 +33,11 @@ class Crypto: assert signature == hmac.new(key, string_to_sign, digestmod=hashlib.sha256).digest() else: key = self.load_pem_public_key(key, backend=self.default_backend()) - key.verify(signature, string_to_sign, self.PKCS1v15(), self.SHA256()) + hasher = self.SHA1() if self.algorithm.endswith("sha1") else self.SHA256() + if self.algorithm == "ecdsa-sha256": + key.verify(signature, string_to_sign, self.ec.ECDSA(hasher)) + else: + key.verify(signature, string_to_sign, self.PKCS1v15(), hasher) class HTTPSignatureAuth(requests.auth.AuthBase): hasher_constructor = hashlib.sha256 @@ -93,13 +97,17 @@ class HTTPSignatureAuth(requests.auth.AuthBase): request.headers["Authorization"] = "Signature " + ",".join('{}="{}"'.format(k, v) for k, v in sig_struct) return request + @classmethod + def get_sig_struct(self, request): + scheme, sig_struct = request.headers["Authorization"].split(" ", 1) + return {i.split("=", 1)[0]: i.split("=", 1)[1].strip('"') for i in sig_struct.split(",")} + @classmethod def verify(self, request, key_resolver): assert "Authorization" in request.headers, "No Authorization header found" msg = 'Unexpected scheme found in Authorization header (expected "Signature")' assert request.headers["Authorization"].startswith("Signature "), msg - scheme, sig_struct = request.headers["Authorization"].split(" ", 1) - sig_struct = {i.split("=", 1)[0]: i.split("=", 1)[1].strip('"') for i in sig_struct.split(",")} + sig_struct = self.get_sig_struct(request) for field in "keyId", "algorithm", "signature": assert field in sig_struct, 'Required signature parameter "{}" not found'.format(field) assert sig_struct["algorithm"] in self.known_algorithms, "Unknown signature algorithm" diff --git a/setup.cfg b/setup.cfg index e9a8c95..a703cf9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,4 @@ universal=1 [flake8] max-line-length=120 -ignore: E301, E302, E401, E261, E265, E226, F401, E501 +ignore: E301, E302, E401, E261, E265, E226, F401 diff --git a/test/privkey.pem b/test/privkey.pem new file mode 100644 index 0000000..425518a --- /dev/null +++ b/test/privkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF +NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F +UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB +AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA +QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK +kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg +f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u +412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc +mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7 +kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA +gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW +G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI +7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA== +-----END RSA PRIVATE KEY----- diff --git a/test/pubkey.pem b/test/pubkey.pem new file mode 100644 index 0000000..b3bbf6c --- /dev/null +++ b/test/pubkey.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3 +6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6 +Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw +oYi+1hqp1fIekaxsyQIDAQAB +-----END PUBLIC KEY----- diff --git a/test/test.py b/test/test.py index 4bcdc7d..80fb2ea 100755 --- a/test/test.py +++ b/test/test.py @@ -15,22 +15,31 @@ hmac_secret = b"monorail_cat" passphrase = b"passw0rd" class TestAdapter(HTTPAdapter): + def __init__(self, testcase): + self.testcase = testcase def send(self, request, *args, **kwargs): def key_resolver(key_id, algorithm): if "pubkey" in request.headers: return base64.b64decode(request.headers["pubkey"]) return hmac_secret HTTPSignatureAuth.verify(request, key_resolver=key_resolver) + if "expectSig" in request.headers: + self.testcase.assertEqual(request.headers["expectSig"], + HTTPSignatureAuth.get_sig_struct(request)["signature"]) response = requests.Response() response.status_code = requests.codes.ok response.url = request.url return response +class DigestlessSignatureAuth(HTTPSignatureAuth): + def add_digest(self, request): + pass + class TestRequestsHTTPSignature(unittest.TestCase): def setUp(self): logging.basicConfig(level="DEBUG") self.session = requests.Session() - self.session.mount("http://", TestAdapter()) + self.session.mount("http://", TestAdapter(self)) def test_basic_statements(self): url = 'http://example.com/path?query#fragment' @@ -38,15 +47,39 @@ class TestRequestsHTTPSignature(unittest.TestCase): with self.assertRaises(AssertionError): self.session.get(url, auth=HTTPSignatureAuth(key=hmac_secret[::-1], key_id="sekret")) - def test_rfc_example(self): + def test_rfc_examples(self): + # The date in the RFC is wrong (2014 instead of 2012). + # See https://github.com/joyent/node-http-signature/issues/54 + # Also, the values in https://github.com/joyent/node-http-signature/blob/master/test/verify.test.js don't match + # up with ours. This is because node-http-signature seems to compute the content-length incorrectly in its test + # suite (it should be 18, but they use 17). url = 'http://example.org/foo' payload = {"hello": "world"} - date = "Tue, 07 Jun 2014 20:51:35 GMT" + date = "Thu, 05 Jan 2012 21:31:40 GMT" auth = HTTPSignatureAuth(key=hmac_secret, key_id="sekret", headers=["(request-target)", "host", "date", "digest", "content-length"]) self.session.post(url, json=payload, headers={"Date": date}, auth=auth) + pubkey_fn = os.path.join(os.path.dirname(__file__), "pubkey.pem") + privkey_fn = os.path.join(os.path.dirname(__file__), "privkey.pem") + url = "http://example.com/foo?param=value&pet=dog" + + with open(pubkey_fn, "rb") as pubkey, open(privkey_fn, "rb") as privkey: + pubkey_b64 = base64.b64encode(pubkey.read()) + auth = DigestlessSignatureAuth(algorithm="rsa-sha256", key=privkey.read(), key_id="Test") + expect_sig = "ATp0r26dbMIxOopqw0OfABDT7CKMIoENumuruOtarj8n/97Q3htHFYpH8yOSQk3Z5zh8UxUym6FYTb5+A0Nz3NRsXJibnYi7brE/4tx5But9kkFGzG+xpUmimN4c3TMN7OFH//+r8hBf7BT9/GmHDUVZT2JzWGLZES2xDOUuMtA=" # noqa + headers = {"Date": date, "pubkey": pubkey_b64, "expectSig": expect_sig} + self.session.post(url, json=payload, headers=headers, auth=auth) + + with open(pubkey_fn, "rb") as pubkey, open(privkey_fn, "rb") as privkey: + pubkey_b64 = base64.b64encode(pubkey.read()) + auth = HTTPSignatureAuth(algorithm="rsa-sha256", key=privkey.read(), key_id="Test", + headers="(request-target) host date content-type digest content-length".split()) + expect_sig = "DkOOyDlO9rXmOiU+k6L86N4UFEcey2YD+/Bz8c+Sr6XVDtDCxUuFEHMO+Atag/V1iLu+3KczVrCwjaZ39Ox3RufJghHzhTffyEkfPI6Ivf271mfRU9+wLxuGj9f+ATVO14nvcZyQjAMLvV7qh35zQcYdeD5XyxLLjuYUnK14rYI=" # noqa + headers = {"Date": date, "pubkey": pubkey_b64, "expectSig": expect_sig, "content-type": "application/json"} + self.session.post(url, json=payload, headers=headers, auth=auth) + def test_rsa(self): from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa @@ -69,5 +102,6 @@ class TestRequestsHTTPSignature(unittest.TestCase): auth = HTTPSignatureAuth(algorithm="rsa-sha256", key=private_key_pem, key_id="sekret", passphrase=passphrase) self.session.get(url, auth=auth, headers=dict(pubkey=base64.b64encode(public_key_pem))) + if __name__ == '__main__': unittest.main()