[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&#39;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&#39;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 &lt;<a href=3D"mailto:[email protected]">king347=
[email protected]</a>&gt; 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 &quot;reject before approval-bearing=
<br>
state mutation&quot; as the common invariant across both profiles.<br>
<br>
I&#39;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 &lt;<a href=3D"mailto:team=
@emiliaprotocol.ai" target=3D"_blank">[email protected]</a>&gt; wrote:=
<br>
&gt; Songbo =E2=80=94<br>
&gt;<br>
&gt; The processing-model + negative-test-table framing is the<br>
&gt; right call, and the decision-matrix axes you list are exactly the comp=
arison<br>
&gt; I&#39;d want too =E2=80=94 formats will differ, the verifier decision =
shouldn&#39;t.<br>
&gt;<br>
&gt; Two concrete things from my side:<br>
&gt;<br>
&gt; - I just<br>
&gt; published EP&#39;s Class-A signoff negative vectors as a tagged batter=
y,<br>
&gt; grouped on your failure classes =E2=80=94 structural, cryptographic,<b=
r>
&gt; action-binding, operation/audience, lifecycle/UV =E2=80=94 and run thr=
ough two<br>
&gt; independent verifiers. It&#39;s a starting table for the pass you offe=
red;<br>
&gt; please shred it. One honest scope line I drew, which I think matters f=
or<br>
&gt; the survey: I split OFFLINE assertion checks (crypto, action- binding,=
<br>
&gt; audience, UV) from SERVER-STATE checks (replay / one-time consumption =
=E2=80=94<br>
&gt; the nonce is our global consumption key =E2=80=94 and enrollment-activ=
e). A<br>
&gt; relying party verifying an EP receipt offline gets the former; the lat=
ter<br>
&gt; live at the consume/ledger step. PSEA&#39;s counter/jti high-water-mar=
k<br>
&gt; invariant is the same idea on your side.<br>
&gt;<br>
&gt; - Here&#39;s<br>
&gt; EP filled into your matrix, as a strawman for the joint note:<br>
&gt;<br>
&gt; - action<br>
&gt; canonicalization/hash: JCS (RFC 8785) of the action object =E2=86=92 S=
HA-256<br>
&gt; context hash; non-canonical input MUST be rejected.<br>
&gt;<br>
&gt; - approver/subject<br>
&gt; binding: device-bound approver key (Class A, WebAuthn P-256); approver=
_id<br>
&gt; resolved via a signed Approver Directory.<br>
&gt;<br>
&gt; - operation/audience<br>
&gt; binding: action_hash + policy ref + initiator in the signed context;<b=
r>
&gt; WebAuthn rp_id for the relying party.<br>
&gt;<br>
&gt; - replay<br>
&gt; key: 128-bit CSPRNG nonce, globally unique =E2=80=94 it IS the consump=
tion key.<br>
&gt;<br>
&gt; - one-time<br>
&gt; state: consumed at most once, globally; replay across<br>
&gt; sessions/operators/time terminally refused.<br>
&gt;<br>
&gt; - trust<br>
&gt; root: the signed Approver Directory; MUST NOT default to the EP operat=
or.<br>
&gt;<br>
&gt; - relying-party<br>
&gt; result: the offline-verifiable Trust Receipt (signed authorization +<b=
r>
&gt; consumption record + Merkle anchor).<br>
&gt;<br>
&gt; - enforcement<br>
&gt; class: Class A device-bound (recommended) vs Class C operator-custodie=
d<br>
&gt; (legacy, weaker); execution-side enforcement is the strongest deployme=
nt<br>
&gt; class.<br>
&gt;<br>
&gt; - residual<br>
&gt; risk: presentation attack / WYSIWYS, and key-revocation timing =E2=80=
=94 both<br>
&gt; stated, neither claimed solved.<br>
&gt;<br>
&gt; The two profiles land on different rows only at assurance<br>
&gt; tier (your dedicated-hardware attestation vs. our commodity authentica=
tor) and<br>
&gt; scope (we add enrollment + separation of duties). Same decision matrix=
.<br>
&gt;<br>
&gt; I&#39;ll fold this into the survey skeleton I&#39;m roughing out and<b=
r>
&gt; send the whole thing off-list for you and Mohamad to edit.<br>
&gt;<br>
&gt; On Thu, Jun 11, 2026 at 12:24=E2=80=AFAM Songbo Bu &lt;<a href=3D"mail=
to:[email protected]" target=3D"_blank">[email protected]</a>&gt; wro=
te:<br>
&gt;<br>
&gt; Hi Mohamad, Iman, all,<br>
&gt;<br>
&gt; Thanks, Mohamad. And Iman, that is a useful data point.<br>
&gt;<br>
&gt; For PSEA, my first pass would be to make the verifier-side text more<b=
r>
&gt;<br>
&gt; like a processing model, plus a small set of negative verifier cases.<=
br>
&gt;<br>
&gt; That seems more useful to implementers than adding another long<br>
&gt;<br>
&gt; requirements list.<br>
&gt;<br>
&gt; For the processing model, something along these lines:<br>
&gt;<br>
&gt; &gt; 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>
&gt;<br>
&gt; I would then group failures into a small negative-test table rather<br=
>
&gt;<br>
&gt; than leave them spread across the document:<br>
&gt;<br>
&gt; - structural / cryptographic: wrong profile, unsupported alg, bad<br>
&gt;<br>
&gt; signature, unrecognized critical header;<br>
&gt;<br>
&gt; - action-binding: missing actionPayload, payload-hash mismatch,<br>
&gt;<br>
&gt; non-canonical payload, or psea_payload_hash encoding mismatch;<br>
&gt;<br>
&gt; - cross-replay: psea_tier, psea_op, aud, iss, or psea_caller_package<b=
r>
&gt;<br>
&gt; not matching the expected operation context;<br>
&gt;<br>
&gt; - freshness / replay: expired proof, implausible iat, wrong eat_nonce,=
<br>
&gt;<br>
&gt; reused jti, or non-increasing counter;<br>
&gt;<br>
&gt; - lifecycle / trust gate: enrollment not active, revoked/suspended<br>
&gt;<br>
&gt; key, or user-verification evidence insufficient for the required<br>
&gt;<br>
&gt; assurance level;<br>
&gt;<br>
&gt; - optional chain layer: psea_chain_prev mismatch when that layer is en=
abled.<br>
&gt;<br>
&gt; The key point I would make testable is that all of those failures have=
<br>
&gt;<br>
&gt; the same security effect: the verifier rejects before any state<br>
&gt;<br>
&gt; mutation that could make the proof look consumed or approved. In<br>
&gt;<br>
&gt; test-vector terms, a rejected proof should leave the counter<br>
&gt;<br>
&gt; high-water mark, finalized-jti set, chain entry, approval ledger, and<=
br>
&gt;<br>
&gt; approval-bearing Attestation Result unchanged or absent.<br>
&gt;<br>
&gt; For Iman&#39;s draft, I think the useful comparison is not whether the=
<br>
&gt;<br>
&gt; token formats are identical, but whether the verifier decision<br>
&gt;<br>
&gt; matrices line up: action canonicalization/hash, approver or subject<br=
>
&gt;<br>
&gt; binding, operation/audience binding, replay key, one-time state, trust=
<br>
&gt;<br>
&gt; root, relying-party result, enforcement class, and residual risk. I<br=
>
&gt;<br>
&gt; can take a pass over that next, especially around Class A signoff<br>
&gt;<br>
&gt; negative vectors and deployment-class claims.<br>
&gt;<br>
&gt; Best,<br>
&gt;<br>
&gt; Songbo Bu<br>
&gt;<br>
&gt; --<br>
&gt;<br>
&gt; Iman Schrock<br>
&gt; Founder &amp; CEO<br>
&gt;<br>
&gt; EMILIA Protocol | Trust Before High-Risk Action<br>
&gt; Eye warns. EP verifies. Signoff owns.<br>
&gt;<br>
&gt; [<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>
&gt; <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 &amp; 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==--