#!/usr/bin/env bash # How long a key stays trusted after the issuer removes it from the JWK Set. # # The only traffic after the retirement is the leaked token itself. That is the point: # a token whose kid IS in the cached set never triggers the unknown-kid refresh, so the # only thing that can dislodge the stale JWK Set is the cache expiring on its own. # # Run under both cache configurations and diff the transcripts: # ./scripts/run-rs.sh stub,roles && ./scripts/retired-key-demo.sh "stub,roles" # ./scripts/run-rs.sh stub,roles,nottlcache && ./scripts/retired-key-demo.sh "stub,roles,nottlcache" set -eu . "$(dirname "$0")/lib.sh" PROFILES="${1:-unknown}" PROBES="${2:-16}" INTERVAL="${3:-30}" probe() { curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $1" "$RS/api/me"; } head1 "resource server profiles: $PROFILES" curl -s -X POST "$STUB/admin/reset-counter" >/dev/null echo "Scenario: a signing key is compromised. The issuer publishes a replacement and" echo "removes the compromised key from the JWK Set immediately. Tokens it signed are" echo "already out there with an hour left to run." echo VICTIM_KID=$(curl -s "$STUB/admin/state" | python3 -c 'import json,sys;print(json.load(sys.stdin)["activeKid"])') LEAKED=$(stub_token "sub=attacker&aud=reports-api&expiresInSeconds=3600") echo "1. A token signed with $VICTIM_KID, one hour to live: GET /api/me -> $(probe "$LEAKED")" echo " jwks fetches: $(stub_fetches)" echo NEW=$(curl -s -X POST "$STUB/admin/publish" | python3 -c 'import json,sys;print(json.load(sys.stdin)["publishedKids"][-1])') curl -s -X POST "$STUB/admin/activate?kid=$NEW" >/dev/null curl -s -X POST "$STUB/admin/retire?kid=$VICTIM_KID" >/dev/null echo "2. Issuer rotates to $NEW and retires $VICTIM_KID." stub_state echo echo " Anyone fetching /jwks.json from this moment sees only $NEW." echo echo "3. From here the ONLY traffic is the leaked token. Nothing carries an unknown kid," echo " so nothing forces a refresh. Whether the token keeps working is decided purely" echo " by whether the cached JWK Set expires." echo printf ' %-10s %-8s %s\n' "elapsed" "leaked" "jwksFetches" START=$(date +%s) for i in $(seq 1 "$PROBES"); do ELAPSED=$(( $(date +%s) - START )) printf ' t+%-8s %-8s %s\n' "${ELAPSED}s" "$(probe "$LEAKED")" "$(stub_fetches)" sleep "$INTERVAL" done echo echo "A row that flips to 401 is the cache expiring and the retired key going away." echo "A column of 200s is a resource server that has not noticed, and will not, until" echo "something happens to bring it a token it cannot verify."