requests-http-signature: A Requests auth module for HTTP Signature

requests-http-signature is a Requests authentication plugin (requests.auth.AuthBase subclass) implementing the IETF HTTP Message Signatures draft standard.

Installation

$ pip install requests-http-signature

Usage

import requests
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)

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, 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 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 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:

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

response = requests.get(url, auth=auth)
verify_result = HTTPSignatureAuth.verify(response,
                                         signature_algorithm=algorithms.HMAC_SHA256,
                                         key_resolver=MyKeyResolver())

More generally, you can reconstruct an arbitrary request using the Requests API and pass it to verify():

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 and http-message-signatures packages.

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 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:

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)

class MyKeyResolver:
    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=fh.read(),
                         key_resolver=MyKeyResolver())
requests.get(url, auth=auth)

Digest algorithms

To generate a Content-Digest header using SHA-512 instead of the default SHA-256, subclass HTTPSignatureAuth as follows:

class MySigner(HTTPSignatureAuth):
    signing_content_digest_algorithm = "sha-512"

License

Licensed under the terms of the Apache License, Version 2.0.

API documentation

class requests_http_signature.HTTPSignatureAuth(*, signature_algorithm, key=None, key_id, key_resolver=None, covered_component_ids=('@method', '@authority', '@target-uri'), label=None, include_alg=True, use_nonce=False, expires_in=None)[source]

A Requests authentication plugin (requests.auth.AuthBase subclass) implementing the IETF HTTP Message Signatures draft RFC.

Parameters
  • signature_algorithm (Type[http_message_signatures._algorithms.HTTPSignatureAlgorithm]) – One of requests_http_signature.algorithms.HMAC_SHA256, requests_http_signature.algorithms.ECDSA_P256_SHA256, requests_http_signature.algorithms.ED25519, requests_http_signature.algorithms.RSA_PSS_SHA512, or requests_http_signature.algorithms.RSA_V1_5_SHA256.

  • key (bytes) – Key material that will be used to sign the request. In the case of HMAC, this should be the raw bytes of the shared secret; for all other algorithms, this should be the bytes of the PEM-encoded private key material.

  • key_id (str) – The key ID to use in the signature.

  • key_resolver (http_message_signatures.resolvers.HTTPSignatureKeyResolver) – Instead of specifying a fixed key, you can instead pass a key resolver, which should be an instance of a subclass of http_message_signatures.HTTPSignatureKeyResolver. A key resolver should have two methods, get_private_key(key_id) (required only for signing) and get_public_key(key_id) (required only for verifying). Your implementation should ensure that the key id is recognized and return the corresponding key material as PEM bytes (or shared secret bytes for HMAC).

  • covered_component_ids (Sequence[str]) – 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 sign. By default, @method, @authority, and @target-uri are covered, and the Authorization, Content-Digest, and Date header fields are always covered if present.

  • label (str) – The label to use to identify the signature.

  • include_alg (bool) – By default, the signature parameters will include the alg parameter, using it to identify the signature algorithm. If you wish not to include this parameter, set this to False.

  • use_nonce (bool) – Set this to True to include a unique message-specific nonce in the signature parameters. The format of the nonce can be controlled by subclassing this class and overloading the get_nonce() method.

  • expires_in (datetime.timedelta) – Use this to set the expires signature parameter to the time of signing plus the given timedelta.

component_resolver_class

A subclass of http_message_signatures.HTTPSignatureComponentResolver can be used to override this value to customize the retrieval of header and derived component values if needed.

alias of http_message_signatures.resolvers.HTTPSignatureComponentResolver

signing_content_digest_algorithm = 'sha-256'

The hash algorithm to use to generate the Content-Digest header field (either sha-256 or sha-512).

classmethod verify(message, *, require_components=('@method', '@authority', '@target-uri'), signature_algorithm, key_resolver, max_age=datetime.timedelta(days=1))[source]

Verify an HTTP message signature.

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.

You can ensure that the information signed is what you expect to be signed by only trusting the VerifyResult tuple returned by verify().

Parameters
  • message (Union[requests.models.PreparedRequest, requests.models.Response]) –

    The HTTP response or request to verify. You can either pass a received response, or reconstruct an arbitrary request using the Requests API:

    request = requests.Request(...)
    prepared_request = request.prepare()
    HTTPSignatureAuth.verify(prepared_request, ...)
    

  • require_components (Sequence[str]) –

    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 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 with a body.

  • signature_algorithm (Type[http_message_signatures._algorithms.HTTPSignatureAlgorithm]) – The algorithm expected to be used by the signature. Any signature not using the expected algorithm will cause an InvalidSignature exception. Must be one of requests_http_signature.algorithms.HMAC_SHA256, requests_http_signature.algorithms.ECDSA_P256_SHA256, requests_http_signature.algorithms.ED25519, requests_http_signature.algorithms.RSA_PSS_SHA512, or requests_http_signature.algorithms.RSA_V1_5_SHA256.

  • key_resolver (http_message_signatures.resolvers.HTTPSignatureKeyResolver) – A key resolver, which should be an instance of a subclass of http_message_signatures.HTTPSignatureKeyResolver. A key resolver should have two methods, get_private_key(key_id) (required only for signing) and get_public_key(key_id) (required only for verifying). Your implementation should ensure that the key id is recognized and return the corresponding key material as PEM bytes (or shared secret bytes for HMAC).

  • max_age (datetime.timedelta) – The maximum age of the signature, defined as the difference between the created parameter value and now.

Returns

VerifyResult, a namedtuple with the following attributes:

  • label (str): The label for the signature

  • algorithm: (same as signature_algorithm above)

  • covered_components: A mapping of component names to their values, as covered by the signature

  • parameters: A mapping of signature parameters to their values, as covered by the signature, including “alg”, “created”, “expires”, “keyid”, and “nonce”. To protect against replay attacks, retrieve the “nonce” parameter here and check that it has not been seen before.

  • body: The message body for messages that have a body and pass validation of the covered content-digest; None otherwise.

Raises

InvalidSignature - raised whenever signature validation fails for any reason.

Return type

http_message_signatures.structures.VerifyResult

exception requests_http_signature.RequestsHttpSignatureException(*args, **kwargs)[source]

An error occurred while constructing the HTTP Signature for your request.