[Secdispatch] Re: [saag] Re: Re: draft-yossif-psea-0 2 posted — re-scoped as an EAT token profile (diff)
Iman Schrock <[email protected]> Thu, 11 Jun 2026 01:07:17 -0700
| Newsgroups | gmane.ietf.secdispatch,gmane.ietf.saag |
|---|---|
| Message-ID | <CAOfgHgqv3oaS591iFVJ601qCiKzx-j-tNdH1m=u7G2hA2QQE9g@mail.gmail.com> |
--===============1484852092391133973== Content-Type: multipart/alternative; boundary="000000000000eec2d70653f5d961" --000000000000eec2d70653f5d961 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Songbo =E2=80=94 Thank you =E2=80=94 running the vectors yourself is the validation that mat= ters; reproduction beats assertion, and I'm glad Node and Web agreed on your machine too. Agreed completely: the offline / server-state split is a conformance boundary, not an implementation detail, so it gets a first-class row in the matrix rather than a footnote. Your phrasing of the invariant =E2=80=94 *re= ject before any approval-bearing state mutation* =E2=80=94 is the right one to s= tate once in the core section and inherit per profile. And it maps to PSEA exactly as you put it: the signed proof can carry or bind the jti / counter / freshness material, but the high-water-mark decision is verifier state on both sides. That convergence =E2=80=94 one invariant, two attestation tiers, enforced i= n the same place =E2=80=94 is what makes the dispatch question concrete rather th= an asserted. Send the off-list nits whenever; I'll fold them in with the skeleton. Iman, On Thu, Jun 11, 2026 at 12:46=E2=80=AFAM Songbo Bu <[email protected]> w= rote: > Hi Iman, > > Thanks, this split is useful. > > I took a quick look at the new signoff vectors and ran the signoff > conformance runner. The current shape matches the verifier boundary > you describe: 9 vectors, 8 reject cases, all at the offline assertion > layer, with the Node and Web Crypto verifiers agreeing. > > One thing I would make explicit in the survey matrix is that the > offline/server-state split is not just an implementation detail; it is > a conformance boundary: > > - verifier-state boundary: an offline assertion verifier can reject > structural, cryptographic, action-binding, audience, and UV failures; > replay / one-time consumption, enrollment-active, and revocation > freshness require server-state or ledger-side evaluation. > > That maps cleanly to PSEA too. The signed proof can carry or bind the > jti/counter/freshness material, but the high-water mark decision is > still verifier state. So I would treat "reject before approval-bearing > state mutation" as the common invariant across both profiles. > > I'll take a closer pass over the actual vectors and send concrete nits > off-list once the survey skeleton lands. > > Best, > Songbo Bu > > On Thu, 11 Jun 2026 00:38:02 -0700, Iman Schrock <[email protected]> > wrote: > > Songbo =E2=80=94 > > > > The processing-model + negative-test-table framing is the > > right call, and the decision-matrix axes you list are exactly the > comparison > > I'd want too =E2=80=94 formats will differ, the verifier decision shoul= dn't. > > > > Two concrete things from my side: > > > > - I just > > published EP's Class-A signoff negative vectors as a tagged battery, > > grouped on your failure classes =E2=80=94 structural, cryptographic, > > action-binding, operation/audience, lifecycle/UV =E2=80=94 and run thro= ugh two > > independent verifiers. It's a starting table for the pass you offered; > > please shred it. One honest scope line I drew, which I think matters fo= r > > the survey: I split OFFLINE assertion checks (crypto, action- binding, > > audience, UV) from SERVER-STATE checks (replay / one-time consumption = =E2=80=94 > > the nonce is our global consumption key =E2=80=94 and enrollment-active= ). A > > relying party verifying an EP receipt offline gets the former; the latt= er > > live at the consume/ledger step. PSEA's counter/jti high-water-mark > > invariant is the same idea on your side. > > > > - Here's > > EP filled into your matrix, as a strawman for the joint note: > > > > - action > > canonicalization/hash: JCS (RFC 8785) of the action object =E2=86=92 SH= A-256 > > context hash; non-canonical input MUST be rejected. > > > > - approver/subject > > binding: device-bound approver key (Class A, WebAuthn P-256); approver_= id > > resolved via a signed Approver Directory. > > > > - operation/audience > > binding: action_hash + policy ref + initiator in the signed context; > > WebAuthn rp_id for the relying party. > > > > - replay > > key: 128-bit CSPRNG nonce, globally unique =E2=80=94 it IS the consumpt= ion key. > > > > - one-time > > state: consumed at most once, globally; replay across > > sessions/operators/time terminally refused. > > > > - trust > > root: the signed Approver Directory; MUST NOT default to the EP operato= r. > > > > - relying-party > > result: the offline-verifiable Trust Receipt (signed authorization + > > consumption record + Merkle anchor). > > > > - enforcement > > class: Class A device-bound (recommended) vs Class C operator-custodied > > (legacy, weaker); execution-side enforcement is the strongest deploymen= t > > class. > > > > - residual > > risk: presentation attack / WYSIWYS, and key-revocation timing =E2=80= =94 both > > stated, neither claimed solved. > > > > The two profiles land on different rows only at assurance > > tier (your dedicated-hardware attestation vs. our commodity > authenticator) and > > scope (we add enrollment + separation of duties). Same decision matrix. > > > > I'll fold this into the survey skeleton I'm roughing out and > > send the whole thing off-list for you and Mohamad to edit. > > > > On Thu, Jun 11, 2026 at 12:24=E2=80=AFAM Songbo Bu <[email protected]= m> wrote: > > > > Hi Mohamad, Iman, all, > > > > Thanks, Mohamad. And Iman, that is a useful data point. > > > > For PSEA, my first pass would be to make the verifier-side text more > > > > like a processing model, plus a small set of negative verifier cases. > > > > That seems more useful to implementers than adding another long > > > > requirements list. > > > > For the processing model, something along these lines: > > > > > On receipt of a proof submission, the Verifier treats the signed > proof, the submitted action payload, enrollment state, and replay state a= s > one atomic authorization decision. The Verifier first validates the signe= d > token and then uses only claims from the verified payload, together with > server-side enrollment and replay state, to evaluate the proof. Before an= y > counter advance, ledger write, acknowledgement signing, or success-bearin= g > Attestation Result, the Verifier canonicalizes the submitted actionPayloa= d, > recomputes the payload hash, and compares it byte-for-byte with the signe= d > psea_payload_hash. Any mismatch, missing required signed claim, stale > freshness value, replayed jti/counter, inactive enrollment state, or fail= ed > cross-replay binding is a non-approval outcome. > > > > I would then group failures into a small negative-test table rather > > > > than leave them spread across the document: > > > > - structural / cryptographic: wrong profile, unsupported alg, bad > > > > signature, unrecognized critical header; > > > > - action-binding: missing actionPayload, payload-hash mismatch, > > > > non-canonical payload, or psea_payload_hash encoding mismatch; > > > > - cross-replay: psea_tier, psea_op, aud, iss, or psea_caller_package > > > > not matching the expected operation context; > > > > - freshness / replay: expired proof, implausible iat, wrong eat_nonce, > > > > reused jti, or non-increasing counter; > > > > - lifecycle / trust gate: enrollment not active, revoked/suspended > > > > key, or user-verification evidence insufficient for the required > > > > assurance level; > > > > - optional chain layer: psea_chain_prev mismatch when that layer is > enabled. > > > > The key point I would make testable is that all of those failures have > > > > the same security effect: the verifier rejects before any state > > > > mutation that could make the proof look consumed or approved. In > > > > test-vector terms, a rejected proof should leave the counter > > > > high-water mark, finalized-jti set, chain entry, approval ledger, and > > > > approval-bearing Attestation Result unchanged or absent. > > > > For Iman's draft, I think the useful comparison is not whether the > > > > token formats are identical, but whether the verifier decision > > > > matrices line up: action canonicalization/hash, approver or subject > > > > binding, operation/audience binding, replay key, one-time state, trust > > > > root, relying-party result, enforcement class, and residual risk. I > > > > can take a pass over that next, especially around Class A signoff > > > > negative vectors and deployment-class claims. > > > > Best, > > > > Songbo Bu > > > > -- > > > > Iman Schrock > > Founder & CEO > > > > EMILIA Protocol | Trust Before High-Risk Action > > Eye warns. EP verifies. Signoff owns. > > > > [emiliaprotocol.ai](http://emiliaprotocol.ai) | GitHub > > [email protected] > --=20 Iman Schrock Founder & CEO EMILIA Protocol | Trust Before High-Risk Action Eye warns. EP verifies. Signoff owns. emiliaprotocol.ai | GitHub [email protected] --000000000000eec2d70653f5d961 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable <div dir=3D"ltr"><div style=3D"font-size:12pt;text-decoration-style:solid;d= irection:ltr;margin:0px;font-family:Aptos,Arial,Helvetica,sans-serif;color:= rgb(0,0,0)">Songbo =E2=80=94</div><div style=3D"font-size:12pt;text-decorat= ion-style:solid;margin:0px;font-family:Aptos,Arial,Helvetica,sans-serif;col= or:rgb(0,0,0)">Thank you =E2=80=94 running the vectors yourself is the vali= dation that matters; reproduction beats assertion, and I'm glad Node an= d Web agreed on your machine too.</div><div style=3D"font-size:12pt;text-de= coration-style:solid;margin:0px;font-family:Aptos,Arial,Helvetica,sans-seri= f;color:rgb(0,0,0)">Agreed completely: the offline / server-state split is = a conformance boundary, not an implementation detail, so it gets a first-cl= ass row in the matrix rather than a footnote. Your phrasing of the invarian= t =E2=80=94<span class=3D"gmail-Apple-converted-space">=C2=A0</span><i>reje= ct before any approval-bearing state mutation</i>=C2=A0=E2=80=94 is the rig= ht one to state once in the core section and inherit per profile. And it ma= ps to PSEA exactly as you put it: the signed proof can carry or bind the jt= i / counter / freshness material, but the high-water-mark decision is verif= ier state on both sides.</div><div style=3D"font-size:12pt;text-decoration-= style:solid;margin:0px;font-family:Aptos,Arial,Helvetica,sans-serif;color:r= gb(0,0,0)">That convergence =E2=80=94 one invariant, two attestation tiers,= enforced in the same place =E2=80=94 is what makes the dispatch question c= oncrete rather than asserted. Send the off-list nits whenever; I'll fol= d them in with the skeleton.</div><div style=3D"font-size:12pt;text-decorat= ion-style:solid;margin:0px;font-family:Aptos,Arial,Helvetica,sans-serif;col= or:rgb(0,0,0)">Iman,</div></div><br><div class=3D"gmail_quote gmail_quote_c= ontainer"><div dir=3D"ltr" class=3D"gmail_attr">On Thu, Jun 11, 2026 at 12:= 46=E2=80=AFAM Songbo Bu <<a href=3D"mailto:[email protected]">king347= [email protected]</a>> wrote:<br></div><blockquote class=3D"gmail_quote" sty= le=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);paddi= ng-left:1ex">Hi Iman,<br> <br> Thanks, this split is useful.<br> <br> I took a quick look at the new signoff vectors and ran the signoff<br> conformance runner. The current shape matches the verifier boundary<br> you describe: 9 vectors, 8 reject cases, all at the offline assertion<br> layer, with the Node and Web Crypto verifiers agreeing.<br> <br> One thing I would make explicit in the survey matrix is that the<br> offline/server-state split is not just an implementation detail; it is<br> a conformance boundary:<br> <br> - verifier-state boundary: an offline assertion verifier can reject<br> structural, cryptographic, action-binding, audience, and UV failures;<br> replay / one-time consumption, enrollment-active, and revocation<br> freshness require server-state or ledger-side evaluation.<br> <br> That maps cleanly to PSEA too. The signed proof can carry or bind the<br> jti/counter/freshness material, but the high-water mark decision is<br> still verifier state. So I would treat "reject before approval-bearing= <br> state mutation" as the common invariant across both profiles.<br> <br> I'll take a closer pass over the actual vectors and send concrete nits<= br> off-list once the survey skeleton lands.<br> <br> Best,<br> Songbo Bu<br> <br> On Thu, 11 Jun 2026 00:38:02 -0700, Iman Schrock <<a href=3D"mailto:team= @emiliaprotocol.ai" target=3D"_blank">[email protected]</a>> wrote:= <br> > Songbo =E2=80=94<br> ><br> > The processing-model + negative-test-table framing is the<br> > right call, and the decision-matrix axes you list are exactly the comp= arison<br> > I'd want too =E2=80=94 formats will differ, the verifier decision = shouldn't.<br> ><br> > Two concrete things from my side:<br> ><br> > - I just<br> > published EP's Class-A signoff negative vectors as a tagged batter= y,<br> > grouped on your failure classes =E2=80=94 structural, cryptographic,<b= r> > action-binding, operation/audience, lifecycle/UV =E2=80=94 and run thr= ough two<br> > independent verifiers. It's a starting table for the pass you offe= red;<br> > please shred it. One honest scope line I drew, which I think matters f= or<br> > the survey: I split OFFLINE assertion checks (crypto, action- binding,= <br> > audience, UV) from SERVER-STATE checks (replay / one-time consumption = =E2=80=94<br> > the nonce is our global consumption key =E2=80=94 and enrollment-activ= e). A<br> > relying party verifying an EP receipt offline gets the former; the lat= ter<br> > live at the consume/ledger step. PSEA's counter/jti high-water-mar= k<br> > invariant is the same idea on your side.<br> ><br> > - Here's<br> > EP filled into your matrix, as a strawman for the joint note:<br> ><br> > - action<br> > canonicalization/hash: JCS (RFC 8785) of the action object =E2=86=92 S= HA-256<br> > context hash; non-canonical input MUST be rejected.<br> ><br> > - approver/subject<br> > binding: device-bound approver key (Class A, WebAuthn P-256); approver= _id<br> > resolved via a signed Approver Directory.<br> ><br> > - operation/audience<br> > binding: action_hash + policy ref + initiator in the signed context;<b= r> > WebAuthn rp_id for the relying party.<br> ><br> > - replay<br> > key: 128-bit CSPRNG nonce, globally unique =E2=80=94 it IS the consump= tion key.<br> ><br> > - one-time<br> > state: consumed at most once, globally; replay across<br> > sessions/operators/time terminally refused.<br> ><br> > - trust<br> > root: the signed Approver Directory; MUST NOT default to the EP operat= or.<br> ><br> > - relying-party<br> > result: the offline-verifiable Trust Receipt (signed authorization +<b= r> > consumption record + Merkle anchor).<br> ><br> > - enforcement<br> > class: Class A device-bound (recommended) vs Class C operator-custodie= d<br> > (legacy, weaker); execution-side enforcement is the strongest deployme= nt<br> > class.<br> ><br> > - residual<br> > risk: presentation attack / WYSIWYS, and key-revocation timing =E2=80= =94 both<br> > stated, neither claimed solved.<br> ><br> > The two profiles land on different rows only at assurance<br> > tier (your dedicated-hardware attestation vs. our commodity authentica= tor) and<br> > scope (we add enrollment + separation of duties). Same decision matrix= .<br> ><br> > I'll fold this into the survey skeleton I'm roughing out and<b= r> > send the whole thing off-list for you and Mohamad to edit.<br> ><br> > On Thu, Jun 11, 2026 at 12:24=E2=80=AFAM Songbo Bu <<a href=3D"mail= to:[email protected]" target=3D"_blank">[email protected]</a>> wro= te:<br> ><br> > Hi Mohamad, Iman, all,<br> ><br> > Thanks, Mohamad. And Iman, that is a useful data point.<br> ><br> > For PSEA, my first pass would be to make the verifier-side text more<b= r> ><br> > like a processing model, plus a small set of negative verifier cases.<= br> ><br> > That seems more useful to implementers than adding another long<br> ><br> > requirements list.<br> ><br> > For the processing model, something along these lines:<br> ><br> > > On receipt of a proof submission, the Verifier treats the signed = proof, the submitted action payload, enrollment state, and replay state as = one atomic authorization decision. The Verifier first validates the signed = token and then uses only claims from the verified payload, together with se= rver-side enrollment and replay state, to evaluate the proof. Before any co= unter advance, ledger write, acknowledgement signing, or success-bearing At= testation Result, the Verifier canonicalizes the submitted actionPayload, r= ecomputes the payload hash, and compares it byte-for-byte with the signed p= sea_payload_hash. Any mismatch, missing required signed claim, stale freshn= ess value, replayed jti/counter, inactive enrollment state, or failed cross= -replay binding is a non-approval outcome.<br> ><br> > I would then group failures into a small negative-test table rather<br= > ><br> > than leave them spread across the document:<br> ><br> > - structural / cryptographic: wrong profile, unsupported alg, bad<br> ><br> > signature, unrecognized critical header;<br> ><br> > - action-binding: missing actionPayload, payload-hash mismatch,<br> ><br> > non-canonical payload, or psea_payload_hash encoding mismatch;<br> ><br> > - cross-replay: psea_tier, psea_op, aud, iss, or psea_caller_package<b= r> ><br> > not matching the expected operation context;<br> ><br> > - freshness / replay: expired proof, implausible iat, wrong eat_nonce,= <br> ><br> > reused jti, or non-increasing counter;<br> ><br> > - lifecycle / trust gate: enrollment not active, revoked/suspended<br> ><br> > key, or user-verification evidence insufficient for the required<br> ><br> > assurance level;<br> ><br> > - optional chain layer: psea_chain_prev mismatch when that layer is en= abled.<br> ><br> > The key point I would make testable is that all of those failures have= <br> ><br> > the same security effect: the verifier rejects before any state<br> ><br> > mutation that could make the proof look consumed or approved. In<br> ><br> > test-vector terms, a rejected proof should leave the counter<br> ><br> > high-water mark, finalized-jti set, chain entry, approval ledger, and<= br> ><br> > approval-bearing Attestation Result unchanged or absent.<br> ><br> > For Iman's draft, I think the useful comparison is not whether the= <br> ><br> > token formats are identical, but whether the verifier decision<br> ><br> > matrices line up: action canonicalization/hash, approver or subject<br= > ><br> > binding, operation/audience binding, replay key, one-time state, trust= <br> ><br> > root, relying-party result, enforcement class, and residual risk. I<br= > ><br> > can take a pass over that next, especially around Class A signoff<br> ><br> > negative vectors and deployment-class claims.<br> ><br> > Best,<br> ><br> > Songbo Bu<br> ><br> > --<br> ><br> > Iman Schrock<br> > Founder & CEO<br> ><br> > EMILIA Protocol | Trust Before High-Risk Action<br> > Eye warns. EP verifies. Signoff owns.<br> ><br> > [<a href=3D"http://emiliaprotocol.ai" rel=3D"noreferrer" target=3D"_bl= ank">emiliaprotocol.ai</a>](<a href=3D"http://emiliaprotocol.ai" rel=3D"nor= eferrer" target=3D"_blank">http://emiliaprotocol.ai</a>) | GitHub<br> > <a href=3D"mailto:[email protected]" target=3D"_blank">team@emili= aprotocol.ai</a><br> </blockquote></div><div><br clear=3D"all"></div><div><br></div><span class= =3D"gmail_signature_prefix">-- </span><br><div dir=3D"ltr" class=3D"gmail_s= ignature"><div dir=3D"ltr">Iman Schrock<br>Founder & CEO<br><br>EMILIA = Protocol | Trust Before High-Risk Action<br>Eye warns. EP verifies. Signoff= owns.<br><br><a href=3D"http://emiliaprotocol.ai" target=3D"_blank">emilia= protocol.ai</a> | GitHub<br><a href=3D"mailto:[email protected]" targe= t=3D"_blank">[email protected]</a></div></div> --000000000000eec2d70653f5d961-- --===============1484852092391133973== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: inline X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KU2VjZGlzcGF0 Y2ggbWFpbGluZyBsaXN0IC0tIHNlY2Rpc3BhdGNoQGlldGYub3JnClRvIHVuc3Vic2NyaWJlIHNl bmQgYW4gZW1haWwgdG8gc2VjZGlzcGF0Y2gtbGVhdmVAaWV0Zi5vcmcK --===============1484852092391133973==--