Test suite update
parent
546cf1f82e
commit
91313a6d43
|
@ -52,6 +52,7 @@ When verifying, the ``key_resolver()`` callback should provide the public key as
|
|||
Links
|
||||
-----
|
||||
* `IETF HTTP Signatures draft <https://tools.ietf.org/html/draft-cavage-http-signatures>`_
|
||||
* https://github.com/joyent/node-http-signature
|
||||
* `Project home page (GitHub) <https://github.com/kislyuk/requests-http-signature>`_
|
||||
* `Documentation (Read the Docs) <https://requests-http-signature.readthedocs.io/en/latest/>`_
|
||||
* `Package distribution (PyPI) <https://pypi.python.org/pypi/requests-http-signature>`_
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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-----
|
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
|
||||
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
|
||||
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
|
||||
oYi+1hqp1fIekaxsyQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
40
test/test.py
40
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()
|
||||
|
|
Loading…
Reference in New Issue