Compare commits
6 Commits
9b7d9269b5
...
69f17689f5
Author | SHA1 | Date |
---|---|---|
Andrey Kislyuk | 69f17689f5 | |
Andrey Kislyuk | 7fd0077c58 | |
Andrey Kislyuk | d8b7916639 | |
Andrey Kislyuk | adecf44d46 | |
Andrey Kislyuk | e3e7c0f4c7 | |
Andrey Kislyuk | b68a6e7db4 |
19
README.rst
19
README.rst
|
@ -28,9 +28,9 @@ Usage
|
|||
requests.get(url, auth=auth)
|
||||
|
||||
By default, only the ``Date`` header and the ``@method``, ``@authority``, and ``@target-uri`` derived component
|
||||
identifiers are signed for body-less requests such as GET. The ``Date`` header is set if it is absent. In addition, for
|
||||
requests with bodies (such as POST), the ``Content-Digest`` header is set to the SHA256 of the request body using the
|
||||
format described in the
|
||||
identifiers are signed for body-less requests such as GET. The ``Date`` header is set if it is absent. In addition,
|
||||
the ``Authorization`` header is signed if it is present, and for requests with bodies (such as POST), the
|
||||
``Content-Digest`` header is set to the SHA256 of the request body using the format described in the
|
||||
`IETF Digest Fields draft RFC <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-digest-headers>`_ and signed.
|
||||
To add other headers to the signature, pass an array of header names in the ``covered_component_ids`` keyword argument.
|
||||
See the `API documentation <https://pyauth.github.io/requests-http-signature/#id3>`_ for the full list of options and
|
||||
|
@ -48,9 +48,10 @@ The class method ``HTTPSignatureAuth.verify()`` can be used to verify responses
|
|||
return 'monorail_cat'
|
||||
|
||||
response = requests.get(url, auth=auth)
|
||||
HTTPSignatureAuth.verify(response,
|
||||
signature_algorithm=algorithms.HMAC_SHA256,
|
||||
key_resolver=MyKeyResolver())
|
||||
verify_result = HTTPSignatureAuth.verify(response,
|
||||
signature_algorithm=algorithms.HMAC_SHA256,
|
||||
key_resolver=MyKeyResolver())
|
||||
# To avoid substitution attacks, only trust response data referenced by verify_result
|
||||
|
||||
More generally, you can reconstruct an arbitrary request using the
|
||||
`Requests API <https://docs.python-requests.org/en/latest/api/#requests.Request>`_ and pass it to ``verify()``:
|
||||
|
@ -112,11 +113,11 @@ constructor as bytes in the PEM format, or configure the key resolver as follows
|
|||
|
||||
Digest algorithms
|
||||
~~~~~~~~~~~~~~~~~
|
||||
If you need to generate a Content-Digest header using SHA-512, you can do so via subclassing::
|
||||
To generate a Content-Digest header using SHA-512 instead of the default SHA-256, subclass ``HTTPSignatureAuth`` as
|
||||
follows::
|
||||
|
||||
class MySigner(HTTPSignatureAuth):
|
||||
def add_digest(self, request):
|
||||
super().add_digest(request, algorithm="sha-512")
|
||||
signing_content_digest_hasher = "sha-512"
|
||||
|
||||
Links
|
||||
-----
|
||||
|
|
|
@ -77,7 +77,10 @@ class HTTPSignatureAuth(requests.auth.AuthBase):
|
|||
to customize the retrieval of header and derived component values if needed.
|
||||
"""
|
||||
|
||||
_digest_hashers = {"sha-256": hashlib.sha256, "sha-512": hashlib.sha512}
|
||||
_content_digest_hashers = {"sha-256": hashlib.sha256, "sha-512": hashlib.sha512}
|
||||
signing_content_digest_hasher = "sha-256"
|
||||
"The hash algorithm to use to generate the Content-Digest header field (either ``sha-256`` or ``sha-512``)."
|
||||
|
||||
_auto_cover_header_fields = {"authorization", "content-digest", "date"}
|
||||
|
||||
def __init__(self, *,
|
||||
|
@ -111,14 +114,14 @@ class HTTPSignatureAuth(requests.auth.AuthBase):
|
|||
if "Date" not in request.headers:
|
||||
request.headers["Date"] = email.utils.formatdate(timestamp, usegmt=True)
|
||||
|
||||
def add_digest(self, request, algorithm="sha-256"):
|
||||
def add_digest(self, request):
|
||||
if request.body is None and "content-digest" in self.covered_component_ids:
|
||||
raise RequestsHttpSignatureException("Could not compute digest header for request without a body")
|
||||
if request.body is not None:
|
||||
if "Content-Digest" not in request.headers:
|
||||
hasher = self._digest_hashers[algorithm]
|
||||
hasher = self._content_digest_hashers[self.signing_content_digest_hasher]
|
||||
digest = hasher(request.body).digest()
|
||||
digest_node = http_sfv.Dictionary({algorithm: digest})
|
||||
digest_node = http_sfv.Dictionary({self.signing_content_digest_hasher: digest})
|
||||
request.headers["Content-Digest"] = str(digest_node)
|
||||
|
||||
def get_nonce(self, request):
|
||||
|
@ -168,7 +171,7 @@ class HTTPSignatureAuth(requests.auth.AuthBase):
|
|||
require_components: List[str] = ("@method", "@authority", "@target-uri"),
|
||||
signature_algorithm: HTTPSignatureAlgorithm,
|
||||
key_resolver: HTTPSignatureKeyResolver,
|
||||
max_age: datetime.timedelta = None):
|
||||
max_age: datetime.timedelta = datetime.timedelta(days=1)):
|
||||
"""
|
||||
Verify an HTTP message signature.
|
||||
|
||||
|
@ -192,15 +195,14 @@ class HTTPSignatureAuth(requests.auth.AuthBase):
|
|||
HTTPSignatureAuth.verify(prepared_request, ...)
|
||||
|
||||
:param require_components:
|
||||
A list of lowercased header names or derived component IDs (
|
||||
A list of lowercased header names or derived component IDs (``@method``, ``@target-uri``, ``@authority``,
|
||||
``@scheme``, ``@request-target``, ``@path``, ``@query``, ``@query-params``, ``@status``, or
|
||||
``@request-response``, as specified in the standard) to require to be covered by the signature. If the
|
||||
"content-digest" header field is specified here (recommended for messages that have a body), it will be
|
||||
``content-digest`` header field is specified here (recommended for messages that have a body), it will be
|
||||
verified by matching it against the digest hash computed on the body of the message (expected to be bytes).
|
||||
|
||||
If this parameter is not specified, ``verify()`` will set it to ("@method", "@authority", "@target-uri")
|
||||
for messages without a body, and ("@method", "@authority", "@target-uri", "content-digest") for messages
|
||||
If this parameter is not specified, ``verify()`` will set it to ``("@method", "@authority", "@target-uri")``
|
||||
for messages without a body, and ``("@method", "@authority", "@target-uri", "content-digest")`` for messages
|
||||
with a body.
|
||||
:param signature_algorithm:
|
||||
The algorithm expected to be used by the signature. Any signature not using the expected algorithm will
|
||||
|
@ -257,10 +259,10 @@ class HTTPSignatureAuth(requests.auth.AuthBase):
|
|||
if len(digest) < 1:
|
||||
raise InvalidSignature("Found a content-digest header with no digests")
|
||||
for k, v in digest.items():
|
||||
if k not in cls._digest_hashers:
|
||||
raise InvalidSignature(f'Unsupported digest algorithm "{k}"')
|
||||
if k not in cls._content_digest_hashers:
|
||||
raise InvalidSignature(f'Unsupported content digest algorithm "{k}"')
|
||||
raw_digest = v.value
|
||||
hasher = cls._digest_hashers[k]
|
||||
hasher = cls._content_digest_hashers[k]
|
||||
expect_digest = hasher(body).digest()
|
||||
if raw_digest != expect_digest:
|
||||
raise InvalidSignature("The content-digest header does not match the message body")
|
||||
|
|
|
@ -43,7 +43,7 @@ class TestAdapter(HTTPAdapter):
|
|||
response.raw = io.BytesIO(json.dumps({}).encode())
|
||||
signer = HTTPMessageSigner(signature_algorithm=self.client_auth.signer.signature_algorithm,
|
||||
key_resolver=self.client_auth.signer.key_resolver)
|
||||
hasher = HTTPSignatureAuth._digest_hashers["sha-256"]
|
||||
hasher = HTTPSignatureAuth._content_digest_hashers["sha-256"]
|
||||
digest = hasher(response.raw.getvalue()).digest()
|
||||
response.headers["Content-Digest"] = str(http_sfv.Dictionary({"sha-256": digest}))
|
||||
signer.sign(response,
|
||||
|
|
Loading…
Reference in New Issue