| Internet-Draft | PSEA Token Profile | October 2026 |
| Yossif | Expires 11 April 2027 | [Page] |
This document defines the PSEA Token Profile, an Entity Attestation Token (EAT) profile for action-bound, user-verification-gated transaction-confirmation Evidence. The profile specifies the canonical encoding, the signed proof-token claim set, the action-payload and cross-replay bindings, an optional hash-chain integrity layer, and the security properties that together constitute a What-You-Sign-Is-What-You-Execute proof that a human, present and verified at an authenticator, approved a specific named action at the moment of execution. The profile binds what is signed to what the Verifier executes; it does not, by itself, bind what a human saw on a potentially compromised display to what was signed (the What-You-See-Is-What-You-Sign problem), which remains out of scope. The strength of the human-presence assurance depends on the authenticator's user-verification enforcement: where the platform attestation conveys that enforcement it is hardware-attested, and otherwise it rests on the authenticator's signed assertion that user verification occurred. The profile does not, by itself, prove a specific human identity. It fills the transaction-confirmation gap left unaddressed by deployed authentication standards and complements OAuth 2.0 Step-Up Authentication by supplying the per-action, cryptographically action-bound Evidence that step-up flows can require.¶
This note is to be removed before publishing as an RFC.¶
This is an individual submission to the IETF. "Standards Track" above is the intended status; this document has not been adopted by any IETF working group and does not represent IETF consensus. The author intends to request dispatch of this work (for example through the SECDISPATCH working group) toward adoption by an appropriate working group. Review and comments are welcome at the repository referenced in the Introduction.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 11 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Session-based authentication answers "did this entity present valid credentials at some point?"; execution-time authority answers "is an authorized human, present and verified, approving this specific action right now?". The gap between these two questions is where session-hijacking, autonomous malware, and unattended-device risk materialize. This document defines a token profile that fills that gap at the token layer: a signed EAT-profile token whose claim set constitutes cryptographic Evidence of execution authority at the moment of action, independent of prior session state.¶
The signed token is a JSON Web Signature (JWS) Compact Serialization object
([RFC7515]) whose payload is an EAT-JSON claims-set. The profile reuses the
registered EAT claims ueid, eat_nonce, submods, and
eat_profile ([RFC9711]) alongside JSON Web Token (JWT) registered claims
([RFC7519]) and the PSEA-private psea_* extension claims defined in
this document.¶
The profile applies to any authenticator that can generate a hardware-backed, user-verification-gated ES256 signature over a raw digest the host supplies. Conforming authenticator classes include Trusted Execution Environment (TEE)- or Secure-Element-backed mobile keystores (for example Android Keystore keys that require user authentication, or iOS Secure Enclave keys gated by a local-authentication context), PIN-gated Personal Identity Verification (PIV)-class smartcards that expose a user-verification-gated raw ECDSA-over-digest operation, and programmable secure elements. A standard FIDO2/WebAuthn security key does not natively conform: a WebAuthn authenticator signs only its fixed assertion structure (authenticator data concatenated with a hash of the client data) and exposes no primitive to sign an arbitrary digest, so it cannot emit a JWS whose payload is an arbitrary EAT-JSON claims-set. Binding a FIDO assertion into this profile would require a separate FIDO-assertion binding, which is out of scope of this document (Section 4).¶
Where the authenticator exposes only a raw user-verification-gated signing primitive (the typical case for keystore- and smartcard-backed keys), the host application -- the integrating SDK -- assembles the JWS, computes the action-payload hash, maintains the replay counter, and populates the user-verification claim; the hardware authenticator contributes only the hardware-protected, user-verification-gated signature. In this profile the term "Attester" denotes the composite of that host software and the hardware authenticator. The security properties this profile claims rest on the hardware authenticator's protected key and its user-verification gating; the surrounding claim assembly is performed by the host and is itself untrusted until the Verifier validates the resulting signature and cross-checks the claims against the platform attestation.¶
The deployment architecture -- including any operational enforcement model that maps applications to capability or assurance levels, the HTTP verifier endpoints, the attestation-evidence wrapper, the Verifier acknowledgement, and the trust-state model -- is deployment-specific and is out of scope of this profile.¶
The transaction-confirmation gap this profile addresses was previously targeted by the WebAuthn txAuthSimple and txAuthGeneric extensions, both of which were removed in Web Authentication Level 2 without widely deployed successors (Section 4). This profile complements OAuth 2.0 Step-Up Authentication ([RFC9470]) by providing the per-action, cryptographically action-bound Evidence token that a step-up challenge can require and a resource server can verify (Section 5).¶
This document is the protocol-specification revision of PSEA. The earlier revisions draft-yossif-psea-00 and draft-yossif-psea-01 were Informational and described PSEA as a security model and a set of requirements for verifying authority at the moment of action. This revision specifies the concrete wire protocol that realizes that work as an EAT profile ([RFC9711]), with a defined claim set, a canonical encoding, and normative verification rules; the intended status is Standards Track accordingly.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses the following terms. RATS terminology follows [RFC9334]; PSEA-specific terms are defined here.¶
psea_payload_hash,
maintains psea_counter, and populates psea_uv; the authenticator contributes
the signature. A standard FIDO2/WebAuthn security key is not a conforming Attester under this
profile (Section 1). A natural person is not an Attester.¶
psea_tier claim and bound into the signature
(Section 3.13.4). The value space is opaque to this profile and is
agreed out of band between producer and Verifier; this profile does not define a fixed set of
levels, and the operational mapping of applications to levels is deployment-specific and out of
scope of this profile.¶
The canonical encoding rules in this section apply wherever a PSEA hash is taken over canonical JSON octets. The PSEA proof is carried as JWS Compact Serialization (Section 3), as is any Verifier acknowledgement a deployment defines (that artifact's format is out of scope of this profile); a JWS signature covers the received wire octets directly, so canonical encoding is therefore not load-bearing for verifying those JWS objects. It is normative (a MUST) for exactly two uses, in each of which the producer and the Verifier independently hash canonical JSON bytes with SHA-256 and compare the result:¶
actionPayload that
produces the signed psea_payload_hash claim, which the Verifier independently re-computes
and compares (Section 3.13);¶
ueid derivation -- the SHA-256 over the canonical two-member JSON object that
encodes the deviceId and iss inputs, which the Verifier independently re-computes
for its own iss (Section 7.1).¶
For this use, implementations that deviate from the rules below will produce byte sequences that fail the payload-binding comparison on peer platforms.¶
The underlying data format is JSON ([RFC8259]). Canonical encoding MUST follow the JSON Canonicalization Scheme (JCS) [RFC8785] in full. This section restates the [RFC8785] rules that are most consequential for cross-platform PSEA implementers and notes the one schema restriction PSEA imposes: PSEA payload schemas use JSON integer numbers only at this revision, so the floating-point number-serialization rules of [RFC8785], Section 3.2.2.3 do not arise (see Section 2.5); in all other respects an implementation MUST conform to [RFC8785]. Implementers MUST read this section in full before relying on a JSON library's default serialization or a third-party JCS implementation; platform defaults frequently diverge from these requirements in ways that are difficult to detect without cross-platform test vectors.¶
This document uses the verb "will" in lowercase to describe deterministic consequences of non-conformance, not as a BCP 14 keyword.¶
The canonical byte sequence MUST be encoded as UTF-8 [RFC3629]. The encoding MUST NOT include a byte-order mark.¶
Object members MUST be ordered as [RFC8785], Section 3.2.3 specifies: property names, in their raw (unescaped) form, are compared as arrays of UTF-16 code units, each code unit treated as an unsigned integer, independent of locale. The comparison MUST NOT normalize case, fold Unicode equivalence classes, or apply collation rules. Two keys that differ only in case (for example, "endedAt" and "endReason") MUST order according to the value of their first differing code unit; in this example, "endReason" (uppercase "R" = U+0052 = 82) sorts before "endedAt" (lowercase "e" at the comparison position = U+0065 = 101) because 82 < 101.¶
For names that contain no character outside the Basic Multilingual Plane, UTF-16 code-unit order, Unicode code-point order, and UTF-8 byte order coincide. They diverge when one name carries a character outside the Basic Multilingual Plane (encoded in UTF-16 as a surrogate pair in the range U+D800 to U+DFFF) and the other carries a character in the range U+E000 to U+FFFF at the same position; the UTF-16 code-unit order governs in that case.¶
This rule is the canonical encoding's most consequential implementer-warning point. See Section 2.8 for cross-platform notes.¶
JSON arrays preserve their natural element order; the canonical encoding MUST NOT re-order array elements. The semantics of array order are payload-specific and are defined where applicable in Section 3.¶
String values MUST be serialized following the encoding rules in [RFC8785], Section 3.2.2.2 (Serialization of Strings). In particular: U+0022 (quotation mark) and U+005C (reverse solidus) MUST be serialized as the two-character escapes \" and \\; U+0008, U+0009, U+000A, U+000C, and U+000D MUST be serialized as the two-character escapes \b, \t, \n, \f, and \r; every other code point in the range U+0000 to U+001F MUST be serialized in the six-character form \uhhhh with lowercase hexadecimal digits; and every other code point is emitted as is. Input containing a lone surrogate (an unpaired code point in the range U+D800 to U+DFFF) MUST cause canonicalization to fail with an error. Canonical encoding does not normalize Unicode form.¶
PSEA payload schemas define numeric fields only as integers. The canonical encoding of an integer N MUST follow [RFC8785], Section 3.2.2.3, which for an integer is its shortest decimal representation (no leading zeros, no "+" sign, no exponent, no decimal point). Because every numeric field defined in this revision is an integer, the floating-point cases of [RFC8785] do not arise; a full RFC 8785 serializer and an implementation of only this integer rule produce identical output for every payload schema defined in this revision.¶
Floating-point numbers, decimal fractions, and exponent notation MUST NOT appear in any PSEA payload field at this revision. A future revision MAY extend the subset; until such an extension is registered, any non-integer numeric value in a signed payload field is non-conformant.¶
This integers-only rule also governs the actionPayload that the action binding hashes
(Section 3.13). The actionPayload schema is deployment-defined, but it
is hashed under the same canonical encoding, so a JSON floating-point value placed in it would
re-introduce exactly the [RFC8785] floating-point serialization divergence this
section avoids for the profile's own claims. Accordingly, monetary and other fractional quantities
carried in actionPayload MUST be represented as integers in a fixed minor
unit (for example, integer cents) or as strings, and MUST NOT be represented as JSON
floating-point numbers. The worked example of Appendix A.3 follows this rule
(an amount of 2500 minor units, not 25.00). A deployment that places a JSON float in
actionPayload falls outside the cross-platform canonicalization guarantee of this section
and will observe payload-binding failures between peers whose float serializers differ.¶
The literals true, false, and null MUST be serialized
with their lowercase JSON spelling. null as a value SHOULD NOT appear in
PSEA payload schemas; absent fields are conveyed by omitting the key rather than by including
the key with a null value.¶
The canonical encoding MUST NOT include whitespace between tokens (no spaces, tabs, newlines, or carriage returns). Whitespace inside string values is preserved verbatim per Section 2.4.¶
Implementations MUST verify their canonical encoder against the test vectors in Appendix A. A canonical encoder that produces output matching the test vectors for every input in that appendix is conformant; an encoder that diverges on any single test vector is non-conformant and will produce signed bytes that fail peer verification.¶
Specific implementer-warning notes on common platform pitfalls:¶
JSONSerialization with the sortedKeys option performs a
case-insensitive sort, which diverges from this section's case-sensitive rule (see
Section 2.2). Implementations on Apple platforms
MUST NOT rely on sortedKeys. Swift's default comparison of
String values does not compare UTF-16 code units either, so an implementation
MUST emit canonical bytes via a manual sort that compares the UTF-16 code units
of each name.¶
Array.prototype.sort() on string keys compares UTF-16 code units, matching this
section's requirement. JSON.stringify with a comparator option is not standard; use a
manual canonicalization pass.¶
String.compareTo() compares UTF-16 code units, matching this section's
requirement; the standard JCS implementations in major libraries (Jackson, Gson) produce
conformant output when configured for canonical mode.¶
sorted() on strings, and the json module's
sort_keys=True, order by Unicode code point, which diverges from UTF-16 code-unit order
in the case described in Section 2.2; an implementation
MUST sort by the UTF-16 encoding of each name.¶
The most consequential pitfall is an Apple sortedKeys case-insensitive sort silently
producing canonical bytes that diverge from a peer using a case-sensitive sort, causing
cross-platform signature-verification failures. This is the failure mode the implementer-warning
here is specifically designed to prevent.¶
A PSEA proof is a JWS Compact Serialization object ([RFC7515]):
BASE64URL(protected header) || "." || BASE64URL(payload) || "." || BASE64URL(signature).
The Attester signs the header.payload octets with its hardware-protected signing key. The payload is
an Entity Attestation Token claims-set in EAT-JSON form ([RFC9711]); PSEA defines it
as an EAT profile (Section 3.1). The claim set
reuses JWT registered claims ([RFC7519]), the registered EAT claims
([RFC9711]) ueid, eat_nonce, submods, and
eat_profile for device-state conveyance and profile identification, and the profile's
PSEA-private psea_* extension
claims. The proof travels in a transport body (Section 3.6) alongside the
unsigned cleartext actionPayload that the Verifier independently hashes and compares
against the signed payload-hash claim.¶
The integrity flows are: the proof's authenticity and integrity are established by the JWS
signature over its received octets; the action binding is established by the Verifier re-hashing
the cleartext actionPayload and comparing to the signed psea_payload_hash claim
(Section 3.13); the attestation evidence carried in integrityEvidence
(contents out of scope) is appraised independently against the platform Endorsement; and, where the
deployment-optional chain
layer is enabled (Section 3.12), chainEntry is computed deterministically
by the Verifier from signed inputs, so its integrity flows from the signature on those inputs.¶
This profile defines a single proof token type: the user-verification-gated, action-bound execution-authority proof. A deployment MAY define additional, lower-assurance signed-token types that reuse the same JWS Compact Serialization representation and the same canonical encoding; such tokens are out of scope of this profile.¶
The PSEA proof is a profile of the Entity Attestation Token
([RFC9711]), as that term is used in [RFC9711], Section 6. The
token is a compact JWS ([RFC7515]) signed with ES256 (ECDSA P-256 with SHA-256)
whose payload is an EAT-JSON claims-set. A conforming PSEA proof
MUST carry the eat_profile claim ([RFC9711],
Section 4.3.2) with the stable profile identifier
https://datatracker.ietf.org/doc/draft-yossif-psea/. The claim carries a URI or an OID
and [RFC9711] defines no registry for profile identifiers, so this identifier
requires no registration and carries no dependency on the publication stream this document
appears in. A Verifier MUST reject a
proof whose eat_profile claim is absent or carries any other value, except as
permitted in Section 3.5.¶
The profile is composed of three claim families:¶
ueid (the
per-device identifier, [RFC9711], Section 4.2.1; see the encoding requirement
in Section 3.2 and the privacy discussion in
Section 7.1), eat_nonce (OPTIONAL freshness nonce),
submods (the device-state submodules map), and eat_profile (this profile's
identifier).¶
jti,
aud, iss, iat, and exp.¶
psea_*). The properties for which EAT
defines no registered claim: psea_payload_hash (action-binding payload hash;
Section 3.13), psea_tier (capability/assurance-level binding),
psea_counter (monotonic per-counter-scope replay counter;
Section 3.10), psea_uv (user-verification claim;
Section 3.7.1), psea_op (operation binding;
Section 3.13.4), and psea_proof_version. The OPTIONAL extension
claims psea_chain_prev (deployment-optional hash-chain anchor;
Section 3.12), psea_user_hash, psea_caller_package, and
psea_sdk_version MAY also be present.¶
This document does not claim that a generic EAT consumer can fully appraise a PSEA proof
without understanding the psea_* extension claims; the extension claims are
profile-specific and are defined normatively in Section 3. The
eat_profile claim signals to a consumer which extension semantics apply.¶
This subsection states the profile in the form [RFC9711], Section 6 calls for,
so the eat_profile commitment is auditable in one place. A token claiming
https://datatracker.ietf.org/doc/draft-yossif-psea/ MUST satisfy all of
the following.¶
eat_profile claim ([RFC9711], Section 4.3.2)
MUST equal the URI
https://datatracker.ietf.org/doc/draft-yossif-psea/. This document fixes exactly one
value for this claim, and that value requires no registration
(Section 8.1).¶
alg is not "ES256" and MUST apply the header hardening of
Section 3.4.¶
psea_counter
(Section 3.10), by the iat/exp validity window
(Section 3.11), and, when the Verifier issues a challenge, by the OPTIONAL
eat_nonce claim ([RFC9711]) carrying that challenge value. A Verifier
that issued a challenge MUST reject a token whose eat_nonce is absent
or does not equal the issued value.¶
kid; a Verifier MUST NOT use any key conveyed in or referenced by the
token header (Section 3.4).¶
submods map ([RFC9711]) carries a single
psea-device-state submodule whose contents are out of scope; an authenticator that
cannot produce device-state omits submods entirely.¶
ueid claim carries a deterministic per-issuer value under the RFC 9711 RAND
type tag (0x01). The value is not preserved across factory reset, so it does not meet
the permanence [RFC9711] requires of a UEID; it is retained for wire continuity,
and the migration to sueids is stated in Section 7.1.¶
additionalProperties: false,
Section 3.5), any unknown psea_proof_version
(Section 3.15), and any unrecognized crit header parameter
(Section 3.4). A consequence intended by this design is that a future
psea_* claim is rejected by current-revision Verifiers until they are upgraded to a
revision that defines it; the wire format is versioned as a whole rather than extended in
place. This departs from the ignore-unknown extensibility customary for EAT and JWT consumers,
and is chosen so that the set of claims a Verifier appraises is exactly the set the profile
revision specifies. Whether a profile may narrow EAT this far — [RFC9711],
Section 1.2 states that no claims are mandatory and that claims not recognized should be
ignored — is under discussion on the IETF RATS mailing list at the time of writing, and a
future revision may narrow this rule.¶
JWS Compact Serialization (value of the transport-body "proof" field)
BASE64URL(UTF8(protected header))
|| "." || BASE64URL(JCS-canonical-JSON(payload))
|| "." || BASE64URL(r || s) ; ES256, see Signature below
Protected header (a JSON object; base64url-encoded, no padding)
+-- alg ; "ES256" (ECDSA P-256 with SHA-256)
+-- kid ; opaque, deployment-internal enrollment-lookup selector,
| ; untrusted until verified (a forged kid fails: the wrong
| ; key will not validate the signature). The SHA-256 of the
| ; signing-key SubjectPublicKeyInfo (SPKI) in standard base64
| ; is one ILLUSTRATIVE derivation; the value space is not
| ; constrained by this profile.
+-- typ ; "psea-proof+jwt"
Payload (the claim set; base64url-encoded, no padding)
JWT registered claims (RFC 7519)
+-- jti ; action id; also the global-uniqueness key
+-- aud ; intended Verifier/audience identifier (RFC 7519)
+-- iss ; tenant / deployment id
+-- iat ; NumericDate, epoch SECONDS
+-- exp ; NumericDate, epoch SECONDS
EAT claims (RFC 9711)
+-- ueid ; base64url no-pad of the RAND-type UEID byte
| ; string, 33 bytes:
| ; 0x01 || SHA-256(JCS({"deviceId": <deviceId>,
| ; "iss": <iss>}))
| ; The two inputs are encoded as a canonical JSON
| ; object (RFC 8785), not concatenated, so the
| ; pair cannot be read two ways.
| ; Per-issuer (pairwise): the same device yields
| ; a distinct ueid per deployment
| ; (RFC 9711 Sec 4.2.1.1; 0x01 = RAND type tag)
[+-- eat_nonce] ; OPTIONAL server nonce for push-initiated
| ; freshness; omitted entirely when absent
[+-- submods] ; OPTIONAL; { "psea-device-state": {...} } --
| ; device-state appraisal input; contents
| ; out of scope of this document
+-- eat_profile ; "https://datatracker.ietf.org/doc/
| ; draft-yossif-psea/" -- one string; wrapped
| ; here only to fit the diagram
| ; (RFC 9711 Sec 4.3.2); REQUIRED
PSEA-private extension claims
+-- psea_tier ; capability/assurance indicator (opaque;
| ; deployment-defined value space)
+-- psea_counter ; JSON integer [0, 2^53-1], monotonic per
| ; (attester, counter scope)
+-- psea_payload_hash ; standard base64 of SHA-256(canonical
| ; actionPayload) -- the action binding
+-- psea_op ; operation/authority-context discriminator
| ; (REQUIRED; see cross-replay binding)
+-- psea_uv ; { "verified": bool, "method": string }
| ; user-verification (human-presence) claim;
| ; REQUIRED; see the psea_uv section
+-- psea_proof_version ; "1"
[+-- psea_chain_prev] ; OPTIONAL deployment-optional hash-chain
| ; anchor; 64-char lowercase hex
| ; (sentinel: 64 ASCII '0' for first proof);
| ; present only when the deployment enables
| ; the chain layer (see ChainEntry section)
[+-- psea_caller_package] ; OPTIONAL; reverse-DNS app identifier
[+-- psea_sdk_version] ; OPTIONAL; Attester implementation version
[+-- psea_user_hash] ; OPTIONAL; base64url no-pad; omitted
; entirely when empty
Signature
ECDSA P-256 (ES256). The signature is the fixed-width 64-byte
concatenation of the (r, s) integers, base64url no-pad, computed
over ASCII(headerB64 || "." || payloadB64). This is the JWS ES256
signature encoding (RFC 7518 Sec 3.4), NOT a DER/ASN.1 encoding.
Encoding split: ueid uses base64url (no padding) of the 33-byte
RAND-type UEID; psea_payload_hash uses standard base64 (with
padding); psea_user_hash uses base64url (no padding).
NOTE on chainEntry (deployment-optional layer): when a deployment
enables the hash-chain tamper-evidence layer, the Attester sends
psea_chain_prev IN the signed payload (the prior proof's chainEntry,
or the sentinel for the first proof). chainEntry itself is not carried
inbound: the Verifier recomputes this proof's chainEntry from the
signed inputs (see the ChainEntry section). The mechanism by which the
recomputed chainEntry is conveyed back to the Attester for use as the
next proof's psea_chain_prev is deployment-specific and out of scope.
Deployments that do not enable the chain layer omit psea_chain_prev
entirely.
Base64 encoding split (normative): psea_payload_hash MUST be encoded as
standard base64 ([RFC4648], Section 4) with padding (the pattern
^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$; the restricted final-sextet class rejects
non-canonical encodings of the 32-octet digest, whose trailing two bits are always zero).
ueid and psea_user_hash MUST be encoded as base64url ([RFC4648], Section 5) without
padding. This is a deliberate per-claim encoding split that implementers MUST observe
exactly; using base64url for psea_payload_hash or standard base64 for ueid or
psea_user_hash produces a non-conformant token.¶
The two encodings are not interchangeable; they carry the same underlying octets and differ only in alphabet and padding. A Verifier validates each claim against the exact pattern in Section 3.5.¶
The operation a proof is presented for determines which psea_tier and
psea_op values the Verifier expects in the payload; the expected values are
deployment-defined and agreed out of band. The payload's psea_tier,
psea_op, aud, and iss claims together close the cross-replay attack
surface (Section 3.13.4).¶
The transport body POSTed to the endpoint wraps the JWS in the proof field and carries
the cleartext action and evidence alongside it:¶
{
"proof": "<compact JWS>",
"actionPayload": { ... }, // unsigned cleartext; the
// Verifier re-canonicalizes,
// SHA-256s, and compares against
// psea_payload_hash
"integrityEvidence": { ... }, // OPTIONAL attestation evidence
"requestId": "...", // OPTIONAL opaque request id
"signalReport": { ... }, // OPTIONAL device-signal report
"proofId": "..." // OPTIONAL opaque proof
// identifier
}
¶
The actionPayload is unsigned cleartext; the Verifier re-canonicalizes it
(Section 2), takes its SHA-256, and compares the result against the
signed psea_payload_hash claim. This comparison is the fail-closed action binding
(Section 3.13). Because a compact JWS has a single authoritative payload, the
proof carries no envelope-level duplicates of the signed claims.¶
A compact JWS has a single, cryptographically attested payload. The PSEA proof
format therefore carries no envelope-level duplicates of its signed claims, and there is no
envelope-versus-signed-body consistency check to perform: the only field a Verifier reads ahead
of signature verification is the protected-header kid, which is the
untrusted-until-verified enrollment-lookup selector. A forged kid fails verification
because the key it selects will not validate the signature, so no separate envelope-body
comparison is needed to defend against a substituted routing value.¶
The Verifier looks up the enrollment by kid, verifies the JWS signature with the
enrolled key, and only then reads the authentic claim set (including jti,
psea_counter, psea_payload_hash, and ueid) directly from the verified
payload. There are no pre-signature lookup copies of these values to reconcile.¶
A compact JWS carries a single authoritative payload, so there is no envelope/body pair to
reconcile and this profile defines no envelope-body mismatch error codes. A Verifier reads the
signed claim set (jti, psea_counter, psea_payload_hash, ueid)
directly from the verified payload.¶
Because a JWS protected header is attacker-influenceable until the signature is verified, a Verifier MUST apply the following constraints to every PSEA proof JWS before trusting any claim. A deployment that represents its Verifier acknowledgement / Attestation Result as a JWS — that artifact's format is out of scope of this profile (Section 3.13.3) — SHOULD apply equivalent header hardening when the Attester consumes it:¶
alg is not
"ES256". ES256 is the only mandatory-to-implement algorithm at this revision
(Section 9.1.1); a Verifier that does not implement algorithm negotiation
(Section 9.1.3) MUST NOT accept any other
alg value.¶
alg is
"none". An unsigned token MUST NOT be accepted under any
circumstance.¶
jwk, jku, and x5u header parameters. The verification
key is resolved only from the enrolled record selected by the
untrusted-until-verified kid; the Verifier MUST NOT verify a PSEA JWS
against any key conveyed in or referenced by the token itself.¶
typ value. A PSEA
proof carries typ = "psea-proof+jwt", and a consumer expecting a proof
MUST reject a JWS whose typ is not "psea-proof+jwt". A deployment
that defines a Verifier acknowledgement / Attestation Result as a JWS (out of scope of this
profile) SHOULD give it a distinct typ — for example
"psea-ack+jwt" — applying the explicit-typing guidance of [RFC8725],
Section 3.11, so that a proof cannot be processed as an acknowledgement or the reverse.¶
crit header
parameter naming an extension this profile does not define, and MUST reject any
JWS that uses the unencoded-payload option (the b64 header parameter set to
false, [RFC7797]). A conforming PSEA JWS uses only the base64url-encoded
payload form and defines no critical header extensions at this revision.¶
These constraints close the classic JOSE downgrade and key-confusion attacks (algorithm
substitution, alg:"none", attacker-supplied verification keys, cross-type confusion
between proof and acknowledgement, and unencoded-payload / unknown-critical-header tricks) at the
protocol's wire surface, and align with the JSON Web Token Best Current Practices
([RFC8725]); see also Section 6.4.1.¶
The following JSON Schema describes the claim set carried in the
BASE64URL(payload) segment of the PSEA proof JWS. The claims combine JWT
registered claims ([RFC7519]), a subset of EAT claims ([RFC9711]),
and PSEA-private psea_* claims. eat_nonce and psea_user_hash are
optional and, when not applicable, are omitted entirely rather than serialized as empty or
null.¶
The schema sets additionalProperties: false. This is deliberate: as stated in the
"Extensibility model" entry of Section 3.1.1, this profile is a strict
lock-step profile, so a Verifier rejects a proof carrying any claim outside the set below, and a
future psea_* claim is by design rejected by current-revision Verifiers until they are
upgraded. This departs from the ignore-unknown-claims extensibility customary for EAT and JWT.¶
Two OPTIONAL members, psea_chain_pending and psea_last_confirmed_head, are
registered in the schema below as opaque diagnostic extension members: they are permitted so that
proofs carrying deployment-defined diagnostic context are not rejected by
additionalProperties: false, but they carry no Verifier appraisal obligation, and a
Verifier that does not recognize them ignores them.¶
The OPTIONAL member psea_rp_context_hash is not a diagnostic member and is not
ignorable in the same way. A deployment MAY bind a relying-party request context to
a proof; where a deployment does so, this claim carries that binding and a Verifier in that
deployment appraises it. A binding that is carried but never appraised confers nothing. Whether a
deployment establishes such a context at all, and the content model of the context, are
deployment-defined and out of scope of this profile, which requires no deployment to establish
one.¶
The OPTIONAL member psea_signals_hash commits the proof to an auxiliary document
carried beside it in the transport body (Section 3.6) rather than inside the
signed payload. It relates to that document as psea_payload_hash relates to
actionPayload: the digest is signed, the document is not, and the document is bound only
to the extent that a Verifier recomputes the digest over it. The document's content model is
deployment-defined and out of scope. A deployment MAY require
psea_signals_hash on every proof, and a Verifier in such a deployment
MUST refuse a proof that omits it: without the claim the auxiliary document is
unsigned and can be stripped, substituted, or replayed independently of the proof.¶
The OPTIONAL member psea_chain_prev is carried only where the deployment enables the
chain layer (Section 3.12). A deployment that enables it MAY
require the claim on every proof, and a Verifier in such a deployment MUST refuse
a proof that omits it: a proof without the claim bypasses the linkage check, so it could be
inserted into the stored proof sequence without the gap being detected.¶
The OPTIONAL member psea_stepup_selection is carried only where the deployment,
before the signature, asked the user a step-up question about the action and holds a commitment
to the expected answer. The claim places a commitment to the user's selection inside the signed
payload, so the selection is covered by the proof signature. How the question is formed, how the
commitment is constructed, and how the Verifier obtains the value it compares against are
deployment-defined and out of scope of this profile. A deployment that issued such a step-up for
an action MAY require the claim on the proof for that action, and a Verifier in
such a deployment MUST refuse a proof that omits it or whose value does not
match.¶
The eat_profile claim carries the profile identifier of
Section 3.1, and a conforming Attester emits only that value. Notwithstanding
the enumeration in the schema below, a Verifier MAY additionally accept the value
urn:ietf:params:psea:eat-profile:2, which implementations built before this revision
emit, provided every other requirement of this profile is met. That value is not a registered
URN: this document requests no registration in the urn:ietf:params namespace, and the
value is accepted only as an identifier emitted by earlier implementations, never as one a new
Attester emits.¶
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "PseaProofClaims",
"description": "The JWS payload claim set for the PSEA proof.",
"type": "object",
"required": [
"jti", "aud", "iss", "iat", "exp", "ueid", "eat_profile",
"psea_tier", "psea_op", "psea_counter", "psea_payload_hash",
"psea_uv", "psea_proof_version"
],
"properties": {
"jti": { "type": "string", "minLength": 1, "maxLength": 128,
"pattern": "^[A-Za-z0-9._-]+$",
"description": "Action id; also the global-uniqueness
key. Alphanumeric/dot/underscore/hyphen only. UUID
RECOMMENDED." },
"aud": { "type": "string", "minLength": 1, "maxLength": 256,
"description": "JWT audience (RFC 7519): identifier of
the intended Verifier/audience. See cross-replay
binding." },
"iss": { "type": "string", "minLength": 1, "maxLength": 128,
"description": "Tenant / deployment identifier;
verified against the deployment/tenant the Verifier
resolves for the request. See cross-replay
binding." },
"iat": { "type": "integer", "minimum": 0,
"description": "NumericDate; epoch SECONDS." },
"exp": { "type": "integer", "minimum": 0,
"description": "NumericDate; epoch SECONDS." },
"ueid": { "type": "string", "pattern": "^[A-Za-z0-9_-]{44}$",
"description": "RFC 9711 Sec 4.2.1.1 RAND-type UEID:
base64url (no padding) of the 33-byte string 0x01 ||
SHA-256(JCS({\"deviceId\": <deviceId>, \"iss\":
<iss>})). JCS is the RFC 8785 canonical form of that
two-member object; the inputs are encoded, not
concatenated, so the pair cannot be read two ways.
Per-issuer (pairwise): the same device yields a
distinct ueid per iss, preventing cross-deployment
correlation. The leading 0x01 is the RAND type tag.
See Privacy Considerations." },
"eat_nonce": { "type": "string",
"description": "OPTIONAL. Server nonce for
push-initiated freshness. Omitted entirely when
absent." },
"submods": { "type": "object",
"properties": {
"psea-device-state": { "type": "object",
"description": "Reserved placeholder extension
point; contributes nothing wire-normative in this
revision (contents out of scope)." }
},
"description": "OPTIONAL EAT submodules map. The
psea-device-state submodule is a reserved,
deployment-specific placeholder; an authenticator
that cannot produce device-state omits submods
entirely." },
"eat_profile": { "type": "string",
"enum": [
"https://datatracker.ietf.org/doc/draft-yossif-psea/"
],
"description": "EAT profile identifier (RFC 9711 Sec
4.3.2). REQUIRED. This document fixes exactly one
value for this claim. The claim carries a URI or an
OID and RFC 9711 defines no registry for profile
identifiers, so no registration is requested for this
value." },
"psea_tier": { "type": "string", "minLength": 1,
"maxLength": 128,
"description": "Capability/assurance-level binding.
Opaque to this profile; deployment-defined value
space agreed out of band. The Verifier rejects the
proof if it does not match the level expected for the
operation. See cross-replay binding." },
"psea_op": { "type": "string", "minLength": 1, "maxLength": 128,
"description": "Operation / authority-context
discriminator the proof is bound to. Opaque to this
profile; the Verifier rejects the proof if it does
not match the operation being performed. See
cross-replay binding." },
"psea_counter": { "type": "integer", "minimum": 0,
"maximum": 9007199254740991,
"description": "Monotonic per (attester, counter
scope). JSON integer in [0, 2^53-1]; see counter
model." },
"psea_payload_hash": { "type": "string",
"pattern": "^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$",
"description": "Standard base64 (with padding) of
SHA-256(canonical actionPayload); the action binding.
The restricted final-sextet class rejects
non-canonical encodings (the digest's trailing two
bits are always zero). See action binding." },
"psea_chain_prev": { "type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "OPTIONAL deployment-optional hash-chain
anchor. 64-char lowercase hex; sentinel 64 ASCII '0'
for the first proof. Present only when the deployment
enables the chain tamper-evidence layer. See
ChainEntry." },
"psea_uv": { "type": "object",
"required": ["verified", "method"],
"properties": {
"verified": { "type": "boolean",
"description": "Whether a contemporaneous user-
verification event gated this signature." },
"method": { "type": "string",
"description": "User-verification method.
Extensible string; suggested baseline values
include \"biometric\", \"pin\", \"fido_uv\" (no
IANA registry is defined). Unknown values MUST be
tolerated." }
},
"description": "User-verification claim. REQUIRED on
the PSEA proof. The Verifier MUST require
verified==true and MUST cross-check it against the
authenticator's attested UV-enforcement where the
attestation conveys it. See the user-verification
claim section." },
"psea_proof_version": { "type": "string", "enum": ["1"] },
"psea_caller_package": { "type": "string", "minLength": 1,
"maxLength": 256,
"description": "OPTIONAL profile-extension claim.
Reverse-DNS identifier of the calling
application." },
"psea_sdk_version": { "type": "string", "maxLength": 64,
"description": "OPTIONAL profile-extension claim. Attester
implementation version." },
"psea_signals_hash": { "type": "string",
"pattern": "^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$",
"description": "OPTIONAL profile-extension claim. Standard
base64 (with padding) of SHA-256 over the canonical form of
an auxiliary document carried beside the proof in the
transport body rather than inside the signed payload. Its
presence commits the proof to that document, so the document
cannot be substituted, stripped, or replayed independently of
the signature. Same canonicalization and encoding as
psea_payload_hash; the restricted final-sextet class rejects
non-canonical encodings. The document's content model is
deployment-defined and out of scope." },
"psea_user_hash": { "type": "string",
"pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$",
"description": "OPTIONAL. base64url (no padding) of
SHA-256(subject_id): a pairwise, deployment-local commitment
to the enrolled subject that establishes no identity, which a
Verifier MAY require to match the enrollment record (see
Subject Hash). The restricted final-sextet class rejects
non-canonical encodings. Omitted entirely when empty. MUST be
derived pairwise per issuer (see Privacy Considerations)." },
"psea_chain_pending": {
"description": "OPTIONAL registered opaque profile-extension
member; empty schema (any JSON value). Semantics are
deployment-defined and out of scope; the member carries no
Verifier appraisal obligation, and a Verifier that does not
recognize it ignores it. Present only when the deployment's
chain gap-tolerance layer emits it." },
"psea_last_confirmed_head": {
"description": "OPTIONAL registered opaque profile-extension
member; empty schema (any JSON value). Semantics are
deployment-defined and out of scope; the member carries no
Verifier appraisal obligation, and a Verifier that does not
recognize it ignores it. Present only when the deployment's
chain gap-tolerance layer emits it." },
"psea_stepup_selection": { "type": "string",
"pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$",
"description": "OPTIONAL profile-extension claim. base64url
(no padding) of a 32-octet commitment to a selection the
user made in a deployment-defined step-up interaction about
the action. Present only when the deployment issued such a
step-up for this action. The commitment's construction and
the value a Verifier compares it against are
deployment-defined and out of scope." },
"psea_rp_context_hash": {
"description": "OPTIONAL profile-extension member; empty schema
(any JSON value). Present only when the deployment binds a
relying-party request context. Where a deployment establishes
such a context, this member carries that binding and a
Verifier in that deployment appraises it rather than ignoring
it. The content model of the context is deployment-defined
and out of scope." }
},
"additionalProperties": false
}
¶
A PSEA proof is submitted to the Verifier in a transport
body of the form
{ proof, actionPayload, integrityEvidence?, requestId?, signalReport?, proofId? }, where
proof is the compact JWS (Section 3.2), actionPayload is the
unsigned cleartext action bound by psea_payload_hash (Section 3.13), and
integrityEvidence is an OPTIONAL attestation evidence block whose contents are out of scope
of this profile. The concrete transport (HTTP method and path) is deployment-specific and out of
scope.¶
Of the transport-body fields, only proof is signed, and actionPayload is
cryptographically bound to it through psea_payload_hash (Section 3.13).
The remaining fields — integrityEvidence, requestId, signalReport, and
proofId — are unsigned and attacker-mutable in transit. A Verifier MUST NOT
use any unsigned transport-body field as an input to a security decision. integrityEvidence
is appraised on its own cryptographic merits against the platform Endorsement (its appraisal does
not trust the envelope framing); requestId and proofId are correlation and
operational hints only and confer no authority.¶
signalReport is not unconditionally in that last category. Where the proof carries
psea_signals_hash (Section 3.5), that claim commits to the report, and a
Verifier in such a deployment appraises the received report against the claim by recomputing the
digest over it — the same relation actionPayload bears to psea_payload_hash. A
report appraised in that way is a committed field rather than an unsigned one, so the rule above is
not engaged by it. Where the claim is absent, or where no such comparison has been made,
signalReport is an unsigned hint conferring no authority and the rule above applies to it
without qualification.¶
This profile establishes the human-presence requirement through the signing operation itself rather than through a separate wire-level presence block. The Attester's hardware-protected signing key MUST be gated by a platform user-verification event such that the signing operation cannot complete without a successful, contemporaneous user-verification ceremony. The signed proof body is therefore implicit Evidence that a user-verification event occurred at the moment of signing; the signature itself is the presence commitment.¶
Per-operation gating (normative). The user-verification gate MUST be enforced per signing operation: each proof signature MUST be preceded by its own fresh user-verification event. A configuration in which a single user-verification event authorizes signing for a time window or for multiple subsequent signatures (duration- or window-based authentication) is NOT RECOMMENDED for any signing and is non-conformant for proof signing: it would let a compromised host pre-compute a batch of proofs from one ceremony (see Section 6.4.1, Paragraph 3). A conforming Attester binds exactly one user-verification event to exactly one proof signature.¶
This rules out "authenticate-once-then-reuse" key configurations. By way of example, the following common platform configurations are non-conformant for proof signing, and only their per-operation counterparts conform: a PIV-class smartcard in PIN-once mode (only a PIN-always / per-signature PIN policy conforms); an Android Keystore key with a non-zero user-authentication validity timeout (only a timeout of 0 — authentication required for every use — conforms); and an iOS authentication context reused across multiple signing operations (a fresh, non-reused authentication context per signature is required). The specific platform mechanism is out of scope; the normative requirement is the one-verification-per-signature binding above.¶
The user-verification mechanisms that gate the signing key — for example, a platform biometric
subsystem (fingerprint or face match), a Secure Enclave PIN entry, a hardware-token PIN, a
smartcard with PIN, a magnetic-stripe credential combined with a PIN, or any equivalent platform
user-verification primitive — are out of scope of this specification. The specific per-authenticator
mechanism is mechanism-agnostic; what is normative is that the signing operation cannot complete
without a contemporaneous user-verification event and that the proof carries an explicit
psea_uv claim asserting that fact (Section 3.7.1).¶
The Verifier MUST NOT infer presence silently from the bare existence of a
signature from the hardware-protected signing key. For the PSEA proof it MUST require the explicit
psea_uv claim and, where the authenticator's attestation conveys a UV-enforcement
property, cross-check the claim against that attested property (appraised through the
authenticator attestation and the deployment's Reference Values).
The detailed normative rule, and the statement of where this property is attested versus where it
rests on a documented trust assumption, are given in Section 3.7.1.¶
This design uses a single, compact user-verification claim rather than a parallel wire-level
presence block whose other fields would duplicate information already carried by the attestation
evidence (in integrityEvidence) for platforms that bundle user-verification into the
attestation chain. The
cryptographic anchor for "a human was present at the moment of signing" is the psea_uv
claim covered by the proof signature plus the hardware-protected-key attestation against which it is
cross-checked.¶
Every PSEA proof MUST carry a signed psea_uv claim:¶
psea_uv.verified — a boolean asserting that a contemporaneous user-verification
event gated the signing operation that produced this proof.¶
psea_uv.method — a string identifying the user-verification method. The value
space is extensible; suggested baseline values are "biometric",
"pin", and "fido_uv" (this document defines no IANA registry for them). A
Verifier MUST tolerate an unknown method value (treat it as an opaque label)
rather than rejecting solely on the method string.¶
A token that makes no human-presence claim (such tokens are out of scope of this profile)
does not carry psea_uv. The claim is part of the JWS payload and is
therefore covered by the proof's ES256 signature, so it is tamper-evident: an intermediary
cannot flip verified from false to true without invalidating the signature.¶
Normative Verifier rule: the Verifier MUST require
psea_uv.verified == true and MUST reject the proof otherwise. Beyond
that, anchoring of the claim is conditional on what the authenticator's attestation conveys:¶
psea_uv against it and MUST reject the
proof if the attested key properties contradict the claim (for example, the claim asserts
verified == true but the attested signing key is not user-authentication-gated). The
Verifier MUST NOT accept such a psea_uv as merely self-asserted when it
could have been cross-checked against the attestation.¶
This rule is device-agnostic: any conforming Attester emits psea_uv, and the
Verifier anchors it wherever the authenticator's attestation permits.¶
Examples of the per-authenticator cross-check (informative, NOT normative — the specific
mechanism is out of scope): a phone with an attested UV-gated hardware key whose key
attestation conveys an auth-enforced / user-authentication-required property; a PIN-gated
smartcard whose attested key properties indicate a PIN-verified signing operation. In each case
the conforming Attester emits psea_uv and the Verifier cross-checks it against that
authenticator's own attested UV-enforcement. The FIDO/WebAuthn user-verified (UV) flag carried
in signed authenticator data is the same idea in a different envelope, but a FIDO authenticator
does not itself emit a PSEA proof (Section 1); consuming a FIDO assertion's
UV signal would require the separate, out-of-scope FIDO-assertion binding.¶
Verification depth (known limitation): where the
authenticator's platform attestation conveys UV-enforcement (for example, a hardware-key
attestation carrying an auth-enforced field), the Verifier cross-checks psea_uv
against it and the user-verification property is attested. Where the platform
attestation cannot convey UV-enforcement, conformance rests on a documented
trust assumption in the authenticator's user-verification enforcement; in that case
the psea_uv claim is required and signed, but it is not independently attested.
Authenticator-signed user-verification evidence — for example, a FIDO-style UV flag carried in
signed authenticator data — is the path to full verifiability and is the RECOMMENDED direction
for authenticators that can produce it. This document does not claim the property is fully
solved on every platform; it normatively requires the claim and the cross-check, and documents
the residual trust assumption where the attestation surface does not yet permit the
cross-check.¶
The rationale for the unattested case above is that such a psea_uv is advisory
only — a signed boolean the host populated, with no independent cryptographic backing for the
user-verification event — so it cannot be relied upon as an attested human-presence assurance
for high-assurance operations.¶
The OPTIONAL psea_user_hash claim (Section 3.5) is an Attester-signed
subject-binding commitment: the SHA-256 of an opaque, deployment-issued subject identifier,
encoded as base64url without padding. The claim cryptographically attributes the action to a
subject identifier for the audit trail. It is a pairwise, deployment-local commitment to the
enrolled subject: it does not establish who the subject is, and a Verifier MUST NOT
treat it as proof of the identity of any person.¶
When present, psea_user_hash MUST be derived so that it does not
function as a cross-deployment correlator, mirroring the pairwise ueid derivation
(Section 7.1): the subject identifier MUST be bound to the
deployment before hashing — for example by salting the hash input with a per-issuer
(iss) secret not published with the proofs, or by deriving a per-issuer subject
identifier. A deployment MUST NOT emit a bare SHA-256 of a globally stable subject
identifier (see Section 7.2).¶
A Verifier MAY require that the per-proof psea_user_hash match the
value recorded for the subject at enrollment. A Verifier that does so MUST refuse a
proof whose psea_user_hash is absent or does not byte-exactly equal the recorded value.
Such a check establishes only that the proof was produced for the same deployment-issued subject
identifier the enrollment recorded; it does not establish which natural person that identifier
denotes. The cryptographic chain of authorization at the Verifier continues to rest on the
device-bound signature, the monotonic counter, and the presence Evidence
(Section 3.7); the subject-hash comparison is a consistency check
against the enrollment record, not a substitute for any of them.¶
The per-proof psea_user_hash is distinct from the enrollment-bound subject binding a
deployment records at enrollment time. The binding between the recorded value and a natural
person is established at enrollment, by the deployment's enrollment ceremony and identity
proofing, and is out of scope of this profile (Section 6.3).¶
The OPTIONAL psea-device-state submodule of the EAT submods map
(Section 3.5) carries an opaque device-posture commitment from the Attester.
Its contents and the Verifier's appraisal logic are out of scope of this document. An
authenticator that cannot produce device-state omits submods entirely.¶
On the wire, the psea-device-state submodule is a JSON object whose internal
structure is not constrained by this specification. The Verifier appraises the commitment as part
of the deployment's policy evaluation; the wire surface visible to the Attester is binary -- the
Verifier either accepts the commitment (the proof proceeds through subsequent appraisal steps)
or rejects the proof through the deployment's policy verdict. The
appraisal mechanism the Verifier applies, the categorical properties the commitment may carry,
and any operator-side controls over appraisal sensitivity are deployment-specific and outside
the scope of this specification.¶
Because the submodule travels inside the signed JWS payload, its bytes are covered by the proof
signature as written; a future revision of this document MAY define a normative
sealed commitment form for the submodule, but until such a revision is published, conforming
implementations treat submods.psea-device-state as an opaque deployment-specific object
whose contents are out of scope.¶
The signed psea_counter claim is a monotonic replay-ordering anchor. The Verifier
compares the submitted value against the last-accepted counter value it holds for the same
Attester and the same counter scope (see below); a value that is not strictly greater than the
stored value for that scope MUST cause the Verifier to reject the proof.¶
An Attester MAY maintain a single counter or several independent counter scopes,
and emits in psea_counter the value of the scope the proof is produced under. The number
of scopes, and the mapping of an application's actions to scopes, is deployment-specific and out of
scope of this profile. Independent scopes serve only to preserve independent offline progress: if
proofs produced offline under different scopes drew every value from one shared sequence, an
offline-signed proof would collide on submission with a proof signed under another scope after the
first was composed but before it was synced. Independent scopes let each progress without
colliding.¶
Scope selection (normative). When a deployment uses more than one counter
scope, the value that identifies the scope a proof was produced under MUST be
cryptographically bound to that proof — either carried as a signed claim (for example
psea_tier) or carried in the action payload and therefore covered by the signed
psea_payload_hash (Section 3.13). A Verifier MUST select
the stored counter value to compare against using only such a bound scope identifier, and
MUST NOT derive the scope from any field that is not covered by the signature or by
the action-binding hash. This closes the otherwise-exploitable case in which an attacker steers
the comparison toward a stale bucket by altering an unbound scope selector. Because the
action-binding check (Section 3.13.2) MUST run and
fail-closed before the counter comparison is reached, a scope identifier conveyed in the action
payload is fully bound at the point the Verifier selects the bucket. A deployment that maintains a
single counter has no scope to select and emits one monotonic sequence.¶
The cross-scope replay surface that a single counter would defend against (a captured
proof submitted under a different scope) is closed instead by a global action-identifier
uniqueness check the Verifier MUST maintain (each jti action identifier
finalizes exactly once) plus the cross-replay binding of Section 3.13.4
(the signed psea_tier + psea_op bindings force operation-correctness independent
of counter scope). The Verifier MUST retain each finalized jti for at least
as long as a proof bearing it could still be within its exp validity window (that is, at
least until the maximum exp the Verifier will accept has passed); a jti
MAY be evicted once no proof carrying it could still pass the exp freshness
check. A Verifier that retains finalized jti values for less than the exp window
reopens the replay surface this check exists to close.¶
Atomicity (normative). A Verifier MUST perform the
counter comparison-and-advance and the jti finalization atomically — serialized per
(Attester, counter scope) — within a single transaction: read the stored counter, compare, and
conditionally advance in one atomic step that also records the jti. The counter value
compared MUST be the value read inside that transaction (not a value read earlier),
and the stored value MUST be advanced only when the submitted counter is strictly
greater than it. A blind last-write-wins advance is non-conformant. This guarantees that two
concurrent or out-of-order submissions cannot both commit and cannot lower (regress) the stored
high-water mark: of any set of submissions racing at a given scope, at most one advances, and a
later-arriving lower counter is rejected rather than overwriting a higher stored value.¶
Interoperability bound (normative): Producers MUST emit
psea_counter as a JSON integer in the range [0, 253 − 1] (inclusive) so
that the value round-trips losslessly through IEEE 754 double-precision JSON parsers. Verifiers
MUST treat the claim as a 64-bit unsigned integer for comparison purposes. This
profile does not string-encode the counter; a string-valued psea_counter is
non-conformant. At any reasonable operational rate a producer never approaches the
253 bound, but producers MUST enforce this bound explicitly.¶
The signed iat and exp claims ([RFC7519]) bound the time
window in which a proof is acceptable. Both are NumericDate values in epoch seconds and are
REQUIRED (Section 3.5). A producer MUST set iat to the
time of signing and exp to a value no later than iat plus the deployment's
maximum proof lifetime.¶
A Verifier MUST reject a proof whose exp is at or before the current
time, and MUST reject a proof whose iat is implausibly in the future. The
Verifier MAY allow a small clock-skew tolerance when applying these checks;
the tolerance SHOULD NOT exceed 60 seconds. The Verifier SHOULD
additionally enforce a bounded maximum acceptable proof lifetime (the interval between
iat and exp); a RECOMMENDED bound is given in Section 9.2.
These checks MUST be applied after signature verification and before any state
mutation.¶
The iat/exp window complements, but does not replace, the replay defenses of
Section 3.10: the monotonic psea_counter provides ordering, the global
jti uniqueness check ensures each action finalizes exactly once, and the OPTIONAL
eat_nonce (when the Verifier issued a challenge) binds the proof to that challenge. A
deployment that produces proofs offline for deferred submission MUST choose an
exp margin wide enough to cover its expected offline-to-sync interval; the freshness
anchor for such proofs is then the counter and the jti uniqueness check rather than a
narrow exp window.¶
When a Verifier issued a challenge, it MUST correlate the returned proof to the
issued challenge using only the signed eat_nonce claim value read from the verified
payload — for example, by looking up its outstanding-challenge state keyed on the nonce value
itself. A Verifier MUST NOT rely on any unsigned transport-body field (for example
requestId, Section 3.6) to decide which issued challenge a proof
answers, because an unsigned field is attacker-mutable in transit and a mismatched correlation
could let a proof carrying one (signed) nonce be accepted against a different outstanding
challenge. The eat_nonce value is covered by the proof signature, so keying the
correlation on it keeps the challenge-to-proof binding within the signed surface.¶
The chainEntry is a deterministic hash-chain anchor that links each
proof to the prior proof from the same Attester. It provides a per-Attester tamper-evident ledger
of accepted proofs in addition to the monotonic ordering already provided by
psea_counter (Section 3.10); its value is detecting after-the-fact
alteration or omission within a stored proof sequence, not freshness, which the counter and the
action-binding already supply.¶
The chain is a deployment-optional layer. A deployment MAY
enable it; when enabled, the Attester carries the prior proof's chainEntry inbound as the
psea_chain_prev claim and the Verifier behaves as specified in
Section 3.12.3. A deployment that does not enable the chain
MUST omit psea_chain_prev entirely (the claim is OPTIONAL in
Section 3.5), and a Verifier that does not implement the chain ignores it.¶
Completing the chain loop requires the Verifier to convey each recomputed chainEntry
back to the Attester so it can serve as the next proof's psea_chain_prev. That conveyance
rides on the Verifier acknowledgement / Attestation Result, whose format is deployment-specific
and out of scope of this profile. Two independent implementations therefore interoperate on the
chain only after they additionally agree on that out-of-scope acknowledgement format; the chain is
consequently specified here as an optional deployment feature rather than a mandatory wire
element, and a self-contained conforming Verifier (Appendix B.3) is not
required to implement it.¶
For each PSEA proof, the chainEntry is computed over a length-prefixed
concatenation of the three input fields, where actionId is the jti claim,
counter is psea_counter, and chainPrev is psea_chain_prev.
Length-prefixing guarantees domain separation:
for any two distinct tuples (actionId, counter, chainPrev) the encoded inputs are
byte-distinct regardless of whether any field contains a : character, NUL
bytes, or any other character. This is the standard cryptographic pattern for
unambiguous hash-input encoding.¶
encode(s) = u32_be(len(utf8(s))) || utf8(s)
input = encode(actionId)
|| encode(decimal(counter))
|| encode(chainPrev)
chainEntry = lowercase_hex( SHA-256( input ) )
¶
where u32_be(n) is the 4-byte big-endian unsigned-32-bit encoding of the integer
n, utf8(s) is the UTF-8 byte sequence of the string s,
decimal(counter) is the digit-only decimal representation of counter per
Section 2.5 (the byte length of any field encoded by
encode in this revision fits comfortably in u32), and the output is the lowercase-hex
representation of the SHA-256 digest [FIPS180-4].¶
Byte layout (worked example) for
actionId="550e8400-e29b-41d4-a716-446655440000", counter=42,
chainPrev = 64 '0' characters:¶
Offset Bytes Description
─────────────────────────────────────────────────────────────────────
[00–03] 00 00 00 24 u32_be(36) = len(actionId)
[04–39] 35 35 30 65 38 34 30 30 ... actionId UTF-8 (36 bytes)
[40–43] 00 00 00 02 u32_be(2) = len("42")
[44–45] 34 32 "42" UTF-8 (2 bytes)
[46–49] 00 00 00 40 u32_be(64) = len(chainPrev)
[50–113] 30 30 30 30 ... chainPrev UTF-8 (64 bytes)
─────────────────────────────────────────────────────────────────────
Total: 114 bytes → SHA-256 → 64 hex chars
¶
For the first proof from an Attester since enrollment (i.e., when the Attester has no prior
chain entry stored locally), the Attester MUST use the sentinel value 64 ASCII
'0' characters ("0000000000000000000000000000000000000000000000000000000000000000") as
psea_chain_prev. The Verifier MUST accept this sentinel value when its
stored prior chain entry for the Attester is empty.¶
A Verifier that has enabled the chain layer performs a strict-equality linkage check on every
proof that carries psea_chain_prev: if the inbound signed
psea_chain_prev does not equal the chainEntry of the last proof the Verifier accepted
for the Attester — or the sentinel of Section 3.12.2 when the Verifier holds no
prior chain entry for the Attester — the Verifier MUST reject the proof. A
Verifier MAY, as a local operational policy out of scope of this profile,
additionally accept a bounded class of out-of-order or backfilled proofs; any such tolerance
MUST preserve the tamper-evidence of the chain by accepting only chainEntry values
the Verifier has itself previously computed and accepted for that Attester. A Verifier that has
not enabled the chain layer ignores psea_chain_prev if present and performs no linkage
check; the proof's freshness and replay resistance then rest on psea_counter
(Section 3.10), the jti uniqueness check, and the action binding.¶
PSEA's execution-authority guarantee requires that an approved action be cryptographically bound to a specific action payload. Without this binding, a captured proof from one execution can be replayed against a different payload at the Verifier.¶
A PSEA proof producer (a conforming Attester) MUST compute the action payload
hash over the canonical encoding defined in Section 2 and include the
resulting digest as the psea_payload_hash claim of the signed execution-authority
proof. The proof MUST be signed by the hardware-protected signing key that holds the active
user-verification binding for the originating action.¶
A producer MUST NOT emit a proof whose signed psea_payload_hash claim
is absent, empty, or computed over input other than the canonical encoding of the actual action
payload being authorized by the human.¶
A conforming Verifier MUST, on every per-action proof submission, perform the following steps in order:¶
psea_payload_hash claim
decoded from the signed proof in step 1.¶
psea_payload_hash claim, the Verifier
MUST reject the proof and MUST NOT return any Attestation Result
or other wire response that a Relying Party could interpret as approval.¶
The Verifier MUST emit this rejection before producing any wire response that could be interpreted by the Relying Party as an approval. A rejection MUST NOT carry a success-bearing Attestation Result.¶
The action-binding obligation in this subsection applies identically to every PSEA proof, regardless of the capability or assurance level under which it was produced; a Verifier that enforces the binding for some levels but not others is non-conformant.¶
On a successful appraisal, a Verifier that is not co-located with the Relying Party MUST convey the outcome as an integrity-protected Attestation Result. A Relying Party that receives a success-bearing, integrity-protected Attestation Result from a conforming Verifier MAY treat the action binding as enforced and is not required to re-perform the payload-hash computation. This is the central simplification that PSEA's normative fail-closed binding enables: the Relying Party can trust the Verifier's integrity-protected result without performing client-side binding enforcement.¶
The concrete encoding of the Attestation Result, the Verifier-to-Relying-Party transport,
and the enrollment ceremony that binds a kid to an enrolled key are deployment-specific
and out of scope of this profile. The extent of what a second party can build from this document
alone should be stated precisely. The proof-token validation core is fully specified here: from
this document alone a second party can build a conforming Attester that emits byte-conformant
proofs, and the Verifier-side validation core — JWS signature verification, the JOSE header
hardening (Section 3.4), the fail-closed payload-hash action binding
(Section 3.13.2), the cross-replay binding
(Section 3.13.4), the monotonic counter (Section 3.10),
the jti uniqueness check, and freshness (Section 3.11). A
deployable Verifier additionally requires two inputs this profile deliberately leaves
out of scope: the enrollment binding that resolves kid to an enrolled public key
(without which no signature can be verified), and — for human presence to be attested
rather than merely signed — the appraisal of the out-of-scope attestation evidence against which
psea_uv is cross-checked (Section 3.7.1). Interoperating on those
two interfaces, and on the Verifier-to-Relying-Party Attestation Result, additionally requires a
companion deployment profile that fixes them.¶
In addition to the payload binding of Section 3.13.2, every PSEA proof MUST carry the following cross-replay binding claims in its signed JWS payload. These close attack surfaces where a captured signed proof could be replayed across operations, across authority levels, or across deployments without the signature alone catching the mismatch.¶
psea_tier — capability / assurance-level binding: an abstract indicator of the
authority or assurance context the proof is produced under. The value space is opaque to this
profile and agreed out of band between producer and Verifier. The Verifier
MUST reject any proof whose signed psea_tier does not match the level
expected for the operation being performed.¶
psea_op — operation / authority-context binding: a string identifying the
operation the proof is presented for. The producer and Verifier agree on the value space out
of band; the values are opaque to this profile. The Verifier MUST reject any
proof whose signed psea_op does not equal the operation identifier for the operation
being performed. This is the discriminator that prevents a proof minted for one operation from
being accepted for another.¶
aud — the JWT audience claim ([RFC7519]): an identifier of the
intended Verifier / audience. This profile restricts aud to a single case-sensitive
string value; the array form that [RFC7519] otherwise permits is
non-conformant in a PSEA proof, and a Verifier MUST reject a proof whose
aud is an array or is absent. The Verifier MUST reject any proof whose
signed aud does not identify it.¶
iss — the JWT issuer claim ([RFC7519]): the deployment / tenant
identifier the Attester has locally available at sign time. The Verifier
MUST reject any proof whose signed iss does not match the deployment /
tenant the Verifier resolves for the request.¶
All four checks MUST fire after the signed body's signature has been verified (so the bytes are confirmed authentic) and before any state mutation (counter consumption, ledger write, acknowledgement signing). The checks are byte-equality string comparisons; comparisons MUST NOT normalize case or whitespace.¶
The expected psea_tier and psea_op values for a given operation are
deployment-defined and agreed out of band; the proof also carries the aud and
iss bindings above. Any additional, lower-assurance signed-token types a deployment
defines (out of scope of this profile) carry the same binding claims under the same claim names
with their own values.¶
These bindings together close: (a) cross-operation replay (a captured proof for one operation presented for another), (b) cross-level downgrade (a higher-assurance proof processed as a lower-assurance one), and (c) cross-deployment replay (a captured proof presented to a different deployment's Verifier). The bindings are locally derivable at sign time without server round-trips, preserving the offline-capable property of proofs produced offline.¶
The scope of this protection should not be over-read. The cross-replay binding defends against
capture-replay of an existing proof — a proof minted for one operation, level,
audience, or deployment being presented for a different one. It does not defend
against a compromised host minting a fresh proof for the wrong operation: because the
host chooses the claim values it asks the user-verification-gated key to sign, a compromised host
that obtains a user-verification ceremony can sign a proof carrying whatever psea_op /
psea_tier / actionPayload it wishes. The user-verification gate constrains
that signing can occur, not what content is signed. That compromised-host case
is the What-You-See-Is-What-You-Sign problem and is treated in
Section 6.8.2.¶
When a deployment has enrolled an expected caller identity for the operation, a conforming
Verifier MUST reject a proof whose signed psea_caller_package is absent
or does not byte-exactly equal the expected value, before any state mutation. A Verifier
MUST refuse a proof that omits psea_caller_package where the deployment
has registered caller identities, and SHOULD refuse it otherwise. This is
stricter than -02, which required a deployment without an enrolled caller identity not to treat
the claim's absence as failure. The binding is covered by the JWS signature; the comparison is a byte-equality
string comparison that MUST NOT normalize case or whitespace.¶
The enrollment ceremony that binds a kid to an enrolled signing key and to a subject is
deployment-specific and out of scope of this profile (Section 3.13.3). The
enrollment lifecycle states that the Verifier enforces on every proof are, by contrast, in
scope and normative, because they are the trust gate that makes the rest of the profile meaningful:
a valid signature from an enrolled key is necessary but not sufficient for acceptance.¶
A conforming Verifier MUST maintain an authoritative, server-side enrollment
lifecycle for each enrolled Attester with at least the states active, suspended,
and revoked, and MUST reject any proof whose enrollment is not in the
active state. This enrollment-status check is authoritative: this profile carries no
self-asserted wire trust-state field, and a Verifier MUST NOT derive the trust state
from any value the Attester supplies. The check MUST be applied after signature
verification and before any state mutation. The transitions between these states, and the events
that trigger them, are deployment-specific; the requirement here is only that the three states
exist and gate acceptance. The Elevation-of-Privilege analysis in
Section 6.4.6 relies on this requirement.¶
The psea_proof_version claim identifies the wire-format
revision the producer targets. This revision uses "1". Future revisions
MAY extend the value space; a Verifier
MUST reject a proof carrying an unknown version value. Conforming implementations
at this revision emit only "1"; the
version field is included in the signed payload so that the wire-version commitment is
cryptographically anchored.¶
WebAuthn and FIDO2 provide phishing-resistant, hardware-backed authentication for the login boundary. Their transaction-confirmation features do not provide the per-action execution-time action-binding this profile defines, for the following specific reason.¶
Web Authentication Level 1 ([WebAuthn-L1]) defined two transaction-confirmation
extensions: txAuthSimple (display a text prompt and bind the user's approval of that exact
text into the signed assertion) and txAuthGeneric (the same for a hashed content blob).
These were the mechanism by which a WebAuthn assertion could attest that the user approved a
specific transaction -- a What-You-See-Is-What-You-Sign (WYSIWYS) binding. No browser implemented
these extensions, and they were removed in Web Authentication Level 2
([WebAuthn-L2]). A current WebAuthn assertion
therefore attests that an authenticator holding a given credential was exercised with user presence
(and optionally user verification), but it does not cryptographically bind the assertion to a
specific application-level action or to human-readable transaction content.¶
Consequently, the transaction-binding / WYSIWYS property -- proof that a present, verified human approved a specific named action, with specific content, at the moment of execution -- is not provided by any deployed authentication standard today. PSEA addresses precisely this gap: a PSEA proof binds the action identity and a SHA-256 hash of the action payload (the fail-closed action-binding defined later in this document) into a hardware-signed Attestation produced at execution time. PSEA is complementary to WebAuthn and FIDO rather than a replacement: WebAuthn and FIDO2 remain appropriate for establishing the session and verifying the human's credential, while PSEA supplies the per-action execution-time Evidence that the transaction-confirmation extensions were intended to provide but, in deployed form, do not.¶
OAuth 2.0 Step-Up Authentication Challenge [RFC9470] defines a mechanism by which an OAuth-protected resource server signals to a client that the bearer access token presented does not satisfy the resource's authentication requirements. The resource server responds with HTTP 401 and a WWW-Authenticate challenge carrying a required Authentication Context Class Reference (acr_values) and/or a maximum authentication age (max_age). The client then drives the user through re-authentication and obtains a new access token that satisfies the requirement.¶
RFC 9470 and PSEA address adjacent but distinct points in the access-control chain. Both react to the same operational gap: an access token or session that was sufficient at issuance time may be insufficient at the moment a higher-risk action is requested. They differ in the unit of escalation and in the cryptographic properties of the artifact produced.¶
| Property | RFC 9470 step-up | PSEA |
|---|---|---|
| Unit of escalation | The access token (session-scoped). | The individual action (action-scoped). |
| Artifact produced | A new bearer access token, possibly with refreshed acr / auth_time claims. | Evidence (signed proof token; see Section 3) cryptographically bound to the specific action payload. |
| Scope of authority granted | Subsequent requests within the resource server's session policy, until the new token expires or fails its own freshness check. | The single action whose payload is bound by the Evidence. No subsequent action inherits authority. |
| Connectivity requirement | Re-authentication requires reachability of the authorization server. | Deployment-dependent: a proof MAY be produced offline with deferred Verifier interaction, or require synchronous Verifier interaction, per deployment policy (out of scope). |
| Action-payload binding | Not defined. The new token does not bind to any specific resource or operation. | Required for every PSEA proof. The Evidence body contains the action's payload hash; see Section 3.13. |
| Authentication context | Conveyed via acr_values; identifiers are registry-managed and opaque to OAuth itself. | Conveyed via the abstract capability/assurance indicator and other fields in the Evidence. |
RFC 9470 step-up and PSEA MAY be composed: the first to signal that a per-action authority check is required; the second to produce the cryptographically action-bound Evidence that satisfies that check.¶
In this composition, OAuth supplies the session and identity context; PSEA supplies the per- action cryptographic authority proof. Neither mechanism replaces the other.¶
This section identifies architectural complementarity. It does not define a normative integration profile between PSEA and [RFC9470]. A normative integration profile (specifying the precise WWW-Authenticate challenge syntax, the acr_values registration for PSEA capability/assurance levels, the error-mapping table between RFC 9470 challenges and PSEA error codes, and the conveyance of the Evidence in the re-submitted request) is deferred to a future document.¶
This section enumerates the threats this document addresses, the assumptions on which the protocol's security properties rest, and the residual risks. The method is a STRIDE decomposition (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of privilege) applied to the protocol's wire surface; threats specific to a particular deployment's policy or non-protocol implementation choices are out of scope.¶
The protocol's security properties depend on the following assumptions about a conforming Attester:¶
Every security property in this document is conditional on one out-of-scope step: the
enrollment ceremony that binds an enrolled signing key (selected at verification time by
kid) to the correct device and the correct subject. This is stated as an out-of-scope
assumption in O3 (Section 6.7), but its consequence is foundational and
is called out here explicitly so it is not understated: if enrollment binds the wrong key, the
entire chain of guarantees is anchored to the wrong hardware, and every downstream check in this
profile still passes.¶
Concretely, an adversary who can complete the deployment's enrollment ceremony with hardware
the adversary controls — enrolling the adversary's own hardware-protected key as if it were the
victim's device — thereafter produces proofs that are valid in every respect this profile can
check: the signature verifies against the enrolled key, the attestation chain is genuine (it
attests the adversary's real hardware), the user-verification gate fires (on the adversary), the
action binding holds, and the counter and jti checks pass. The Verifier has no in-protocol
signal that the enrolled key belongs to the wrong person, and the substitution persists for the
lifetime of the enrollment. The forged-binding cannot be detected by the wire protocol; it can be
detected only by the strength of the enrollment ceremony and by out-of-band identity proofing.¶
Deployments therefore MUST treat enrollment as the security-critical root of the whole system and apply identity-proofing, attestation freshness, and binding controls commensurate with the authority the resulting proofs will carry. No property claimed elsewhere in this document is stronger than the enrollment binding it rests on.¶
S1. Attacker forging Evidence on behalf of an Attester. Mitigated by hardware-protected
signing keys (A1) and the Verifier's appraisal of the attestation chain. An attacker without access to the hardware key
store cannot produce a valid signature, so the Verifier rejects the proof. The
JOSE header hardening of Section 3.4 additionally closes algorithm-
substitution, alg:"none", and attacker-supplied-key (jwk / jku /
x5u) forgery vectors: the Verifier accepts only ES256 and resolves the verification key
solely from the enrolled record selected by kid.¶
S2. Attacker presenting a recorded user-verification artifact. Mitigated by contemporaneous gating (A2): the platform user-verification ceremony is required at the moment of signing. The Attester delegates user-verification to platform mechanisms; the attestation chain proves the gating is in place. Residual risk: bounded by the platform's user-verification anti-spoofing capability (e.g., presentation-attack detection in biometric subsystems, tamper resistance of hardware-token / smartcard readers, PIN-shoulder-surfing mitigations).¶
S3. Compromised host pre-minting a batch of proofs from a
single user-verification event. If the signing key were gated by a duration- or
window-based user-verification policy (one ceremony unlocking the key for an interval), a
compromised host could sign a stack of future proofs — each with a distinct jti, an
increasing psea_counter, and a distant exp — from one ceremony, and every such
proof would pass the wire checks. Mitigated by the per-operation gating requirement of
Section 3.7: each proof signature MUST be preceded by
its own fresh user-verification event, so the number of valid proofs is bounded by the number of
ceremonies and pre-minting is not reachable. This mitigation is subject to the same
attestation-conditionality as the psea_uv claim (Section 3.7.1):
the Verifier can confirm that the signing key is gated per operation (rather than under a
duration- or window-based policy that would permit pre-minting) only where the platform
attestation conveys the key's user-verification-gating configuration. Where the attestation
conveys it, the Verifier MUST reject a proof whose attested key properties show a
duration- or window-based gating policy, and pre-minting is attested-closed. Where the
attestation does not convey the gating configuration, per-operation gating rests on a documented
trust assumption in the Attester rather than an attested property, and a compromised host that
configures window-based authentication could pre-mint without the Verifier detecting it from the
attestation; for high-assurance operations a Verifier SHOULD reject or require an
additional independent factor in that case, as for psea_uv. Residual risk: a host that
subverts the platform user-verification subsystem itself (O1) is out of scope.¶
T1. Attacker modifying Evidence in transit. Mitigated by the signed body's ECDSA P-256 signature. Any tampering of the signed octets causes signature verification to fail and the Verifier to reject the proof.¶
T2. Attacker swapping action payloads between proof and request. Mitigated by the action-binding fail-closed check (Section 3.13.2): the Verifier recomputes the payload hash and rejects the proof on mismatch.¶
T3. Attacker re-using a captured proof against a different request. Mitigated by the monotonic counter (Section 3.10) and by the action-binding (T2). The counter prevents re-issuance with the same counter value; the action-binding prevents reuse of a proof against a different payload.¶
R1. Subject claims they did not approve an action. Mitigated by the device-bound
signature (binding to a specific physical device) plus the contemporaneous user-verification
ceremony (binding to a human at that moment). The per-proof psea_user_hash claim
(Section 3.8) provides an additional Attester commitment to subject
identifier for audit-trail purposes; it does not affect the cryptographic non-repudiability,
which rests on the device-bound key and the presence Evidence binding.¶
I1. User-verification template leakage. Out of scope (the user-verification template is platform-internal; PSEA does not transit or store it).¶
I2. Subject identifier leakage via wire fields. Reduced — not eliminated — by hashing: the wire carries SHA-256 hashes of subject identifiers, not the raw identifiers, so the raw value and the biometric template never transit. Hashing is not anonymization; an unsalted hash of a low-entropy identifier is reversible and linkable. See Section 7.2 for the treatment and the salt / pairwise-derivation mitigations.¶
I3. Wire-protocol leak via verbose error responses. Mitigated by constraining rejection responses to a fixed set of abstract outcomes without leaking deployment-internal diagnostic detail to the Attester.¶
D1. Rate-flooding of Verifier endpoints. Mitigated by per-deployment rate limiting.¶
D2. Quota exhaustion of Verifier processing. Mitigated by per-deployment rate limiting and by implementation-specific quota controls (an exhausted-quota rejection is out of scope for this protocol and is not assigned a PSEA wire response code).¶
E1. Attester signing a proof while it should be locally untrusted. Mitigated by
the authoritative server-side enrollment lifecycle the Verifier maintains, specified normatively
in Section 3.14: the Verifier rejects any proof whose enrollment is not
in the active state. This server-side enrollment-status / revocation check is the trust
gate, so a misbehaving or compromised Attester that signs a proof it should have withheld cannot
bypass a suspended or revoked state. This profile carries no self-asserted wire trust-state
field; the Verifier's enrollment record, not any Attester self-assertion, is authoritative.¶
E2. Attester emitting proofs after local-state revocation. Mitigated by the Verifier's enrollment lifecycle: even if a misbehaving Attester continues to emit proofs after local revocation, the Verifier rejects them.¶
The replay defenses of Section 3.10 — the strictly-increasing
psea_counter compared-and-advanced atomically per (Attester, counter scope), and the
global jti uniqueness check that finalizes each action identifier exactly once — are
stated as logical, single-domain guarantees. A horizontally-scaled (sharded) Verifier deployment
and the durability of the Verifier's state both bear on whether those guarantees actually hold, and
neither is closed by the wire format alone.¶
Sharding / distributed serialization. The atomic compare-and-advance of
Section 3.10 assumes a single transactional domain per (Attester, counter scope),
and the jti uniqueness check assumes a globally consistent index. A deployment that spreads
verification across multiple nodes MUST preserve these assumptions: all submissions
for a given (Attester, counter scope) MUST be serialized to a single authority (for
example by routing or partitioning on the Attester identity, or by a shared transactional store
that serializes per scope), and the jti finalization index MUST be globally
consistent across all nodes that can accept proofs for the deployment. A design in which two nodes
can independently advance the same scope's counter, or independently finalize the same
jti, without a serializing authority is non-conformant: it reopens the
concurrent-double-accept and counter-regression surfaces that the atomicity requirement of
Section 3.10 exists to close, this time at the storage tier rather than at the
single-node transaction.¶
Rollback / durability. Section 3.10 prevents an Attester from
regressing its counter; the symmetric risk is the Verifier regressing its own stored high-water
mark and jti set. If the Verifier's counter / jti state is restored from an older
backup or otherwise rolled back, counter values and jti values consumed since that backup
become acceptable again, reopening the replay surface for proofs still within their exp
window. A conforming Verifier MUST protect its replay state (the per-scope
high-water-mark counters and the finalized-jti set) against rollback, such that a restore
or failover does not lower a stored high-water mark or forget a finalized jti that could
still be replayed within the exp window. This is the server-side mirror of the client-side
regression the counter model forbids; durability of the replay state is part of the replay defense,
not an operational afterthought.¶
The current protocol provides cryptographic action-binding between the Attester and the Verifier. A future extension MAY introduce a Relying-Party counter-signature primitive in which the Relying Party countersigns the Attestation Result, producing a three-party signed artifact that binds the action approval to (a) the Subject (via the Attester's hardware key + user- verification), (b) the Verifier's appraisal, and (c) the Relying Party's policy acceptance.¶
This three-party binding would close the residual risk that a compromised Relying Party accepts an Attestation Result it should have rejected on policy grounds. The wire-level primitive is informative-only at this revision; a normative specification is deferred to a future document.¶
The following threats are acknowledged but are out of scope of the wire protocol:¶
The following are attack surfaces that PSEA does not solve and that remain unsolved at the authenticator/platform layer at the time of writing. They are documented so that deployers and reviewers can assess residual risk in their own deployment context, and each is a candidate for future research and protocol evolution.¶
An attacker physically present with the legitimate subject can compel the subject to perform the user-verification ceremony. The resulting proof is cryptographically indistinguishable from a freely-given approval: the signed body shows the correct hardware key, the contemporaneous user-verification event happened, and the action payload binds correctly.¶
Why this is hard for any authenticator: the platform user-verification APIs do not expose which finger was used, which face geometry matched, which PIN was entered, or whether the subject appeared distressed. The Attester sees only a binary "verification succeeded." A duress-code scheme (e.g., a specific finger reserved for duress, a specific PIN suffix) would require either (a) platform-level API changes to surface which credential was used so the Attester can interpret it, or (b) the application binding distinct credentials to distinct server-side meanings — but on face-based verification this is effectively impossible because there is no way to enroll a "duress face." This remains an open problem; deployments that require duress detection today rely on out-of-band mechanisms (transaction-monitoring rules, behavioral analytics, secondary channels) that lie outside this protocol.¶
The property PSEA guarantees, for every PSEA proof, is action-binding:
what is signed is what the Verifier executes. The fail-closed action-binding check
(Section 3.13.2) ties the signed proof to the exact action payload
via psea_payload_hash, so the signature-to-execution binding is cryptographically
enforced and a captured proof cannot be replayed against a different payload.¶
What PSEA does not guarantee on a compromised device is the eye-to-signature binding — that what the human actually saw on screen at the moment of the user-verification ceremony is identical to what was signed. A compromised UI layer or a malicious overlay can display "Approve $10 transfer to Alice" while the underlying action payload submitted to the Verifier is "$10,000 transfer to Mallory." The hardware key signs the payload it was given, not the pixels the human saw. PSEA binds signature to execution; it does not, by itself, bind the human's perception to either.¶
It is important to state the degraded-mode value plainly rather than leave it implied. On a fully compromised display — or, equivalently, where a compromised Relying Party or in-path modifier substitutes a plausible-but-tampered payload that the human did not intend — PSEA does not prevent execution of the unintended action: the tampered payload is signed by the genuine hardware key under a genuine user-verification event, so the action-binding check passes on it. What PSEA retains in that case is forensic / non-repudiation value: the signed proof is durable, hardware-anchored evidence of exactly what was signed (and that a user-verification event gated the signing), establishing after the fact what the device actually committed to. PSEA's contribution on a compromised display/RP is therefore non-repudiation, not prevention; prevention of the eye-to-signature mismatch requires the trusted-display path below, which is out of scope.¶
Why this is hard for any authenticator: full WYSIWYS requires a trusted display path the application cannot influence — pixels rendered by a trusted execution path (for example a Trusted Execution Environment), with the user-verification ceremony tied to a render-of-record that the platform attests to. Some platforms have partial support (e.g., trusted-UI on certain TEEs), but the API surface for application developers to express "render THIS exact content in trusted UI before signing" is not generally available across authenticator platforms in a way that survives platform UI redesigns. Trusted-display / secure-UI is out of scope of this specification (it is platform-dependent and not universally available) and remains the open problem.¶
Mitigation for high-consequence actions: for high-consequence operations, deployments SHOULD use a server-signed confirmation round-trip. In this RECOMMENDED pattern the Verifier signs the canonical action it is about to execute and returns it to the authenticator for display and a user-verification step before the action commits, giving the signed content an independent, server-anchored origin rather than one chosen entirely by a potentially-compromised client UI. This is a SHOULD / RECOMMENDED measure for high-consequence operations, not a universal MUST, and it reduces but does not eliminate the residual risk on a fully compromised display.¶
Out-of-band channels vulnerable to SIM-swap or one-time-passcode interception (for example, SMS OTP) are NOT RECOMMENDED as a WYSIWYS mitigation: they reintroduce exactly the channel-interception and credential-replay weaknesses PSEA exists to eliminate, and a deployment that layers them on does not obtain a trusted-display guarantee. Trusted display remains an open problem.¶
PSEA proves a human was present and approved this action. It does not identify
which human, beyond the deployment-issued subject identifier the Attester commits
to (psea_user_hash — an opaque commitment, not a cryptographic identity binding).
Subject identity attribution — KYC, anti-money-laundering, multi-user-on-one-device
scenarios — is the responsibility of the deployment's enrollment ceremony and out-of-band
identity proofing. A device legitimately enrolled to user A whose user-verification
credential is then re-enrolled to user B will produce proofs that the platform attests
contemporaneously gated by user B's verification; PSEA cannot detect this. Deployments
requiring strong subject identity binding rely on enrollment-time mechanisms (in-person
ceremony, document verification, etc.) outside the wire protocol.¶
Nothing in a PSEA proof establishes that the signature preceded the effect it authorizes. A proof minted in ratification of an action that has already completed is structurally identical to one minted before that action began: the same claim set, the same action binding, the same contemporaneous user-verification event, the same hardware key. A Verifier appraising the artifact alone cannot separate the two cases, and neither can a party auditing the artifact afterwards.¶
The criterion this would require is not a property of the artifact. Ordering evidence has to come from a boundary that observed both the authorization and the effect, or from two records whose producing boundaries can be related to one another. Authenticating the artifact is not that criterion: a timestamp minted beside the signature observes only the signature, and establishes when the signature existed rather than what had already happened when it was produced. This is why the gap is not closed by strengthening the proof, and why it is recorded here rather than treated as a deficiency in the claim set.¶
No ordering input is defined at this revision. A composition or appraisal boundary that requires authorization to be shown to precede execution therefore treats the disposition for a PSEA proof as UNSATISFIED rather than INDETERMINATE: the profile does not define an input that happens to be unavailable in a given deployment, it defines none. Deployments that need the property today obtain it from the surrounding system — an execution boundary that records both events, or an independently anchored log that orders them — rather than from this profile.¶
This section follows the guidance of [RFC6973]. PSEA Evidence is, by design, a
device-bound and (via the user-verification gate) human-presence-bearing artifact; that same
binding creates privacy exposure that deployers MUST understand. The principal
concerns are the linkability of the stable per-device identifier (ueid) and the common
but incorrect assumption that hashing an identifier anonymizes it.¶
The ueid claim (Section 3.2) is, within one deployment, a stable
per-device identifier: it is the same value across every action a given device attests to a given
issuer (iss). A Verifier therefore can, and by construction does, link all of a given
device's proofs to one another within its deployment. This intra-deployment linkability is an
inherent property of a device-bound attestation identifier, not a defect; this profile
depends on the Verifier being able to bind successive proofs to one enrollment.¶
To prevent the same physical device from being correlated across deployments, the
ueid MUST be derived pairwise per issuer: the UEID byte string is
0x01 || SHA-256(JCS({"deviceId": <deviceId>, "iss": <iss>}))
(Section 3.2), where JCS() denotes the JSON Canonicalization Scheme
([RFC8785]) form of that two-member JSON object, taken under the canonical-encoding
rules of Section 2, so the same device
yields a different ueid for each distinct iss. The two inputs are encoded as a
canonical JSON object rather than concatenated because a bare concatenation of two
variable-length strings is ambiguous: the pairs ("acme", "corp.example") and
("acmecorp", ".example") concatenate to identical octets and would derive one
ueid for two distinct deployments, defeating the very separation the pairwise derivation
exists to provide. Because iss is itself a
signed claim bound into the proof, the derivation is verifiable: a Verifier recomputes the
expected ueid for its own iss from the enrolled device-id input. The privacy
rationale is that a single, globally-shared device identifier would let independent Relying
Parties and deployments correlate one person's device activity across unrelated services; binding
the identifier to iss confines linkability to the single deployment that legitimately
requires it.¶
This profile carries a deterministic per-issuer value under the RFC 9711 RAND type tag
(0x01). [RFC9711], Section 4.2.1.1 requires that a UEID be permanent and
not change for a given entity. The value this profile carries does not meet that requirement: it
is derived from a deviceId input that is wiped on factory reset and re-derived
afterwards, as happens both to a platform-provided device identifier and to an identifier
generated and stored on the device, so the same entity presents a new value after a factory
reset. [RFC9711], Section 4.2.2 defines the semipermanent UEID (sueids)
claim for this case: a value with the format of a UEID that may change on entity life-cycle
events, of which factory reset is one. sueids is the correct claim for the value this
profile carries. This revision retains ueid for wire continuity only.¶
The sueids claim is a map, not an array: each entry pairs a text-string label with a
value in the UEID format, and a label is required even when only one entry is present
([RFC9711], Section 4.2.2). This profile defines no means for a device owner to
add an identifier: the Attester emits exactly one device identifier, derived from its
deviceId input and the signed iss.¶
Migrating the identifier to sueids is a wire-incompatible change. It is the stated
intent for the next wire-incompatible revision of this profile, which will also define the label.
Until then the ueid value is a per-issuer pseudonym for the current enrollment:
deterministic by construction, not random, and not preserved across factory reset.¶
Per-issuer derivation does not provide per-Relying-Party unlinkability within a single
deployment, nor does it defeat correlation by other shared fields. A future revision
MAY define a pairwise / sueids-style per-Relying-Party identifier
derivation (in the spirit of the EAT semipermanent-UEID concept, [RFC9711]) so that
one device presents a distinct, unlinkable identifier to each Relying Party within a deployment;
this is the path to per-RP unlinkability and is RECOMMENDED as the direction for deployments with
strong unlinkability requirements.¶
The same "hashing is not anonymization" caution that applies to psea_user_hash
(Section 7.2) applies to the deviceId input of the ueid
derivation. If deviceId is a low-entropy or enumerable value (for example a hardware
serial number or other guessable platform identifier), then the derived ueid is
recoverable by brute force from a known iss, defeating the confinement the per-issuer
derivation is meant to provide; encoding the inputs unambiguously prevents one derived
ueid from standing for two input pairs, and does not raise the entropy of either input.
A deployment SHOULD use a high-entropy,
per-enrollment random value as deviceId (a value generated at enrollment and not derived
from a stable hardware identifier), so that the derived ueid is not recoverable from
guessable inputs and is not a globally stable value computable by any other party.¶
PSEA wire fields carry SHA-256 hashes of identifiers (the device-id hash inside ueid,
and the OPTIONAL psea_user_hash subject hash) rather than the identifiers themselves.
Hashing reduces casual exposure of the raw value, but it MUST NOT be relied upon
as a privacy or anonymization guarantee. An unsalted hash of a
low-entropy identifier — a phone number, an account number, an email address, a
national ID, or any identifier drawn from a small or enumerable space — is reversible by
brute-force or dictionary attack: an adversary who can compute the same hash function over
candidate inputs recovers the input, and two systems that hash the same identifier produce the
same digest, making the hash a stable, globally linkable identifier in its own right.¶
Where linkability or re-identification of the hashed identifier matters, a deployment SHOULD either (a) salt or pepper the hash input with a per-deployment secret that is not published with the proofs, so the digest is not a globally stable value computable by any other party, or (b) derive a pairwise identifier per Relying Party (see Section 7.1), so the hashed value is not a single stable identifier shared across contexts. A deployment MUST NOT treat a bare SHA-256 of a user or device identifier as sufficient to claim the wire field is non-identifying. The minimization PSEA does provide is that the raw identifier and the biometric template never transit the wire (Section 6.4.4); that is a reduction in exposure, not anonymization.¶
The Verifier and Relying Party necessarily retain proof records to serve PSEA's audit-trail
purpose, and those records are linkable per Section 7.1. Deployments
SHOULD apply retention limits, access controls, and purpose limitation to stored
proofs and Attestation Results consistent with their regulatory environment, and
SHOULD NOT retain the OPTIONAL psea_user_hash beyond the audit-trail need
it serves. Correlation of proofs with out-of-band identity (which natural person a
ueid or psea_user_hash corresponds to) is established only at enrollment and is
a deployment responsibility outside the wire protocol.¶
This document requests IANA to register one media type:¶
This document requests no URN sub-namespace and no registration of any kind for its
eat_profile identifier. The eat_profile claim carries a URI or an OID and
[RFC9711] defines no registry for profile identifiers, so the identifier this
profile uses — the HTTPS URI https://datatracker.ietf.org/doc/draft-yossif-psea/
(Section 3.1) — requires no registration action and does not depend on the stream
this document is published on.¶
This document does not request registration of the psea_* claims in the JSON Web Token
Claims registry ([RFC7519], Section 10.1) or in the CBOR Web Token (CWT) Claims
registry. The psea_* claims are profile-private extension claims whose semantics are
meaningful only to a consumer that understands this eat_profile
(Section 3.1); their names are scoped by the eat_profile claim rather
than by a global registry, consistent with the strict, profile-scoped extensibility model of this
document (Section 3.1.1). A future revision MAY register
one or more psea_* claims in the JWT Claims registry should cross-profile reuse warrant a
globally-managed name; this revision intentionally does not, to avoid reserving global names for
semantics that are defined only relative to this profile.¶
This document requests registration of the media type application/psea+jwt in the
"Media Types" registry, following the procedures of [RFC6838] and using the
registered +jwt structured syntax suffix ([RFC7519]). The subtype carries
the PSEA proof defined by this profile (protected-header typ = "psea-proof+jwt") per
Section 3. The media type application/psea+jwt labels the object
at the transport layer, and a consumer expecting a proof MUST verify the
protected-header typ value (Section 3.4). The Verifier acknowledgement
/ Attestation Result is out of scope of this profile (Section 3.13.3); a
deployment that represents that artifact as a JWS MAY reuse this media type with a
distinct protected-header typ value (for example "psea-ack+jwt"), applying the
explicit-typing guidance of [RFC8725], Section 3.11 so the two object types are not
confused, but this document does not define that artifact.¶
alg:"none"; ignore jwk/jku/x5u; verify typ;
reject unknown crit parameters and the unencoded-payload option).¶
A CWT/COSE representation ([RFC9052]) carrying the same EAT
([RFC9711]) and psea_* claim set is future work; this revision does not
register a +cose media type.¶
Conforming Attesters and Verifiers MUST implement:¶
This revision does not specify a post-quantum signature suite. A future revision
MAY add a ML-DSA-based suite ([FIPS204]) as an alternate or
successor signature algorithm. The PSEA proof and the Verifier acknowledgement are JWS objects
that carry the signature algorithm in the JWS protected alg header (ES256 is the
mandatory-to-implement algorithm at this revision) and a protected kid header that
provides the key-selection and key-rotation story; no PSEA JWS uses a fixed
out-of-header algorithm. A dedicated rejection outcome for an unsupported algorithm is reserved for a future revision that
admits more than one signature algorithm.¶
Algorithm negotiation between Attester and Verifier is out of scope at this revision; the MTI algorithm is the only conforming choice. A future revision MAY introduce negotiation via a Verifier-published algorithm preference list at a deployment-specific well-known URL.¶
The following operational defaults are RECOMMENDED for conforming deployments:¶
exp - iat)This appendix provides test vectors for verifying canonical-encoding implementations. An implementation that produces output matching every vector in this appendix is conformant with Section 2.¶
Implementations SHOULD construct their own canonical-encoding test suite covering the
canonical-JSON inputs that PSEA hashes (the action payload bound by psea_payload_hash and
the two-member object of the ueid derivation) plus
boundary cases for case-sensitive sort, mixed-case keys, integer serialization, escape forms, and
UTF-8 multi-byte sequences. The PSEA proof and the acknowledgement are JWS objects verified over
their received octets and so do not depend on canonical encoding for signature verification; the
two hashes taken over canonical JSON octets are the action-payload hash and the ueid
derivation (Section 2). Implementations
SHOULD maintain machine-readable golden vectors covering these dimensions;
consulting or publishing them is informative and not a normative requirement of this
specification.¶
A representative subset, demonstrating the case-sensitive sort rule of Section 2.2, is reproduced here:¶
Input object (logical):¶
{ "endReason": "TtlExpired",
"endedAt": 1700000060,
"sessionId": "abc-123",
"startedAt": 1700000000 }
¶
Canonical bytes (the keys ordered by UTF-16 code-unit sort: 'R' = U+0052 = 82 sorts before 'e' = U+0065 = 101, so "endReason" precedes "endedAt"), shown with one line break added for display only; the canonical form contains no line break:¶
{"endReason":"TtlExpired","endedAt":1700000060,
"sessionId":"abc-123","startedAt":1700000000}
¶
The integer 0 is serialized as the single character 0; the integer 42 as 42;
the integer 1700000000000 as 1700000000000; no leading zeros, no decimal point, no
exponent.¶
This vector demonstrates the first of the two normative uses of canonical encoding
(Section 2): the action-payload binding. Given the logical
actionPayload:¶
{ "amount": 2500, "actionType": "transfer",
"to": "alice", "currency": "EUR" }
¶
the canonical encoding orders the keys by UTF-16 code-unit sort
(actionType < amount < currency < to), removes all
inter-token whitespace, and serializes the integer with no decimal point or exponent, yielding the
69 canonical octets:¶
{"actionType":"transfer","amount":2500,"currency":"EUR","to":"alice"}
¶
SHA-256 of those octets is, in lowercase hex:¶
f0f8eb390ecdb3b312765cfe3a888c39ad4571bb94ddfc553230a4b85171e942¶
The psea_payload_hash claim is that digest in standard base64 with padding
(Section 3.2):¶
8PjrOQ7Ns7MSdlz+OoiMOa1FcbuU3fxVMjCkuFFx6UI=¶
A Verifier re-canonicalizes the received actionPayload, recomputes this digest, and
compares it byte-for-byte against the signed psea_payload_hash
(Section 3.13.2). The same 32-byte digest encoded as base64url without
padding — the encoding used by ueid and psea_user_hash, not by
psea_payload_hash — is 8PjrOQ7Ns7MSdlz-OoiMOa1FcbuU3fxVMjCkuFFx6UI; the
difference (+ versus -, trailing = versus none) illustrates why the
per-claim encoding split of Section 3.2 must be observed exactly.¶
This appendix specifies conformance requirements for Attester, Verifier, and Relying Party implementations.¶
This document defines conformance for three roles: Attester, Verifier, Relying Party.¶
A conforming Attester:¶
psea_uv) claim — binding exactly one user-verification
event to exactly one proof signature, never a duration- or window-based authorization
(Section 3.7) — and MUST compute psea_payload_hash over
the canonical action payload per Section 3.13.1.¶
A conforming Verifier MUST perform every check below. The list is the authoritative enumeration of the Verifier obligations stated normatively in the body; an implementation that omits any item is non-conforming. Where ordering matters it is stated; in all cases the signature MUST be verified before any claim is trusted, and all binding/freshness checks MUST complete before any state mutation (counter advance, ledger write, acknowledgement signing).¶
alg = "ES256", reject alg = "none",
ignore any jwk / jku / x5u header key material, resolve the
verification key only from the enrolled record selected by kid, verify the
protected-header typ equals "psea-proof+jwt", and reject any unrecognized
crit header parameter or an unencoded-payload ([RFC7797]
b64:false) header.¶
eat_profile claim is absent or is not
https://datatracker.ietf.org/doc/draft-yossif-psea/ (Section 3.1),
except as permitted in Section 3.5, and a proof whose
psea_proof_version is an unknown value (Section 3.15).¶
exp
is in the past or whose iat is implausibly in the future, applying only a small
bounded clock-skew tolerance (SHOULD NOT exceed 60 seconds) and a bounded maximum proof
lifetime (Section 9.2).¶
eat_nonce is absent or does
not equal the issued value (Section 3.1.1), and MUST correlate the
proof to the issued challenge using only the signed eat_nonce value, never an unsigned
transport-body field (Section 3.11).¶
psea_uv.verified == true and reject otherwise, and MUST anchor the
psea_uv claim to the authenticator's attested UV-enforcement where the attestation
conveys it; where it cannot, MUST NOT treat human presence as attested and SHOULD reject for
high-assurance operations (Section 3.7.1).¶
actionPayload, hash it
with SHA-256, byte-compare against psea_payload_hash, and reject on mismatch or on a
missing payload, before producing any approval-bearing response. This check MUST precede the
counter comparison.¶
psea_tier, psea_op, aud, iss) by case- and
whitespace-exact comparison, after signature verification and before any state mutation.¶
psea_caller_package is absent or does not byte-exactly
equal the expected value, before any state mutation (Section 3.13.5); MUST
refuse a proof that omits psea_caller_package where the deployment has registered
caller identities, and SHOULD refuse it otherwise.¶
psea_user_hash match the value recorded for the subject at
enrollment; a Verifier that does so MUST refuse a proof whose psea_user_hash is absent
or does not byte-exactly equal the recorded value (Section 3.8).¶
psea_counter (Section 3.10): select
the comparison bucket using only a cryptographically bound scope identifier, reject a value
not strictly greater than the stored value for that scope, and treat the claim as a 64-bit
unsigned integer.¶
jti uniqueness check (Section 3.10),
finalizing each jti exactly once and retaining finalized jti values for at
least the exp validity window.¶
jti finalization index
globally consistent across all nodes, so that the counter atomicity and jti
uniqueness guarantees hold under sharding (Section 6.5).¶
jti
set) against rollback, so a backup restore or failover does not lower a stored high-water mark
or forget a finalized jti still replayable within the exp window
(Section 6.5).¶
psea_chain_prev linkage check of
Section 3.12.3; a Verifier that does not enable the chain layer
is not required to implement it.¶
psea_chain_prev or psea_signals_hash
on every proof, refuse a proof that omits the required claim (Section 3.5).¶
requestId
or proofId) as an input to any security decision
(Section 3.6); signalReport is such a field unless the proof
carries psea_signals_hash and the Verifier has appraised the report against that
claim, in which case it is a committed field rather than an unsigned one.¶
A conforming Relying Party:¶
This revision states one known limitation, corrects four claim-set descriptions against the
reference implementation, corrects the ueid derivation, withdraws the URN sub-namespace
registration request, and corrects the restatement of RFC 8785. It also changes normative Verifier
requirements in three places: a Verifier may act on psea_user_hash against the enrollment
record (Section 3.8), a deployment may require psea_chain_prev and
psea_signals_hash (Section 3.5), and an absent
psea_caller_package is refused (Section 3.13.5). Each change is listed
below. The Section 3.6 prohibition on using unsigned transport-body fields as
security-decision inputs retains its force; the only edits to it are to the illustrative lists
that named a field incorrectly.¶
The ueid change is a wire-format correction, not an editorial one. A
conforming Attester built against -02 and one built against this revision derive different
ueid values from the same deviceId and iss, so an implementation and any
enrolled record that stores or recomputes a ueid must be updated together. The defect was
found in review by Thomas Fossati on the IETF RATS mailing list.¶
psea_signals_hash claim in the claim-set schema
(Section 3.5). The claim was already produced by Attesters and appraised by
Verifiers in the reference implementation; under additionalProperties: false its absence
from the schema meant a deployment following the reference implementation emitted proofs the
published schema rejected.¶
psea_rp_context_hash (Section 3.5),
which was grouped with the diagnostic extension members as carrying no Verifier appraisal
obligation and being ignorable. Where a deployment binds a relying-party request context, the
claim carries that binding and is appraised.¶
signalReport unconditionally among the fields that confer no authority. Where
psea_signals_hash commits to the report and a Verifier has appraised it against that
claim, the report is a committed field, as actionPayload is under
psea_payload_hash. The prohibition itself is unchanged; only the enumeration of which
fields it reaches is corrected.¶
ueid derivation (Section 3.2,
Section 3.5, Section 7.1). Through -02 the derivation was
0x01 || SHA-256(deviceId || iss), a bare concatenation of two variable-length strings and
therefore ambiguous: ("acme", "corp.example") and ("acmecorp", ".example") hash
identically, so two distinct deployments could share one ueid — the opposite of what the
pairwise derivation exists to achieve. The two inputs are now encoded as a canonical JSON object
([RFC8785]) before hashing, reusing the canonicalization this profile already
requires (Section 2) rather than introducing a second one. The pairwise
approach itself is unchanged. It achieves per-deployment separation; it is not the strategy of
[RFC9711], Section 8.1, whose precondition, that the entity authenticate the relying
party before deriving an identifier for it, this profile does not meet, since iss is the
identifier the Attester has locally available rather than one the relying party has proven.¶
urn:ietf:params:psea URN sub-namespace, and
removed with it the fallback profile identifier written for the case where that registration was
not granted (Section 8.1). The urn:ietf:params policy would
have made the profile identifier depend on an IETF-stream publication decision, whereas EAT
deliberately requires no registration for profile identifiers. The eat_profile value is
now the HTTPS URI https://datatracker.ietf.org/doc/draft-yossif-psea/
(Section 3.1). With no registration requested there is no non-grant case and
nothing to fall back from, so the fallback clause is removed rather than restated. This changes the
eat_profile value on the wire; Attester and Verifier must be updated together. Withdrawing
the request was recommended in review by Thomas Fossati on the IETF RATS mailing list.¶
eat_profile claim is Section 4.3.2, not Section 4.3.4 (four
occurrences: Section 3.1, the Profile identifier entry and the
claim-tree annotation, and the claim-set schema). EAT profiles are Section 6, not Section 7
(three occurrences, including the heading of Section 3.1.1); Section 7
of [RFC9711] is "Encoding and Collected CDDL" and is unrelated. The publication
date in the [RFC9711] reference entry is also corrected to April 2025. No
requirement is affected: every corrected citation is a pointer, and the normative text it
accompanies is unchanged.¶
ueid claim and does not mention the RAND type at all, so a reader checking the
RAND-tag rationale of Section 7.1 against Section 4.2.1 would not find it
there. The citation of Section 4.2.1 in Section 3.1 is for the ueid
claim itself and is unchanged.¶
psea_user_hash to match the value recorded at enrollment and,
where it does, MUST refuse an absent or differing value; -02 prohibited any
authorization decision based on the claim. The claim is stated as a pairwise, deployment-local
commitment that establishes no identity, because a deployment that records a subject at
enrollment can then detect a proof produced for a different subject identifier.¶
psea_chain_prev or psea_signals_hash and
that its Verifier MUST refuse a proof omitting the required claim; both remain
OPTIONAL in the schema, because omission would bypass the chain linkage check or leave the
auxiliary document unsigned.¶
psea_caller_package where the deployment has
registered caller identities and SHOULD refuse it otherwise, which is stricter
than -02, because an absent claim would otherwise bypass the caller binding.¶
sueids is the correct claim for it;
ueid is retained for wire continuity only, the description of sueids as an array
is corrected to a labelled map, and migration to sueids is the stated intent for the next
wire-incompatible revision. No wire change.¶
ueid derivation, rather than one, and
corrected the two sentences of Appendix A that still named
one use. Removed the RFC 3553 and RFC 8126 references, which were cited only by the withdrawn URN
sub-namespace registration.¶
eat_profile value urn:ietf:params:psea:eat-profile:2 when every other
requirement of this profile is met, and qualified the matching rejection rules in
Section 3.1 and Appendix B.3. In deployed
implementations the :2 suffix marked the corrected ueid derivation;
psea_proof_version remains the wire-format revision marker
(Section 3.15). No claim is added.¶
psea_stepup_selection claim in the claim-set schema
(Section 3.5). As with psea_signals_hash, the claim was already
produced by Attesters in the reference implementation when a deployment issues a step-up, and
under additionalProperties: false its absence from the schema meant such proofs were
rejected by the published schema. Its construction is deployment-defined and out of scope. No
wire change.¶
urn:ietf:params:psea:eat-profile:2 is
not a registered URN and that this document requests no registration for it.¶
Thomas Fossati reviewed this profile on the IETF RATS mailing list and found the
ueid derivation ambiguity and the unnecessary URN registration request, both corrected in
this revision. Laurence Lundblade and Giridhar Mandyam answered which UEID claim fits a value that
does not survive factory reset. Mikhail Sergeev supplied the criterion for ordering evidence stated
in Section 6.8.4. Iman Schrock found schema-validation defects in the reference
Verifier. Songbo Bu wrote an independent Verifier and cross-ran the conformance suite against it,
and Lars Kroehl independently recomputed the canonical encoding. Any remaining errors are the
author's.¶