from typing import Sequence from .auth_jwt_decode_jwt import check_jwt_secret, split_jwt from .auth_jwt_types import JwtVerification from .crypto_constant_time_equal import constant_time_equal from .crypto_hmac_sha256 import hmac_sha256 from .encoding_utf8_utf8_encode import utf8_encode def _fail(error: str, message: str) -> JwtVerification: return JwtVerification(valid=False, claims=None, error=error, message=message) def verify_jwt(token: str, secret: Sequence[int], now: int, leeway_seconds: int) -> JwtVerification: """Check an HS256 token: shape, algorithm (so "none" and algorithm-confusion tokens are refused before any key is used), signature in constant time, then exp, nbf and iat against now with leeway. A bad token is an answer, never an exception; only a bad secret, now or leeway raises.""" check_jwt_secret(secret) if isinstance(now, bool) or not isinstance(now, int): raise TypeError("now must be a whole number of Unix seconds") if isinstance(leeway_seconds, bool) or not isinstance(leeway_seconds, int) or leeway_seconds < 0: raise ValueError("leewaySeconds must be a whole number, 0 or more") parts = split_jwt(token) if parts is None: return _fail("malformed_token", "the token is not a well-formed JWT") header, claims, signing_input, signature = parts if header.get("alg") != "HS256": return _fail("unsupported_algorithm", "only HS256 tokens are accepted") if "crit" in header: return _fail("unsupported_algorithm", "the token requires header extensions (crit) this verifier does not support") expected = hmac_sha256(secret, utf8_encode(signing_input)) if not constant_time_equal(expected, signature): return _fail("invalid_signature", "the token's signature does not match") for name in ("exp", "nbf", "iat"): if name in claims and (isinstance(claims[name], bool) or not isinstance(claims[name], (int, float))): return _fail("invalid_claims", "the token's %s claim is not a number" % name) # RFC 7519 4.1.4: the current time MUST be before exp. if "exp" in claims and now >= claims["exp"] + leeway_seconds: return _fail("token_expired", "the token has expired") # RFC 7519 4.1.5: the current time MUST be at or after nbf. if "nbf" in claims and now + leeway_seconds < claims["nbf"]: return _fail("token_not_yet_valid", "the token is not valid yet") if "iat" in claims and claims["iat"] > now + leeway_seconds: return _fail("token_issued_in_future", "the token was issued in the future") return JwtVerification(valid=True, claims=claims, error=None, message=None)