diff --git a/http_message_signatures/signatures.py b/http_message_signatures/signatures.py index 01b63c0..cee28a2 100644 --- a/http_message_signatures/signatures.py +++ b/http_message_signatures/signatures.py @@ -111,6 +111,7 @@ def sign( class HTTPMessageVerifier(HTTPSignatureHandler): + max_clock_skew: datetime.timedelta = datetime.timedelta(seconds=5) require_created: bool = True def _parse_dict_header(self, header_name, headers): @@ -133,16 +134,18 @@ def _parse_integer_timestamp(self, ts, field_name): def validate_created_and_expires(self, sig_input, max_age=None): now = datetime.datetime.now() + min_time = now - self.max_clock_skew + max_time = now + self.max_clock_skew if "created" in sig_input.params: - if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") > now: + if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") > max_time: raise InvalidSignature('Signature "created" parameter is set to a time in the future') elif self.require_created: raise InvalidSignature('Signature is missing a required "created" parameter') if "expires" in sig_input.params: - if self._parse_integer_timestamp(sig_input.params["expires"], field_name="expires") < now: + if self._parse_integer_timestamp(sig_input.params["expires"], field_name="expires") < min_time: raise InvalidSignature('Signature "expires" parameter is set to a time in the past') if max_age is not None: - if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") + max_age < now: + if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") + max_age < min_time: raise InvalidSignature(f"Signature age exceeds maximum allowable age {max_age}") def verify(self, message, *, max_age: datetime.timedelta = datetime.timedelta(days=1)) -> List[VerifyResult]: diff --git a/test/test.py b/test/test.py index 3cabd29..7d01557 100644 --- a/test/test.py +++ b/test/test.py @@ -295,6 +295,16 @@ def test_created_expires(self): with self.assertRaisesRegex(InvalidSignature, 'Signature "expires" parameter is set to a time in the past'): verifier.verify(self.test_request) + def test_tolerate_clock_skew(self): + signer = HTTPMessageSigner(signature_algorithm=HMAC_SHA256, key_resolver=self.key_resolver) + signer.sign(self.test_request, key_id="test-shared-secret", created=datetime.fromtimestamp(1)) + verifier = HTTPMessageVerifier(signature_algorithm=HMAC_SHA256, key_resolver=self.key_resolver) + signer.sign(self.test_request, key_id="test-shared-secret", created=datetime.now() + timedelta(seconds=9)) + verifier.max_clock_skew = timedelta(seconds=10) + verifier.verify(self.test_request) + verifier.max_clock_skew = timedelta(seconds=0) + with self.assertRaisesRegex(InvalidSignature, 'Signature "created" parameter is set to a time in the future'): + verifier.verify(self.test_request) if __name__ == "__main__": unittest.main()