1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//! M69 — id_token `azp` (authorized party) binding (OIDC Core 1.0 §2).
//!
//! OIDC Core §2: when an ID Token has multiple audiences, the Client
//! SHOULD verify that an `azp` Claim is present; when `azp` is present,
//! it MUST contain the OAuth 2.0 Client ID of the party to which the
//! ID Token was issued.
//!
//! ── Phase 7 strictness — SHOULD elevated to MUST ────────────────────────
//!
//! Two MUSTs are in force here:
//!
//! 1. **Multi-aud REQUIRES azp**. The §2 SHOULD is upgraded to MUST per
//! Phase 7 strict-by-default: a multi-audience id_token without `azp`
//! is rejected with `AzpMissing`. Pinning this prevents a future
//! drift where a multi-aud token slips through because no concrete
//! client_id was asserted.
//! 2. **azp present ⇒ azp == client_id, regardless of aud cardinality**.
//! The §2 wording "MUST contain the Client ID" is unconditional —
//! a single-aud token with a mismatched `azp` is rejected. This
//! catches the substitution case where an attacker forges azp on a
//! single-aud token expecting the engine to ignore it. Pinning *strict
//! when present* matches the Option D no-agility philosophy: the
//! engine has no negotiation surface.
//!
//! ── Expected client_id source ───────────────────────────────────────────
//!
//! The expected client_id is reused from `cfg.shared.audience` (the
//! `aud` whitelist the RP filters on). Threading a separate
//! `expected_azp_client_id` field would invite drift between the aud
//! whitelist and the azp expectation; PAS issues to one client_id at a
//! time per request, and consumers (RCW/CTW) verify against their own
//! single client_id, so the implicit mapping is sound.
//!
//! ── Order in `id_token::verify` ─────────────────────────────────────────
//!
//! Runs after the hash-binding gates (M67 at_hash, M68 c_hash) and
//! before the freshness gate (M70 auth_time) and step-up gate (M71 acr).
//! Cardinality reasoning belongs with the client-binding axis, not with
//! the temporal axes.
use crate;
pub