From f84a5e8c182b18524fcd3432123ff95f7ee78392 Mon Sep 17 00:00:00 2001 From: Aere Network Date: Thu, 1 Oct 2026 17:15:48 +0300 Subject: [PATCH] Texts after the AIP-22 erratum of 2026-10-01 (an anchor certificate shows that its signers sealed the block post-quantum, not that it was decided); SD-JWT key-binding nbf probe and its negative control Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- control-plane/consola-web/index.html | 2 +- identity/control-negativ-sdjwt.mjs | 2 ++ identity/proba-sdjwt.mjs | 9 ++++++++- proof-kinds/README.md | 2 +- verify-layer/README.md | 7 ++++--- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ecf2aef..5b1df2b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ notarization command of the verification layer, and the agents' x402 wallet (EIP | [`pq-kms/`](pq-kms/) | a transit-style key management service where every key is hybrid: X25519 + ML-KEM-768 for encryption, Ed25519 + ML-DSA-65 for signatures (both halves required); versions, rotation, rewrap, data keys, a chained audit log; the root key from the environment or sealed by an HSM through PKCS#11 | | [`pq-pki/`](pq-pki/) | a private certificate authority for ML-DSA (X.509 v3, RFC 9881): root and issuing CAs, leaf certificates, revocation lists, and a strict chain verifier compared against OpenSSL | | [`crypto-inventory/`](crypto-inventory/) | a cryptographic inventory of source code (JavaScript/TypeScript, Python, Java, Go, PEM blocks, dependency manifests): every use classified by its exposure to a quantum computer, with a migration target, written as a CycloneDX 1.6 CBOM; nothing from the scanned tree is executed, and its cost stays linear on input built to be slow | -| [`verify-layer/`](verify-layer/) | an audit-log sidecar for any deployment: entries are AIP-23 envelopes in a hash chain, the runtime adapter records every running Docker or Kubernetes container without any secret value, and the head of the chain can be signed by the operator (ML-DSA-65) and notarized on Aere Network for post-quantum finality; it says plainly what that proves (the history before a published head) and what it does not (that the host told the truth); the same log as a Merkle tree (RFC 9162): signed tree heads, and inclusion and consistency proofs of a few hashes that anyone checks without the log | +| [`verify-layer/`](verify-layer/) | an audit-log sidecar for any deployment: entries are AIP-23 envelopes in a hash chain, the runtime adapter records every running Docker or Kubernetes container without any secret value, and the head of the chain can be signed by the operator (ML-DSA-65) and notarized on Aere Network under a post-quantum validator certificate; it says plainly what that proves (the history before a published head) and what it does not (that the host told the truth); the same log as a Merkle tree (RFC 9162): signed tree heads, and inclusion and consistency proofs of a few hashes that anyone checks without the log | | [`proof-kinds/`](proof-kinds/) | the AIP-23 envelope builder the verification layer uses: fourteen proof kinds, one envelope format, digests instead of raw content | | [`readiness/`](readiness/) | the post-quantum readiness scanner of a public hostname: real TLS handshakes (hybrid only, hybrid preferred, TLS 1.2), HSTS, the certificate; no connection to an address not proven public; a rate limit per client that X-Forwarded-For cannot bypass | | [`control-plane/`](control-plane/) | from findings to a finished migration: a prioritized plan from the inventory and the scanner, its execution through the gateway, KMS and PKI with consent per action and a measured proof afterwards, recipes for the servers the products do not touch and a rescan that judges them, a compliance report against NIST IR 8547, the EU roadmap and optionally CNSA 2.0, and a console that checks it all again | diff --git a/control-plane/consola-web/index.html b/control-plane/consola-web/index.html index abeb397..066b9ab 100644 --- a/control-plane/consola-web/index.html +++ b/control-plane/consola-web/index.html @@ -155,7 +155,7 @@ button.ghost{border:1px solid var(--line2);background:transparent;color:var(--mu
-

Post-quantum finality

+

Post-quantum certificate

diff --git a/identity/control-negativ-sdjwt.mjs b/identity/control-negativ-sdjwt.mjs index 0d87adb..d0535dc 100644 --- a/identity/control-negativ-sdjwt.mjs +++ b/identity/control-negativ-sdjwt.mjs @@ -37,6 +37,8 @@ const PLANTARI = [ ['crit primit', "if (Object.hasOwn(antet, 'crit')) throw", 'if (false) throw', 'REVIZUIRE: crit'], ['base64url necanonic primit', "if (b.toString('base64url') !== s) throw", 'if (false) throw', 'REVIZUIRE: crit'], ['exp-ul KB nu se mai cere', 'if (K.exp !== undefined && (!Number.isFinite(K.exp) || acum >= K.exp)) return', 'if (false) return', 'REVIZUIRE: crit'], + // 2026-10-01: a doua jumatate a "exp/nbf ale KB"; fara plantarea ei, paznicul scos lasa proba verde (masurat: 23/23) + ['nbf-ul KB nu se mai cere', 'if (K.nbf !== undefined && (!Number.isFinite(K.nbf) || K.nbf - clockSkewS > acum)) return', 'if (false) return', 'REVIZUIRE: crit'], ['_sd_alg scos de la orice nivel', "!(sus && k === '_sd_alg')", "k !== '_sd_alg'", 'REVIZUIRE: crit'], ['cheia publica Node nu mai e primita', "(k.type === 'public' ? k : crypto.createPublicKey(k))", 'crypto.createPublicKey(k)', 'REVIZUIRE: cheia emitentului'], ['o afirmatie __proto__ primita (prototipul schimbat)', "if (Object.hasOwn(x, '__proto__')) throw", 'if (false) throw', 'ATAC: o afirmatie numita __proto__'], diff --git a/identity/proba-sdjwt.mjs b/identity/proba-sdjwt.mjs index 0da4e38..c9b1ca7 100644 --- a/identity/proba-sdjwt.mjs +++ b/identity/proba-sdjwt.mjs @@ -178,7 +178,7 @@ test('REVIZUIRE: tipul jetonului: un KB-JWT sau un JWT cu typ JWT dat drept jeto cere(bun.valid && bun.issuerChecked, 'emitentul asteptat refuzat'); cere(refuz(J.verifySdJwt(arata(E.sdJwt, ['age_over_18']), { issuerKey: iss, audience: AUD, nonce: NONCE, now: LA, expectedIssuer: 'https://altul.example' }), /issued by/), 'alt emitent a trecut'); }); -test('REVIZUIRE: crit (orice forma), base64url necanonic al semnaturii, KB expirat -> refuzate; _sd_alg imbricat ramane afirmatie', () => { +test('REVIZUIRE: crit (orice forma), base64url necanonic al semnaturii, KB expirat sau inca nevalabil (nbf) -> refuzate; _sd_alg imbricat ramane afirmatie', () => { const corp = { iss: 'x', exp: NOW + 100, _sd_alg: 'sha-256', meta: { _sd_alg: 'x', note: 1 } }; for (const crit of [[], 'b64', ['b64']]) cere(refuz(J.verifySdJwt(jwtDin({ alg: 'EdDSA', typ: J.TYP, crit, b64: false }, corp, iss.privat.ed25519) + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /crit/), 'crit ' + JSON.stringify(crit)); const j = jwtDin({ alg: 'EdDSA', typ: J.TYP }, corp, iss.privat.ed25519); @@ -193,6 +193,13 @@ test('REVIZUIRE: crit (orice forma), base64url necanonic al semnaturii, KB expir const sh = crypto.createHash('sha256').update(Buffer.from(baza, 'ascii')).digest('base64url'); const kbExp = jwtDin({ alg: 'EdDSA', typ: 'kb+jwt' }, { iat: NOW, aud: AUD, nonce: NONCE, sd_hash: sh, exp: NOW - 100 }, hol.privat.ed25519); cere(refuz(judeca(baza + kbExp), /key binding: expired/), 'KB expirat a trecut'); + // 2026-10-01: nbf-ul KB (a doua jumatate a reparatiei "exp/nbf ale KB" din B-32) nu avea proba: paznicul scos lasa proba 23/23. + // Martorul aceluiasi KB facut de mana, cu nbf in toleranta de ceas, e VALID: refuzul de mai jos e al nbf-ului, nu al constructiei. + const kbNbf = (nbf) => jwtDin({ alg: 'EdDSA', typ: 'kb+jwt' }, { iat: NOW, aud: AUD, nonce: NONCE, sd_hash: sh, nbf }, hol.privat.ed25519); + const rNbfBun = judeca(baza + kbNbf(NOW + 30)); + cere(rNbfBun.valid, 'martorul: KB cu nbf in toleranta (30 s) trebuie sa fie VALID: ' + rNbfBun.reason); + cere(refuz(judeca(baza + kbNbf(NOW + 600)), /key binding: not valid before/), 'KB inca nevalabil (nbf peste 10 minute) a trecut'); + cere(refuz(judeca(baza + kbNbf('maine')), /key binding: not valid before/), 'KB cu nbf necitibil a trecut'); }); test('linia de comanda: verify-sdjwt pe prezentarea RFC 9901 s.5.2 -> 0 VALID la momentul ei (--at), 1 INVALID acum (KB vechi); o cheie privata -> 2', () => { diff --git a/proof-kinds/README.md b/proof-kinds/README.md index d192c5a..2a4d554 100644 --- a/proof-kinds/README.md +++ b/proof-kinds/README.md @@ -13,7 +13,7 @@ const envelope = buildProof('runtime', { host, artifactContent, attested, matche A kind is a schema: its required fields, some of them digests, plus `createdAt`. Raw content given for a digest field is hashed (bytes as they are, strings as UTF-8, other values as their JSON text), and a value that is already a 32-byte digest is kept, so an envelope carries digests, never the raw content. A missing required field stops the build with an error instead of producing -an incomplete envelope. Notarization and post-quantum finality come from AIP-23, not from this builder. +an incomplete envelope. Notarization and the post-quantum certificate over it come from AIP-23, not from this builder. Test: `node proba-proof-kinds.mjs` (24 checks: every kind built and found `VALID` by the reference verifier, six negative controls such as content changed after hashing or a required field missing, no raw content in any envelope, bytes hashed as diff --git a/verify-layer/README.md b/verify-layer/README.md index 50091db..89246fb 100644 --- a/verify-layer/README.md +++ b/verify-layer/README.md @@ -4,7 +4,8 @@ A sidecar that runs next to a deployment, on any cloud or on premises, and keeps AERE Proof Protocol envelope (AIP-23), built by the same `proof-kinds` builder and checked by the same AIP-23 reference verifier as every other AERE proof, and every entry covers the hash of the one before it (an append-only hash chain, `hash = sha256(seq | prev | envelope)`, genesis `0x00..00`). The head of the chain can be notarized on Aere Network, which -gives the history up to that head post-quantum finality that anyone can check. +binds the history up to that head under a post-quantum validator certificate that anyone can check (what such a +certificate proves, and what it does not, is in the AIP-22 erratum of 2026-10-01). Node.js 24, no dependencies; `notarize-head` alone needs `ethers` (`npm install` in this directory). @@ -17,8 +18,8 @@ Node.js 24, no dependencies; `notarize-head` alone needs `ethers` (`npm install` `verify-log` on that file alone reports it intact. A partial edit is caught (the chain breaks at the edited entry and `verify-log` names it), a full rewrite is not. - **What is checkable without trusting the host is the history before a published head.** `attest-head` turns the current - head into an envelope; `notarize-head` records its hash on the AereNotary contract; the AIP-23 reference verifier then gives - it post-quantum finality (the most recent certified anchor, the parent header bound by hash, its state root, and a Merkle + head into an envelope; `notarize-head` records its hash on the AereNotary contract; the AIP-23 reference verifier then shows + it under a post-quantum certificate (the most recent certified anchor, the parent header bound by hash, its state root, and a Merkle proof of `firstSeen[statementHash]`). `verify-log --attested ` requires that today's log **continues** that head: the same first `count` entries, the entry `count - 1` hashing to the attested head. A rewritten or shortened history fails.