Metadata-Version: 2.5
Name: requests-http-signature
Version: 2.0.0
Summary: A Requests auth module for HTTP Message Signatures
Project-URL: Homepage, https://github.com/pyauth/requests-http-signature
Author: Andrey Kislyuk
Author-email: kislyuk@gmail.com
Maintainer: Andrey Kislyuk
Maintainer-email: kislyuk@gmail.com
License: Apache Software License
License-File: LICENSE
License-File: NOTICE
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: http-message-signatures>=3.0.0
Requires-Dist: requests>=2.34.2
Requires-Dist: urllib3>=2.8.0
Provides-Extra: docs
Requires-Dist: furo; extra == 'docs'
Requires-Dist: sphinx-copybutton; extra == 'docs'
Requires-Dist: sphinx>=7; extra == 'docs'
Provides-Extra: tests
Requires-Dist: build; extra == 'tests'
Requires-Dist: coverage; extra == 'tests'
Requires-Dist: mypy; extra == 'tests'
Requires-Dist: ruff; extra == 'tests'
Requires-Dist: wheel; extra == 'tests'
Description-Content-Type: text/x-rst

requests-http-signature: A Requests auth module for HTTP Signature
==================================================================
**requests-http-signature** is a `Requests <https://github.com/requests/requests>`_ `authentication plugin
<http://docs.python-requests.org/en/master/user/authentication/>`_ (``requests.auth.AuthBase`` subclass) implementing
the
`IETF RFC 9421 HTTP Message Signatures standard <https://datatracker.ietf.org/doc/rfc9421/>`_.

Installation
------------
::

    $ pip install requests-http-signature

Usage
-----

.. code-block:: python

  import requests
  from http_message_signatures import HTTPSignatureKeyResolver
  from requests_http_signature import HTTPSignatureAuth, algorithms
  
  preshared_key_id = 'squirrel'
  preshared_secret = b'monorail_cat'
  url = 'https://example.com/'

  auth = HTTPSignatureAuth(key=preshared_secret,
                           key_id=preshared_key_id,
                           signature_algorithm=algorithms.HMAC_SHA256)
  requests.get(url, auth=auth, allow_redirects=False)

By default, the ``Date`` header and the ``@method``, ``@authority``, and ``@target-uri`` derived components are signed.
The ``Date`` header is set if it is absent, and ``Authorization`` is signed if present. The signer generates
and signs a SHA-256 ``Content-Digest`` for buffered request content, including empty content on GET requests, using
the format described in `RFC 9530, Digest Fields <https://www.rfc-editor.org/rfc/rfc9530.html>`_.
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
details.

Verifying responses
~~~~~~~~~~~~~~~~~~~
The class method ``HTTPSignatureAuth.verify()`` can be used to verify responses received back from the server:

.. code-block:: python

  class MyKeyResolver(HTTPSignatureKeyResolver):
      def resolve_public_key(self, key_id):
          assert key_id == 'squirrel'
          return b'monorail_cat'

  with requests.get(url, auth=auth, stream=True, allow_redirects=False) as response:
      verify_result = HTTPSignatureAuth.verify(response,
                                              signature_algorithm=algorithms.HMAC_SHA256,
                                              key_resolver=MyKeyResolver())

Response verification requires ``@status`` plus ``"@method";req``, ``"@authority";req``, and
``"@target-uri";req`` by default. The ``req`` flag binds the response to its associated request.
The server must sign these components; bare request-derived components are invalid in response signatures.
If your protocol uses a different coverage profile, pass it explicitly as ``require_components``.

``verify()`` reads and verifies the response body automatically. Use ``stream=True`` as shown above so it can
verify compressed content before Requests decodes it. After verification, use ``response.content``, ``response.text``,
or ``response.json()`` normally. ``verify_result.body`` contains the authenticated bytes before content decoding.
Verification buffers content in memory; it does not provide incremental verification for arbitrarily large downloads.

Nonempty content requires a covered digest. Empty responses, including HEAD, 204, and 304, do not require a digest
unless it is explicitly required or present. When present, a covered digest of empty content is verified as well.

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()``:

.. code-block:: python

  request = requests.Request(...)  # Reconstruct the incoming request using the Requests API
  prepared_request = request.prepare()  # Generate a PreparedRequest
  HTTPSignatureAuth.verify(prepared_request, ...)

To verify incoming requests and sign responses in the context of an HTTP server, see the
`flask-http-signature <https://github.com/pyauth/flask-http-signature>`_ and
`http-message-signatures <https://github.com/pyauth/http-message-signatures>`_ packages.

.. admonition:: See what is signed

 It is important to understand and follow the best practice rule of "See what is signed" when verifying HTTP message
 signatures. The gist of this rule is: if your application neglects to verify that the information it trusts is
 what was actually signed, the attacker can supply a valid signature but point you to malicious data that wasn't signed
 by that signature. Failure to follow this rule can lead to vulnerability against signature wrapping and substitution
 attacks.

 In requests-http-signature, you can ensure that the information signed is what you expect to be signed by only trusting
 the data returned by the ``verify()`` method::

   verify_result = HTTPSignatureAuth.verify(message, ...)

See the `API documentation <https://pyauth.github.io/requests-http-signature/#id3>`_ for full details.

Asymmetric key algorithms
~~~~~~~~~~~~~~~~~~~~~~~~~
To sign or verify messages with an asymmetric key algorithm, set the ``signature_algorithm`` keyword argument to
``algorithms.ED25519``, ``algorithms.ECDSA_P256_SHA256``, ``algorithms.RSA_V1_5_SHA256``, or
``algorithms.RSA_PSS_SHA512``.

For asymmetric key algorithms, you can supply the private key as the ``key`` parameter to the ``HTTPSignatureAuth()``
constructor as bytes in the PEM format, or configure the key resolver as follows:

.. code-block:: python

  with open('key.pem', 'rb') as fh:
      auth = HTTPSignatureAuth(signature_algorithm=algorithms.RSA_V1_5_SHA256,
                               key=fh.read(),
                               key_id=preshared_key_id)
  requests.get(url, auth=auth, allow_redirects=False)

  class MyKeyResolver(HTTPSignatureKeyResolver):
      def resolve_public_key(self, key_id: str):
          return public_key_pem_bytes[key_id]

      def resolve_private_key(self, key_id: str):
          return private_key_pem_bytes[key_id]

  auth = HTTPSignatureAuth(signature_algorithm=algorithms.RSA_V1_5_SHA256,
                           key_resolver=MyKeyResolver(),
                           key_id="my-key-id")
  requests.get(url, auth=auth, allow_redirects=False)

Digest algorithms
~~~~~~~~~~~~~~~~~
Digest generation and validation use the shared ``http_message_signatures.ContentDigest`` implementation.
To generate SHA-512 instead of the default SHA-256, configure a signer subclass::

  from http_message_signatures import ContentDigest, HTTPMessageSigner

  class SHA512Digest(ContentDigest):
      algorithm = "sha-512"

  class SHA512Signer(HTTPMessageSigner):
      content_digest_class = SHA512Digest

  class MyAuth(HTTPSignatureAuth):
      signer_class = SHA512Signer

``signer_class`` replaces the former ``signing_content_digest_algorithm`` and ``add_digest()`` customization points.
Fixed keys use the upstream ``MappingKeyResolver``; the package no longer provides ``SingleKeyResolver``.
Signing and verification errors from the shared APIs propagate as ``HTTPMessageSignaturesException`` or its
``InvalidSignature`` subclass. Requests-specific preparation errors raise ``RequestsHttpSignatureException``.

Request bodies and streams
~~~~~~~~~~~~~~~~~~~~~~~~~~
Bytes, text, form dictionaries, JSON, and Requests' buffered multipart uploads are supported. Text is encoded as
UTF-8 before hashing and sending. A supplied Content-Digest requires ``use_existing_digest=True`` on the auth object
and is checked against buffered content before signing. This option is also required when signing a request again
or appending another signature. A stale digest is rejected; remove or replace it after changing the content.

File objects and iterators require a caller-supplied Content-Digest for the bytes that will be sent from their current
position and ``use_existing_digest=True``. The auth handler validates the supplied digest's format and algorithms
and signs the whole field without reading or rewinding the stream. For example::

  import io
  from http_message_signatures import ContentDigest

  payload = b"upload content"
  digest = ContentDigest.create(payload)
  stream_auth = HTTPSignatureAuth(key=preshared_secret, key_id=preshared_key_id,
                                  signature_algorithm=algorithms.HMAC_SHA256, use_existing_digest=True)
  response = requests.post(url, data=io.BytesIO(payload), headers={"Content-Digest": digest},
                           auth=stream_auth, allow_redirects=False)

Keep a streaming body unchanged after computing its digest. Verification reads seekable binary request bodies from
the starting position recorded by Requests and restores their current position afterward. Non-seekable request bodies
cannot be verified. Unsupported streams without a supplied digest raise
``RequestsHttpSignatureException``. Malformed, duplicate, wrongly typed, unsupported, or mismatched digests during
verification raise ``InvalidSignature``.

Component identifiers
~~~~~~~~~~~~~~~~~~~~~
Use lowercase component names. Individual query parameters use ``@query-param`` with a ``name`` parameter::

  components = ('"@query-param";name="Foo"', '"@query-param";name="foo"')
  query_auth = HTTPSignatureAuth(key_id=preshared_key_id, key=preshared_secret,
                           signature_algorithm=algorithms.HMAC_SHA256,
                           covered_component_ids=components)

The two query names above are distinct and both are signed. Component identity includes parameter values but ignores
parameter order, as specified in RFC 9421 section 2. Duplicate identifiers, including ``date`` and ``"date"``, are errors.
When a supplied header container exposes names more than once under case-insensitive matching, the package raises an
error instead of choosing a value. It cannot recover field lines already discarded by a header container.

Dictionary-member coverage such as ``'"content-digest";key="sha-256";sf'`` is supported. Its parameter order may differ
in ``require_components``; the signed wire order is preserved. A covered digest member suffices for body validation
without requiring the whole Content-Digest field. ``@request-response`` and unsupported Structured Field flags are
not implemented.

External signing
~~~~~~~~~~~~~~~~
Use ``prepare()`` and ``apply()`` on a fully prepared Requests request when a remote service holds the private key.
No local key or resolver is needed. The auth object prepares the body digest, Date, covered components, nonce, and
creation/expiration parameters, then returns a ``PreparedSignature``::

  session = requests.Session()
  external_auth = HTTPSignatureAuth(key_id="remote-ed25519-key", signature_algorithm=algorithms.ED25519, tag="my-app")
  request = session.prepare_request(requests.Request("POST", url, json={"hello": "world"}))
  prepared = external_auth.prepare(request)
  signature = sign_base(prepared.signature_base)  # Your service signs these bytes with Ed25519.
  external_auth.apply(request, prepared, signature)
  response = session.send(request, allow_redirects=False)

For an asynchronous signer, await ``sign_base(prepared.signature_base)`` between preparation and application.
Requests' send operation remains synchronous. Do not pass a keyless auth object through ``auth=``: that path performs
local signing. Use a session without another request-signing auth handler when preparing the request externally.

``apply()`` accepts raw signature bytes, checks covered headers and buffered body digests for changes, and returns the
request. It does not authenticate the external service, verify its signature, refresh timestamps, or validate a stream
against a supplied digest. Keep the prepared object associated with the right request and keep that request stable
until sending. For DER-encoded ECDSA output, use ``algorithms.ECDSA_P256_SHA256.signature_from_der()`` before applying it.
See the `http-message-signatures external-signing documentation
<https://github.com/pyauth/http-message-signatures#remote-and-asynchronous-signing>`_ for algorithm input requirements.

Signature selection and verification policies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set a signed application ``tag`` when signing. To preserve another signature, also choose a distinct ``label`` and set
``append_if_signature_exists=True`` on the auth object or its ``apply()`` call. During verification::

  result = HTTPSignatureAuth.verify(message, signature_algorithm=algorithms.HMAC_SHA256,
                                    key_resolver=MyKeyResolver(), expect_tag="my-app", expect_label="sig1")

Selection must leave one verified signature. Labels are not signed, so the default policy requires ``expect_tag`` when
``expect_label`` is used. Use ``result.nonce`` to enforce your application's replay policy.

``max_age=None`` disables the age limit but retains expiration checks. Customize the existing verifier class to change
creation-time requirements or clock skew::

  import datetime
  from http_message_signatures import HTTPMessageVerifier

  class MyVerifier(HTTPMessageVerifier):
      require_created = False
      max_clock_skew = datetime.timedelta(seconds=30)

  class MyAuth(HTTPSignatureAuth):
      verifier_class = MyVerifier

  result = MyAuth.verify(message, signature_algorithm=algorithms.HMAC_SHA256,
                         key_resolver=MyKeyResolver(), max_age=None)

Redirects
~~~~~~~~~
Pass ``allow_redirects=False`` for signed requests. Requests copies signature headers while following redirects and
does not re-run custom auth handlers; changing the URL or method invalidates the signature. Automatic redirect
re-signing is not provided. Decide whether to follow each destination, then prepare and sign a fresh request.
For example, this GET-only flow permits one redirect within the original origin::

  from urllib.parse import urljoin, urlsplit

  response = requests.get(url, auth=auth, allow_redirects=False)
  if response.is_redirect:
      destination = urljoin(response.url, response.headers["Location"])
      origin, target = urlsplit(url), urlsplit(destination)
      response.close()
      if (origin.scheme, origin.netloc) != (target.scheme, target.netloc):
          raise ValueError("Redirect destination is not permitted")
      response = requests.get(destination, auth=auth, allow_redirects=False)

For POST or streamed uploads, handle method changes and replay of the body explicitly. Do not reuse stale digest or
signature headers from the first request.

Authors
-------
* `Andrey Kislyuk <https://kislyuk.com>`

Links
-----
* `Project home page (GitHub) <https://github.com/pyauth/requests-http-signature>`_
* `Package documentation <https://pyauth.github.io/requests-http-signature/>`_
* `Package distribution (PyPI) <https://pypi.python.org/pypi/requests-http-signature>`_
* `Change log <https://github.com/pyauth/requests-http-signature/blob/master/Changes.rst>`_
* `http-message-signatures <https://github.com/pyauth/http-message-signatures>`_ - implements HTTP message signing
  and verification
* `IETF RFC 9421, HTTP Message Signatures <https://datatracker.ietf.org/doc/rfc9421/>`_

Bugs
~~~~
Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/pyauth/requests-http-signature/issues>`_.

License
-------
Copyright 2017-2026, Andrey Kislyuk and requests-http-signature contributors. Licensed under the terms of the
`Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of attribution information,
LICENSE and NOTICE files with source copies of this package and derivative works is **REQUIRED** as specified by the
Apache License.
