1010from requests .exceptions import RequestException
1111from http_message_signatures import (algorithms , HTTPSignatureComponentResolver , HTTPSignatureKeyResolver , # noqa: F401
1212 HTTPMessageSigner , HTTPMessageVerifier , HTTPSignatureAlgorithm , InvalidSignature )
13+ from http_message_signatures .structures import CaseInsensitiveDict
1314
1415
1516class RequestsHttpSignatureException (RequestException ):
@@ -54,9 +55,11 @@ class HTTPSignatureAuth(requests.auth.AuthBase):
5455 verifying). Your implementation should ensure that the key id is recognized and return the corresponding
5556 key material as PEM bytes (or shared secret bytes for HMAC).
5657 :param covered_component_ids:
57- A list of lowercased header names or derived component IDs ("@method", "@target-uri", "@authority",
58- "@scheme", "@request-target", "@path", "@query", "@query-params", "@status", or "@request-response" as
59- specified in the standard) to sign.
58+ A list of lowercased header names or derived component IDs (``@method``, ``@target-uri``, ``@authority``,
59+ ``@scheme``, ``@request-target``, ``@path``, ``@query``, ``@query-params``, ``@status``, or
60+ ``@request-response``, as specified in the standard) to sign. By default, ``@method``, ``@authority``,
61+ and ``@target-uri`` are covered, and the ``Authorization``, ``Content-Digest``, and ``Date`` header fields
62+ are always covered if present.
6063 :param label: The label to use to identify the signature.
6164 :param include_alg:
6265 By default, the signature parameters will include the ``alg`` parameter, using it to identify the signature
@@ -75,6 +78,7 @@ class HTTPSignatureAuth(requests.auth.AuthBase):
7578 """
7679
7780 _digest_hashers = {"sha-256" : hashlib .sha256 , "sha-512" : hashlib .sha512 }
81+ _auto_cover_header_fields = {"authorization" , "content-digest" , "date" }
7882
7983 def __init__ (self , * ,
8084 signature_algorithm : HTTPSignatureAlgorithm ,
@@ -111,8 +115,6 @@ def add_digest(self, request, algorithm="sha-256"):
111115 if request .body is None and "content-digest" in self .covered_component_ids :
112116 raise RequestsHttpSignatureException ("Could not compute digest header for request without a body" )
113117 if request .body is not None :
114- if "content-digest" not in self .covered_component_ids :
115- self .covered_component_ids = list (self .covered_component_ids ) + ["content-digest" ]
116118 if "Content-Digest" not in request .headers :
117119 hasher = self ._digest_hashers [algorithm ]
118120 digest = hasher (request .body ).digest ()
@@ -126,25 +128,33 @@ def get_nonce(self, request):
126128 def get_created (self , request ):
127129 created = datetime .datetime .now ()
128130 self .add_date (request , timestamp = int (created .timestamp ()))
129- # TODO: add Date to covered components
130131 return created
131132
132133 def get_expires (self , request , created ):
133134 if self .expires_in :
134135 return datetime .datetime .now () + self .expires_in
135136
137+ def get_covered_component_ids (self , request ):
138+ covered_component_ids = CaseInsensitiveDict ((k , None ) for k in self .covered_component_ids )
139+ headers = CaseInsensitiveDict (request .headers )
140+ for header in self ._auto_cover_header_fields :
141+ if header in headers :
142+ covered_component_ids .setdefault (header , None )
143+ return list (covered_component_ids )
144+
136145 def __call__ (self , request ):
137146 self .add_digest (request )
138147 created = self .get_created (request )
139148 expires = self .get_expires (request , created = created )
149+ covered_component_ids = self .get_covered_component_ids (request )
140150 self .signer .sign (request ,
141151 key_id = self .key_id ,
142152 created = created ,
143153 expires = expires ,
144154 nonce = self .get_nonce (request ),
145155 label = self .label ,
146156 include_alg = self .include_alg ,
147- covered_component_ids = self . covered_component_ids )
157+ covered_component_ids = covered_component_ids )
148158 return request
149159
150160 @classmethod
@@ -157,7 +167,8 @@ def get_body(cls, message):
157167 def verify (cls , message : Union [requests .PreparedRequest , requests .Response ], * ,
158168 require_components : List [str ] = ("@method" , "@authority" , "@target-uri" ),
159169 signature_algorithm : HTTPSignatureAlgorithm ,
160- key_resolver : HTTPSignatureKeyResolver ):
170+ key_resolver : HTTPSignatureKeyResolver ,
171+ max_age : datetime .timedelta = None ):
161172 """
162173 Verify an HTTP message signature.
163174
@@ -181,11 +192,12 @@ def verify(cls, message: Union[requests.PreparedRequest, requests.Response], *,
181192 HTTPSignatureAuth.verify(prepared_request, ...)
182193
183194 :param require_components:
184- A list of lowercased header names or derived component IDs ("@method", "@target-uri", "@authority",
185- "@scheme", "@request-target", "@path", "@query", "@query-params", "@status", or "@request-response" as
186- specified in the standard) to require to be covered by the signature. If the "content-digest" header field
187- is specified here (recommended for messages that have a body), it will be verified by matching it against
188- the digest hash computed on the body of the message (expected to be bytes).
195+ A list of lowercased header names or derived component IDs (
196+ A list of lowercased header names or derived component IDs (``@method``, ``@target-uri``, ``@authority``,
197+ ``@scheme``, ``@request-target``, ``@path``, ``@query``, ``@query-params``, ``@status``, or
198+ ``@request-response``, as specified in the standard) to require to be covered by the signature. If the
199+ "content-digest" header field is specified here (recommended for messages that have a body), it will be
200+ verified by matching it against the digest hash computed on the body of the message (expected to be bytes).
189201
190202 If this parameter is not specified, ``verify()`` will set it to ("@method", "@authority", "@target-uri")
191203 for messages without a body, and ("@method", "@authority", "@target-uri", "content-digest") for messages
@@ -203,6 +215,8 @@ def verify(cls, message: Union[requests.PreparedRequest, requests.Response], *,
203215 ``get_private_key(key_id)`` (required only for signing) and ``get_public_key(key_id)`` (required only for
204216 verifying). Your implementation should ensure that the key id is recognized and return the corresponding
205217 key material as PEM bytes (or shared secret bytes for HMAC).
218+ :param max_age:
219+ The maximum age of the signature, defined as the difference between the ``created`` parameter value and now.
206220
207221 :returns: *VerifyResult*, a namedtuple with the following attributes:
208222
@@ -225,7 +239,7 @@ def verify(cls, message: Union[requests.PreparedRequest, requests.Response], *,
225239 verifier = HTTPMessageVerifier (signature_algorithm = signature_algorithm ,
226240 key_resolver = key_resolver ,
227241 component_resolver_class = cls .component_resolver_class )
228- verify_results = verifier .verify (message )
242+ verify_results = verifier .verify (message , max_age = max_age )
229243 if len (verify_results ) != 1 :
230244 raise InvalidSignature ("Multiple signatures are not supported." )
231245 verify_result = verify_results [0 ]
0 commit comments