# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-a-signing-instance-refuse-to-verify-a-token-whose-kid-is-present-but-whose-key-material-has-been-rotated-out.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-a-signing-instance-refuse-to-verify-a-token-whose-kid-is-present-but-whose-key-material-has-been-rotated-out.warrant.json
# Markdown SHA-256:   430084727e0351c60ba365d908066ae7621b3b11a66a90f5e4c030ba649c5f62
# Code AST SHA-256:   987fe04df924868d64c56111fe5f0a75e36ebbdc2655a94e8cf3c563cfe63aca
# Verified Classes:   RetiredKeyInUse, RevokedKey, UnknownKey, KeyStatus
# Defensive Guards:   5 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass
from typing import Optional


class RetiredKeyInUse(RuntimeError):
    """Raised when a token is signed by a key that has been retired and is
    outside the legitimate in-flight grace window."""


class RevokedKey(RuntimeError):
    """Raised when a token references a revoked key. Revocation is not
    time-scoped; no grace applies."""


class UnknownKey(RuntimeError):
    """Raised when the token's kid cannot be resolved at all."""


@dataclass(frozen=True)
class KeyStatus:
    kid: str
    status: str            # "active" | "retired" | "revoked"
    retired_at: Optional[float] = None   # unix seconds; required if retired


def authorize_key(
    kid: str,
    token_iat: float,
    now: float,
    keyset: dict[str, KeyStatus],
    grace_window: float = 120.0,
) -> KeyStatus:
    """Separate resolution from authorization.

    Presence in `keyset` is necessary but NOT sufficient. The retired-key
    grace window is gated on the token's issuance time, not verification time,
    so an attacker holding the old private key cannot mint fresh tokens that
    ride the window.
    """
    ks = keyset.get(kid)
    if ks is None:
        raise UnknownKey(f"kid {kid!r} not present in keyset")

    if ks.status == "revoked":
        raise RevokedKey(f"kid {kid!r} is revoked; no grace applies")

    if ks.status == "active":
        return ks

    if ks.status == "retired":
        if ks.retired_at is None:
            # A retired key with no retirement timestamp is a policy bug;
            # fail closed rather than guess.
            raise RetiredKeyInUse(
                f"kid {kid!r} retired but retired_at is unset; refusing"
            )
        within_grace = now <= ks.retired_at + grace_window
        minted_before_retirement = token_iat < ks.retired_at
        if within_grace and minted_before_retirement:
            return ks
        raise RetiredKeyInUse(
            f"kid {kid!r} retired at {ks.retired_at}; "
            f"token_iat={token_iat} now={now} grace={grace_window}"
        )

    raise RetiredKeyInUse(f"kid {kid!r} has unrecognized status {ks.status!r}")


# --- example ---
if __name__ == "__main__":
    keys = {
        "rsa-2024a": KeyStatus("rsa-2024a", "retired", retired_at=1_700_000_000),
        "ec-2025b": KeyStatus("ec-2025b", "active"),
    }
    now = 1_700_000_050
    # legitimate in-flight token, minted before retirement
    authorize_key("rsa-2024a", token_iat=1_699_999_990, now=now, keyset=keys)
    # attacker-minted fresh token under retired key -> must raise
    authorize_key("rsa-2024a", token_iat=1_700_000_040, now=now, keyset=keys)
