@cello-protocol/crypto 0.0.59 → 0.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest-test-fixture.d.ts.map +1 -1
- package/dist/manifest-test-fixture.js +12 -1
- package/dist/manifest-test-fixture.js.map +1 -1
- package/dist/session-content-seal.d.ts +86 -0
- package/dist/session-content-seal.d.ts.map +1 -0
- package/dist/session-content-seal.js +124 -0
- package/dist/session-content-seal.js.map +1 -0
- package/dist/session-ephemeral-auth.d.ts +96 -0
- package/dist/session-ephemeral-auth.d.ts.map +1 -0
- package/dist/session-ephemeral-auth.js +137 -0
- package/dist/session-ephemeral-auth.js.map +1 -0
- package/dist/session-key-agreement.d.ts +55 -14
- package/dist/session-key-agreement.d.ts.map +1 -1
- package/dist/session-key-agreement.js +68 -21
- package/dist/session-key-agreement.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export { buildRelayRegistrationTbs, verifyRelayRegistrationSignature } from "./r
|
|
|
17
17
|
export { sealToRecipient, openSealed, CONTENT_SEAL_OVERHEAD_BYTES } from "./content-seal.js";
|
|
18
18
|
export { generateSaltContribution, deriveSessionSalt, saltFingerprint, saltedContentHash, SALT_CONTRIBUTION_BYTES, SALT_FINGERPRINT_BYTES, SESSION_SALT_BYTES, } from "./session-salt.js";
|
|
19
19
|
export { generateSessionEphemeral, destroySessionEphemeral, deriveSessionSecrets, SESSION_KEY_BYTES, type SessionEphemeral, type SessionSecrets, } from "./session-key-agreement.js";
|
|
20
|
+
export { ephemeralSigningMessage, signSessionEphemeral, verifySessionEphemeral, EPHEMERAL_AUTH_REFUSALS, EPHEMERAL_SIG_BYTES, EPHEMERAL_PUBLIC_BYTES, type EphemeralAuthRefusal, type EphemeralAuthResult, } from "./session-ephemeral-auth.js";
|
|
21
|
+
export { sealSessionContent, openSessionContent, SESSION_CONTENT_SEAL_V1, SESSION_CONTENT_SEAL_OVERHEAD_BYTES, } from "./session-content-seal.js";
|
|
20
22
|
export type { ManifestVerifyResult, ManifestVerifyDiagnostics, ManifestVerifySkipReason, ManifestVerifySkippedEntry, ConsortiumManifestInput } from "./manifest.js";
|
|
21
23
|
export { canonicalManifestBody, verifyManifest } from "./manifest.js";
|
|
22
24
|
export type { PreAuthCapability, PreAuthCapabilityBody, CapabilityVerifyResult, CapabilityVerifyReason } from "./preauth-capability.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACpI,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,SAAS,EACT,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACxI,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3F,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,0BAA0B,EAC1B,YAAY,EACZ,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,6BAA6B,EAC7B,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAIzE,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,YAAY,GACb,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,iCAAiC,EACjC,wBAAwB,GACzB,MAAM,mCAAmC,CAAC;AAK3C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGzD,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG5E,OAAO,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAGtG,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAI7F,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACpI,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,SAAS,EACT,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACxI,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3F,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,0BAA0B,EAC1B,YAAY,EACZ,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,6BAA6B,EAC7B,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAIzE,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,YAAY,GACb,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,iCAAiC,EACjC,wBAAwB,GACzB,MAAM,mCAAmC,CAAC;AAK3C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGzD,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG5E,OAAO,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAGtG,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAI7F,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,4BAA4B,CAAC;AAOpC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,mCAAmC,GACpC,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACpK,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEtE,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACxI,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACxI,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAG3F,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC9F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,13 @@ export { generateSaltContribution, deriveSessionSalt, saltFingerprint, saltedCon
|
|
|
28
28
|
// hybrid-PQ hook built in. ONE output, the envelope key: the salt is agreed separately above
|
|
29
29
|
// (Decisions Carried #8), because the key is destroyed at close and the salt outlives the session.
|
|
30
30
|
export { generateSessionEphemeral, destroySessionEphemeral, deriveSessionSecrets, SESSION_KEY_BYTES, } from "./session-key-agreement.js";
|
|
31
|
+
// DOD-M15-EPHEMERAL-AUTH-1: the throwaway key is SIGNED with the agent's identity and the peer's is
|
|
32
|
+
// verified BEFORE anything is derived from it — without this a relay substitutes both sides' keys
|
|
33
|
+
// and reads everything while both ends see a working conversation. Plus the AEAD that encrypts a
|
|
34
|
+
// message body under the agreed key: distinct from `content-seal.ts`, which seals PARKED content to
|
|
35
|
+
// a long-term identity key and deliberately has no forward secrecy.
|
|
36
|
+
export { ephemeralSigningMessage, signSessionEphemeral, verifySessionEphemeral, EPHEMERAL_AUTH_REFUSALS, EPHEMERAL_SIG_BYTES, EPHEMERAL_PUBLIC_BYTES, } from "./session-ephemeral-auth.js";
|
|
37
|
+
export { sealSessionContent, openSessionContent, SESSION_CONTENT_SEAL_V1, SESSION_CONTENT_SEAL_OVERHEAD_BYTES, } from "./session-content-seal.js";
|
|
31
38
|
export { canonicalManifestBody, verifyManifest } from "./manifest.js";
|
|
32
39
|
export { canonicalCapabilityBody, signCapability, verifyCapability, encodeCapability, decodeCapability } from "./preauth-capability.js";
|
|
33
40
|
export { CONSORTIUM_ROOT_KEYS, CONSORTIUM_THRESHOLD, TEST_CONSORTIUM_ROOT_KEYS, TEST_CONSORTIUM_THRESHOLD, } from "./consortium-keys.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEpI,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,SAAS,EACT,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAExI,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAU3F,OAAO,EACL,6BAA6B,EAC7B,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAE1B,iGAAiG;AACjG,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,uGAAuG;AACvG,0FAA0F;AAC1F,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,YAAY,GACb,MAAM,4BAA4B,CAAC;AAEpC,8FAA8F;AAC9F,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,iCAAiC,EACjC,wBAAwB,GACzB,MAAM,mCAAmC,CAAC;AAE3C,qGAAqG;AACrG,mGAAmG;AACnG,qEAAqE;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,8EAA8E;AAC9E,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE5E,oEAAoE;AACpE,OAAO,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAEtG,+EAA+E;AAC/E,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAC7F,kGAAkG;AAClG,oGAAoG;AACpG,sGAAsG;AACtG,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,kGAAkG;AAClG,6FAA6F;AAC7F,mGAAmG;AACnG,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,GAGlB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEpI,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,SAAS,EACT,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAExI,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAU3F,OAAO,EACL,6BAA6B,EAC7B,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAE1B,iGAAiG;AACjG,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,uGAAuG;AACvG,0FAA0F;AAC1F,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,YAAY,GACb,MAAM,4BAA4B,CAAC;AAEpC,8FAA8F;AAC9F,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,iCAAiC,EACjC,wBAAwB,GACzB,MAAM,mCAAmC,CAAC;AAE3C,qGAAqG;AACrG,mGAAmG;AACnG,qEAAqE;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,8EAA8E;AAC9E,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE5E,oEAAoE;AACpE,OAAO,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAEtG,+EAA+E;AAC/E,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAC7F,kGAAkG;AAClG,oGAAoG;AACpG,sGAAsG;AACtG,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,kGAAkG;AAClG,6FAA6F;AAC7F,mGAAmG;AACnG,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,GAGlB,MAAM,4BAA4B,CAAC;AAEpC,oGAAoG;AACpG,kGAAkG;AAClG,iGAAiG;AACjG,oGAAoG;AACpG,oEAAoE;AACpE,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GAGvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,mCAAmC,GACpC,MAAM,2BAA2B,CAAC;AAInC,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACxI,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAE3F,wEAAwE;AACxE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-test-fixture.d.ts","sourceRoot":"","sources":["../src/manifest-test-fixture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAM3F,CAAC;AAEX,sGAAsG;AACtG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,kBAAkB,EAAE,EAC3B,IAAI,CAAC,EAAE,oBAAoB,GAC1B,uBAAuB,
|
|
1
|
+
{"version":3,"file":"manifest-test-fixture.d.ts","sourceRoot":"","sources":["../src/manifest-test-fixture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAM3F,CAAC;AAEX,sGAAsG;AACtG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,kBAAkB,EAAE,EAC3B,IAAI,CAAC,EAAE,oBAAoB,GAC1B,uBAAuB,CA+BzB;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B;;;CAG9B,CAAC"}
|
|
@@ -34,7 +34,18 @@ export function makeTestManifest(nodes, opts) {
|
|
|
34
34
|
const manifest = {
|
|
35
35
|
version: opts?.version ?? 1,
|
|
36
36
|
not_before: opts?.notBefore ?? "2026-01-01T00:00:00Z",
|
|
37
|
-
|
|
37
|
+
/**
|
|
38
|
+
* ⚠️ A FIXTURE THAT EXPIRES IS A TEST SUITE WITH A FUSE. This defaulted to `2027-01-01`, four
|
|
39
|
+
* months out when it was found (2026-08-24) — after which every default-fixture manifest becomes
|
|
40
|
+
* EXPIRED and every test that needs an in-window one goes red **on a date, with no code change**.
|
|
41
|
+
* It would also have silently switched on the lapsed-manifest branches added by
|
|
42
|
+
* `DOD-M15-EXPIRY-CONSUMER-POLICY-1` for every existing verifier test at the same moment.
|
|
43
|
+
*
|
|
44
|
+
* Far-future and FIXED, not `Date.now() + 1y`: a rolling default makes the fixture's window
|
|
45
|
+
* depend on the clock, and the tests that care about window boundaries pass their own dates
|
|
46
|
+
* anyway — which is the reason this default only ever needs to mean "in window".
|
|
47
|
+
*/
|
|
48
|
+
expires: opts?.expires ?? "2099-01-01T00:00:00Z",
|
|
38
49
|
nodes: nodes,
|
|
39
50
|
signatures: [],
|
|
40
51
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-test-fixture.js","sourceRoot":"","sources":["../src/manifest-test-fixture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA0E;IACvG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;CACrB,CAAC;AAqBX;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAA2B,EAC3B,IAA2B;IAE3B,MAAM,QAAQ,GAA4B;QACxC,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,CAAC;QAC3B,UAAU,EAAE,IAAI,EAAE,SAAS,IAAI,sBAAsB;QACrD,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,sBAAsB;QAChD,KAAK,EAAE,KAA2C;QAClD,UAAU,EAAE,EAAE;KACf,CAAC;IAEF,qCAAqC;IACrC,MAAM,IAAI,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAE7C,sDAAsD;IACtD,MAAM,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACzC,YAAY,EAAE,GAAG;QACjB,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACpF,CAAC,CAAC,CAAC;IAEJ,QAAQ,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,aAAa,EAAE,kEAAkE;IACjF,YAAY,EAAE,kEAAkE;CACxE,CAAC"}
|
|
1
|
+
{"version":3,"file":"manifest-test-fixture.js","sourceRoot":"","sources":["../src/manifest-test-fixture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA0E;IACvG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;CACrB,CAAC;AAqBX;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAA2B,EAC3B,IAA2B;IAE3B,MAAM,QAAQ,GAA4B;QACxC,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,CAAC;QAC3B,UAAU,EAAE,IAAI,EAAE,SAAS,IAAI,sBAAsB;QACrD;;;;;;;;;;WAUG;QACH,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,sBAAsB;QAChD,KAAK,EAAE,KAA2C;QAClD,UAAU,EAAE,EAAE;KACf,CAAC;IAEF,qCAAqC;IACrC,MAAM,IAAI,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAE7C,sDAAsD;IACtD,MAAM,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACzC,YAAY,EAAE,GAAG;QACjB,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACpF,CAAC,CAAC,CAAC;IAEJ,QAAQ,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,aAAa,EAAE,kEAAkE;IACjF,YAAY,EAAE,kEAAkE;CACxE,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-M15-EPHEMERAL-AUTH-1 — encrypting a message body under the AGREED SESSION KEY.
|
|
3
|
+
*
|
|
4
|
+
* ─── What this is for, and how it differs from `content-seal.ts` ───────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* Two encryption schemes exist in this codebase and they are NOT interchangeable. Which one applies
|
|
7
|
+
* is decided by the ROUTE a message takes, and getting that wrong is silent:
|
|
8
|
+
*
|
|
9
|
+
* **This file — LIVE messages.** Encrypted under the per-session key both sides agreed from
|
|
10
|
+
* throwaway keypairs (`session-key-agreement.ts`). Those keypairs are destroyed when the session
|
|
11
|
+
* ends, so a recording of the traffic cannot be decrypted later even if both agents' long-term
|
|
12
|
+
* identity keys are eventually compromised. That is forward secrecy, and it is the whole point.
|
|
13
|
+
*
|
|
14
|
+
* **`content-seal.ts` — PARKED messages.** Encrypted to the recipient's LONG-TERM identity key,
|
|
15
|
+
* because the recipient is offline and cannot take part in a handshake. It has no forward secrecy
|
|
16
|
+
* by construction: whoever holds that identity key can open every message ever sealed to it. That
|
|
17
|
+
* is an accepted trade for the mailbox and must not be copied here.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ A message that fails live delivery falls back to the mailbox, so the SAME message can take
|
|
20
|
+
* either scheme. The caller must encrypt the copy that goes on the wire and leave the plaintext it
|
|
21
|
+
* holds for the park backstop alone — sealing an already-encrypted body to the identity key gives
|
|
22
|
+
* the recipient bytes locked under a session key that is about to be destroyed.
|
|
23
|
+
*
|
|
24
|
+
* ─── Construction (SPARC Phase P) ──────────────────────────────────────────────────────────────
|
|
25
|
+
*
|
|
26
|
+
* sealSessionContent(key, plaintext):
|
|
27
|
+
* 1. iv = random 12 bytes # NIST SP 800-38D §8.2.2
|
|
28
|
+
* 2. ct||tag = AES-256-GCM(key, iv, plaintext) # NIST SP 800-38D
|
|
29
|
+
* 3. blob = VERSION(1) || iv(12) || ct || tag(16)
|
|
30
|
+
*
|
|
31
|
+
* openSessionContent(key, blob) -> plaintext | null
|
|
32
|
+
*
|
|
33
|
+
* AES-256-GCM matches `content-seal.ts` rather than introducing a second AEAD, so there is one
|
|
34
|
+
* primitive to reason about. RFCs: HKDF — RFC 5869 (upstream, in the key agreement); AES-GCM — NIST
|
|
35
|
+
* SP 800-38D.
|
|
36
|
+
*
|
|
37
|
+
* ─── Why a RANDOM IV rather than a counter ─────────────────────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* A counter is the stronger construction and it needs state that survives a restart. This session
|
|
40
|
+
* key does not: the secret is deliberately never persisted, and a revived session RE-KEYS
|
|
41
|
+
* (Decisions Carried #5), so a counter would restart at zero under a NEW key — which is safe, and
|
|
42
|
+
* would also have to be proven safe at every future change to the revival rule. A 96-bit random IV
|
|
43
|
+
* under a key that lives for one session, with a birthday bound around 2^32 messages, is not the
|
|
44
|
+
* limiting factor here and needs no state at all.
|
|
45
|
+
*
|
|
46
|
+
* ─── The VERSION byte is FRAMING, not compatibility ────────────────────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* There are no other builds to be compatible with — CELLO is pre-launch with no external installs —
|
|
49
|
+
* so this byte is not here to interoperate with anything. It is here because the alternative is a
|
|
50
|
+
* blob whose layout is implied by its length, and a reader that meets bytes it cannot frame would
|
|
51
|
+
* then hand attacker-chosen input to a decryptor under a guessed layout. One byte, one comparison,
|
|
52
|
+
* and an unknown value REFUSES rather than parsing.
|
|
53
|
+
*
|
|
54
|
+
* That is the only justification, and it is worth stating because the tempting one — "so a future
|
|
55
|
+
* version can change the format" — would be exactly the speculative scaffolding this codebase has
|
|
56
|
+
* ruled against building.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* The only version this build writes, and the only one it reads.
|
|
60
|
+
*
|
|
61
|
+
* `0x01` rather than `0x00`: a zero first byte is what an all-zero buffer and a truncated read both
|
|
62
|
+
* look like, so a version of zero cannot be told from an absent one.
|
|
63
|
+
*/
|
|
64
|
+
export declare const SESSION_CONTENT_SEAL_V1 = 1;
|
|
65
|
+
/** Bytes added to every message: version + IV + tag. */
|
|
66
|
+
export declare const SESSION_CONTENT_SEAL_OVERHEAD_BYTES: number;
|
|
67
|
+
/**
|
|
68
|
+
* Encrypt a message body under the agreed session key.
|
|
69
|
+
*
|
|
70
|
+
* Throws on a wrong-width key rather than deriving from whatever it was given: a short key silently
|
|
71
|
+
* accepted is encryption that appears to work under something that is not the agreed secret.
|
|
72
|
+
*/
|
|
73
|
+
export declare function sealSessionContent(key: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
74
|
+
/**
|
|
75
|
+
* Decrypt a message body under the agreed session key.
|
|
76
|
+
*
|
|
77
|
+
* **Returns `null` on every failure and never throws** — wrong key, tampered ciphertext, truncated
|
|
78
|
+
* blob, unknown version. The caller cannot tell those apart and must not try: GCM's tag check is the
|
|
79
|
+
* only thing that distinguishes "not for us" from "modified in flight", and a caller that branched
|
|
80
|
+
* on the difference would be branching on attacker-controlled input.
|
|
81
|
+
*
|
|
82
|
+
* The caller's job on `null` is to refuse the message loudly, not to retry or to fall back to
|
|
83
|
+
* reading it as plaintext.
|
|
84
|
+
*/
|
|
85
|
+
export declare function openSessionContent(key: Uint8Array, blob: Uint8Array): Uint8Array | null;
|
|
86
|
+
//# sourceMappingURL=session-content-seal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-content-seal.d.ts","sourceRoot":"","sources":["../src/session-content-seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAWH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,IAAO,CAAC;AAE5C,wDAAwD;AACxD,eAAO,MAAM,mCAAmC,QAA2B,CAAC;AAE5E;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,UAAU,CAerF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI,CAiBvF"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-M15-EPHEMERAL-AUTH-1 — encrypting a message body under the AGREED SESSION KEY.
|
|
3
|
+
*
|
|
4
|
+
* ─── What this is for, and how it differs from `content-seal.ts` ───────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* Two encryption schemes exist in this codebase and they are NOT interchangeable. Which one applies
|
|
7
|
+
* is decided by the ROUTE a message takes, and getting that wrong is silent:
|
|
8
|
+
*
|
|
9
|
+
* **This file — LIVE messages.** Encrypted under the per-session key both sides agreed from
|
|
10
|
+
* throwaway keypairs (`session-key-agreement.ts`). Those keypairs are destroyed when the session
|
|
11
|
+
* ends, so a recording of the traffic cannot be decrypted later even if both agents' long-term
|
|
12
|
+
* identity keys are eventually compromised. That is forward secrecy, and it is the whole point.
|
|
13
|
+
*
|
|
14
|
+
* **`content-seal.ts` — PARKED messages.** Encrypted to the recipient's LONG-TERM identity key,
|
|
15
|
+
* because the recipient is offline and cannot take part in a handshake. It has no forward secrecy
|
|
16
|
+
* by construction: whoever holds that identity key can open every message ever sealed to it. That
|
|
17
|
+
* is an accepted trade for the mailbox and must not be copied here.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ A message that fails live delivery falls back to the mailbox, so the SAME message can take
|
|
20
|
+
* either scheme. The caller must encrypt the copy that goes on the wire and leave the plaintext it
|
|
21
|
+
* holds for the park backstop alone — sealing an already-encrypted body to the identity key gives
|
|
22
|
+
* the recipient bytes locked under a session key that is about to be destroyed.
|
|
23
|
+
*
|
|
24
|
+
* ─── Construction (SPARC Phase P) ──────────────────────────────────────────────────────────────
|
|
25
|
+
*
|
|
26
|
+
* sealSessionContent(key, plaintext):
|
|
27
|
+
* 1. iv = random 12 bytes # NIST SP 800-38D §8.2.2
|
|
28
|
+
* 2. ct||tag = AES-256-GCM(key, iv, plaintext) # NIST SP 800-38D
|
|
29
|
+
* 3. blob = VERSION(1) || iv(12) || ct || tag(16)
|
|
30
|
+
*
|
|
31
|
+
* openSessionContent(key, blob) -> plaintext | null
|
|
32
|
+
*
|
|
33
|
+
* AES-256-GCM matches `content-seal.ts` rather than introducing a second AEAD, so there is one
|
|
34
|
+
* primitive to reason about. RFCs: HKDF — RFC 5869 (upstream, in the key agreement); AES-GCM — NIST
|
|
35
|
+
* SP 800-38D.
|
|
36
|
+
*
|
|
37
|
+
* ─── Why a RANDOM IV rather than a counter ─────────────────────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* A counter is the stronger construction and it needs state that survives a restart. This session
|
|
40
|
+
* key does not: the secret is deliberately never persisted, and a revived session RE-KEYS
|
|
41
|
+
* (Decisions Carried #5), so a counter would restart at zero under a NEW key — which is safe, and
|
|
42
|
+
* would also have to be proven safe at every future change to the revival rule. A 96-bit random IV
|
|
43
|
+
* under a key that lives for one session, with a birthday bound around 2^32 messages, is not the
|
|
44
|
+
* limiting factor here and needs no state at all.
|
|
45
|
+
*
|
|
46
|
+
* ─── The VERSION byte is FRAMING, not compatibility ────────────────────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* There are no other builds to be compatible with — CELLO is pre-launch with no external installs —
|
|
49
|
+
* so this byte is not here to interoperate with anything. It is here because the alternative is a
|
|
50
|
+
* blob whose layout is implied by its length, and a reader that meets bytes it cannot frame would
|
|
51
|
+
* then hand attacker-chosen input to a decryptor under a guessed layout. One byte, one comparison,
|
|
52
|
+
* and an unknown value REFUSES rather than parsing.
|
|
53
|
+
*
|
|
54
|
+
* That is the only justification, and it is worth stating because the tempting one — "so a future
|
|
55
|
+
* version can change the format" — would be exactly the speculative scaffolding this codebase has
|
|
56
|
+
* ruled against building.
|
|
57
|
+
*/
|
|
58
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
59
|
+
/** AES-256. */
|
|
60
|
+
const KEY_BYTES = 32;
|
|
61
|
+
/** GCM's standard nonce length (NIST SP 800-38D §8.2). */
|
|
62
|
+
const IV_BYTES = 12;
|
|
63
|
+
/** GCM tag. Full length, never truncated. */
|
|
64
|
+
const TAG_BYTES = 16;
|
|
65
|
+
/**
|
|
66
|
+
* The only version this build writes, and the only one it reads.
|
|
67
|
+
*
|
|
68
|
+
* `0x01` rather than `0x00`: a zero first byte is what an all-zero buffer and a truncated read both
|
|
69
|
+
* look like, so a version of zero cannot be told from an absent one.
|
|
70
|
+
*/
|
|
71
|
+
export const SESSION_CONTENT_SEAL_V1 = 0x01;
|
|
72
|
+
/** Bytes added to every message: version + IV + tag. */
|
|
73
|
+
export const SESSION_CONTENT_SEAL_OVERHEAD_BYTES = 1 + IV_BYTES + TAG_BYTES;
|
|
74
|
+
/**
|
|
75
|
+
* Encrypt a message body under the agreed session key.
|
|
76
|
+
*
|
|
77
|
+
* Throws on a wrong-width key rather than deriving from whatever it was given: a short key silently
|
|
78
|
+
* accepted is encryption that appears to work under something that is not the agreed secret.
|
|
79
|
+
*/
|
|
80
|
+
export function sealSessionContent(key, plaintext) {
|
|
81
|
+
if (key.length !== KEY_BYTES) {
|
|
82
|
+
throw new Error(`SESSION CONTENT SEAL: the session key must be ${KEY_BYTES} bytes, got ${key.length}. This is a ` +
|
|
83
|
+
"LOCAL defect — the key comes from this side's own agreement, not from the peer. Refusing " +
|
|
84
|
+
"rather than encrypting under something that is not the agreed secret.");
|
|
85
|
+
}
|
|
86
|
+
const iv = new Uint8Array(randomBytes(IV_BYTES));
|
|
87
|
+
const cipher = createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
|
|
88
|
+
const ct = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
|
|
89
|
+
const tag = cipher.getAuthTag();
|
|
90
|
+
return new Uint8Array(Buffer.concat([Buffer.from([SESSION_CONTENT_SEAL_V1]), Buffer.from(iv), ct, tag]));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Decrypt a message body under the agreed session key.
|
|
94
|
+
*
|
|
95
|
+
* **Returns `null` on every failure and never throws** — wrong key, tampered ciphertext, truncated
|
|
96
|
+
* blob, unknown version. The caller cannot tell those apart and must not try: GCM's tag check is the
|
|
97
|
+
* only thing that distinguishes "not for us" from "modified in flight", and a caller that branched
|
|
98
|
+
* on the difference would be branching on attacker-controlled input.
|
|
99
|
+
*
|
|
100
|
+
* The caller's job on `null` is to refuse the message loudly, not to retry or to fall back to
|
|
101
|
+
* reading it as plaintext.
|
|
102
|
+
*/
|
|
103
|
+
export function openSessionContent(key, blob) {
|
|
104
|
+
if (key.length !== KEY_BYTES)
|
|
105
|
+
return null;
|
|
106
|
+
// A blob must have room for version + IV + tag, and one byte cannot be assumed present: an empty
|
|
107
|
+
// frame would otherwise index past the end and read `undefined` as a version.
|
|
108
|
+
if (blob.length < SESSION_CONTENT_SEAL_OVERHEAD_BYTES)
|
|
109
|
+
return null;
|
|
110
|
+
if (blob[0] !== SESSION_CONTENT_SEAL_V1)
|
|
111
|
+
return null;
|
|
112
|
+
const iv = blob.subarray(1, 1 + IV_BYTES);
|
|
113
|
+
const ct = blob.subarray(1 + IV_BYTES, blob.length - TAG_BYTES);
|
|
114
|
+
const tag = blob.subarray(blob.length - TAG_BYTES);
|
|
115
|
+
try {
|
|
116
|
+
const decipher = createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
|
|
117
|
+
decipher.setAuthTag(Buffer.from(tag));
|
|
118
|
+
return new Uint8Array(Buffer.concat([decipher.update(Buffer.from(ct)), decipher.final()]));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=session-content-seal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-content-seal.js","sourceRoot":"","sources":["../src/session-content-seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5E,eAAe;AACf,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,0DAA0D;AAC1D,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,6CAA6C;AAC7C,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,wDAAwD;AACxD,MAAM,CAAC,MAAM,mCAAmC,GAAG,CAAC,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAe,EAAE,SAAqB;IACvE,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,iDAAiD,SAAS,eAAe,GAAG,CAAC,MAAM,cAAc;YACjG,2FAA2F;YAC3F,uEAAuE,CACxE,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAChF,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAClF,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,IAAI,UAAU,CACnB,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,uBAAuB,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAClF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAe,EAAE,IAAgB;IAClE,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1C,iGAAiG;IACjG,8EAA8E;IAC9E,IAAI,IAAI,CAAC,MAAM,GAAG,mCAAmC;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,uBAAuB;QAAE,OAAO,IAAI,CAAC;IAErD,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC;IAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACtC,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-M15-EPHEMERAL-AUTH-1 — binding the throwaway key to the agent's identity.
|
|
3
|
+
*
|
|
4
|
+
* ─── The attack this closes, step by step ──────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* The key agreement mixes two throwaway keys into a shared secret, and the secret itself never
|
|
7
|
+
* crosses the wire. But if nobody SIGNS the throwaway key, an arriving one carries no evidence of
|
|
8
|
+
* who sent it:
|
|
9
|
+
*
|
|
10
|
+
* 1. Alice sends her throwaway public key. The relay is carrying the traffic.
|
|
11
|
+
* 2. The relay keeps Alice's and forwards its OWN key to Bob instead.
|
|
12
|
+
* 3. It does the same in the other direction.
|
|
13
|
+
* 4. It now shares one secret with Alice and a different one with Bob.
|
|
14
|
+
* 5. It decrypts everything, reads it, re-encrypts it, and passes it on. Neither side sees
|
|
15
|
+
* anything wrong, because both halves of the conversation decrypt perfectly.
|
|
16
|
+
*
|
|
17
|
+
* So the unauthenticated layer stops a PASSIVE recorder — which is what harvest-now-decrypt-later
|
|
18
|
+
* is about — and does nothing against the party actually carrying the messages. **And we run the
|
|
19
|
+
* relays**, so without this the guarantee reduces to "trust us", which is the one thing CELLO exists
|
|
20
|
+
* so nobody has to do.
|
|
21
|
+
*
|
|
22
|
+
* ─── What is signed, and why the session id is in it ───────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* message = "cello/session/v1/ephemeral" || sessionId || ephemeralPublic
|
|
25
|
+
*
|
|
26
|
+
* The LABEL makes this signature unusable as any other kind of CELLO signature, and vice versa: an
|
|
27
|
+
* agent signs many things, and a signature that is valid in two contexts is a signature an attacker
|
|
28
|
+
* can move between them.
|
|
29
|
+
*
|
|
30
|
+
* The SESSION ID binds the ephemeral to the conversation it was minted for. Without it, a signed
|
|
31
|
+
* ephemeral captured from one session could be replayed into another between the same two agents —
|
|
32
|
+
* the signature verifies, both sides derive, and the relay that replayed it knows the secret from
|
|
33
|
+
* the session it harvested. The session id is length-prefixed so that `sessionId ‖ ephemeral` cannot
|
|
34
|
+
* be re-split at a different point to produce the same bytes from different inputs.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ WHAT THIS DOES NOT DO. It proves the ephemeral came from the holder of that identity key. It
|
|
37
|
+
* does not prove the identity key is the agent you meant to talk to — that is the counterparty
|
|
38
|
+
* identity the caller passes in, and the caller must take it from what the OPERATOR asked for, never
|
|
39
|
+
* from a value the directory or the relay handed back. Verifying against a key the attacker chose is
|
|
40
|
+
* relocating the trust rather than closing it.
|
|
41
|
+
*/
|
|
42
|
+
/** Ed25519 signatures are 64 bytes; X25519 publics are 32. */
|
|
43
|
+
export declare const EPHEMERAL_SIG_BYTES = 64;
|
|
44
|
+
export declare const EPHEMERAL_PUBLIC_BYTES = 32;
|
|
45
|
+
/**
|
|
46
|
+
* The exact bytes both sides sign and verify.
|
|
47
|
+
*
|
|
48
|
+
* Written once and used by BOTH directions on purpose. A signer and a verifier that each build the
|
|
49
|
+
* message from their own reading of a spec is how two implementations end up disagreeing about a
|
|
50
|
+
* byte and rejecting each other for a reason neither can see.
|
|
51
|
+
*/
|
|
52
|
+
export declare function ephemeralSigningMessage(sessionId: Uint8Array, ephemeralPublic: Uint8Array): Uint8Array;
|
|
53
|
+
/** Sign this side's throwaway public key with the agent's long-term identity key. */
|
|
54
|
+
export declare function signSessionEphemeral(signer: {
|
|
55
|
+
sign(data: Uint8Array): Promise<Uint8Array>;
|
|
56
|
+
}, sessionId: Uint8Array, ephemeralPublic: Uint8Array): Promise<Uint8Array>;
|
|
57
|
+
/**
|
|
58
|
+
* Why a peer's ephemeral was refused. A CLOSED set — a free-form string is what lets a new code
|
|
59
|
+
* reach a caller with nothing to act on (`refusal-reasons.ts` records what that cost).
|
|
60
|
+
*/
|
|
61
|
+
export declare const EPHEMERAL_AUTH_REFUSALS: {
|
|
62
|
+
/** No signature at all. */
|
|
63
|
+
readonly SIGNATURE_MISSING: "ephemeral_signature_missing";
|
|
64
|
+
/** Present but the wrong width, or the public key is the wrong width. */
|
|
65
|
+
readonly MALFORMED: "ephemeral_malformed";
|
|
66
|
+
/** Verified against the expected identity and did not match. */
|
|
67
|
+
readonly SIGNATURE_MISMATCH: "ephemeral_signature_mismatch";
|
|
68
|
+
};
|
|
69
|
+
export type EphemeralAuthRefusal = (typeof EPHEMERAL_AUTH_REFUSALS)[keyof typeof EPHEMERAL_AUTH_REFUSALS];
|
|
70
|
+
export type EphemeralAuthResult = {
|
|
71
|
+
ok: true;
|
|
72
|
+
} | {
|
|
73
|
+
ok: false;
|
|
74
|
+
reason: EphemeralAuthRefusal;
|
|
75
|
+
detail: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* VERIFY A PEER'S EPHEMERAL BEFORE ANYTHING IS DERIVED FROM IT.
|
|
79
|
+
*
|
|
80
|
+
* 🚨 MISSING, MALFORMED AND MISMATCHED ALL FAIL. They are separate REASONS because they send an
|
|
81
|
+
* operator to different places, and they are the same OUTCOME because an attacker evading a mismatch
|
|
82
|
+
* check simply supplies no signature at all. "We could not tell" must never be more forgiving than
|
|
83
|
+
* "we proved it wrong" — that is the hole, not the mismatch.
|
|
84
|
+
*
|
|
85
|
+
* Called BEFORE `deriveSessionSecrets`, never alongside it. Deriving first and checking after means
|
|
86
|
+
* a key exists, briefly, that was agreed with someone unproven — and every "briefly" in this
|
|
87
|
+
* codebase eventually turns out to be long enough for something to read it.
|
|
88
|
+
*/
|
|
89
|
+
export declare function verifySessionEphemeral(opts: {
|
|
90
|
+
/** The identity we EXPECT — from what the operator asked for, never from the directory or relay. */
|
|
91
|
+
expectedIdentityPublic: Uint8Array;
|
|
92
|
+
sessionId: Uint8Array;
|
|
93
|
+
peerEphemeralPublic: Uint8Array | undefined;
|
|
94
|
+
peerSignature: Uint8Array | undefined;
|
|
95
|
+
}): EphemeralAuthResult;
|
|
96
|
+
//# sourceMappingURL=session-ephemeral-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-ephemeral-auth.d.ts","sourceRoot":"","sources":["../src/session-ephemeral-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AASH,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,GAAG,UAAU,CActG;AAED,qFAAqF;AACrF,wBAAsB,oBAAoB,CACxC,MAAM,EAAE;IAAE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;CAAE,EACvD,SAAS,EAAE,UAAU,EACrB,eAAe,EAAE,UAAU,GAC1B,OAAO,CAAC,UAAU,CAAC,CAErB;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB;IAClC,2BAA2B;;IAE3B,yEAAyE;;IAEzE,gEAAgE;;CAExD,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAC9B,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAEzE,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,oGAAoG;IACpG,sBAAsB,EAAE,UAAU,CAAC;IACnC,SAAS,EAAE,UAAU,CAAC;IACtB,mBAAmB,EAAE,UAAU,GAAG,SAAS,CAAC;IAC5C,aAAa,EAAE,UAAU,GAAG,SAAS,CAAC;CACvC,GAAG,mBAAmB,CAwCtB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-M15-EPHEMERAL-AUTH-1 — binding the throwaway key to the agent's identity.
|
|
3
|
+
*
|
|
4
|
+
* ─── The attack this closes, step by step ──────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* The key agreement mixes two throwaway keys into a shared secret, and the secret itself never
|
|
7
|
+
* crosses the wire. But if nobody SIGNS the throwaway key, an arriving one carries no evidence of
|
|
8
|
+
* who sent it:
|
|
9
|
+
*
|
|
10
|
+
* 1. Alice sends her throwaway public key. The relay is carrying the traffic.
|
|
11
|
+
* 2. The relay keeps Alice's and forwards its OWN key to Bob instead.
|
|
12
|
+
* 3. It does the same in the other direction.
|
|
13
|
+
* 4. It now shares one secret with Alice and a different one with Bob.
|
|
14
|
+
* 5. It decrypts everything, reads it, re-encrypts it, and passes it on. Neither side sees
|
|
15
|
+
* anything wrong, because both halves of the conversation decrypt perfectly.
|
|
16
|
+
*
|
|
17
|
+
* So the unauthenticated layer stops a PASSIVE recorder — which is what harvest-now-decrypt-later
|
|
18
|
+
* is about — and does nothing against the party actually carrying the messages. **And we run the
|
|
19
|
+
* relays**, so without this the guarantee reduces to "trust us", which is the one thing CELLO exists
|
|
20
|
+
* so nobody has to do.
|
|
21
|
+
*
|
|
22
|
+
* ─── What is signed, and why the session id is in it ───────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* message = "cello/session/v1/ephemeral" || sessionId || ephemeralPublic
|
|
25
|
+
*
|
|
26
|
+
* The LABEL makes this signature unusable as any other kind of CELLO signature, and vice versa: an
|
|
27
|
+
* agent signs many things, and a signature that is valid in two contexts is a signature an attacker
|
|
28
|
+
* can move between them.
|
|
29
|
+
*
|
|
30
|
+
* The SESSION ID binds the ephemeral to the conversation it was minted for. Without it, a signed
|
|
31
|
+
* ephemeral captured from one session could be replayed into another between the same two agents —
|
|
32
|
+
* the signature verifies, both sides derive, and the relay that replayed it knows the secret from
|
|
33
|
+
* the session it harvested. The session id is length-prefixed so that `sessionId ‖ ephemeral` cannot
|
|
34
|
+
* be re-split at a different point to produce the same bytes from different inputs.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ WHAT THIS DOES NOT DO. It proves the ephemeral came from the holder of that identity key. It
|
|
37
|
+
* does not prove the identity key is the agent you meant to talk to — that is the counterparty
|
|
38
|
+
* identity the caller passes in, and the caller must take it from what the OPERATOR asked for, never
|
|
39
|
+
* from a value the directory or the relay handed back. Verifying against a key the attacker chose is
|
|
40
|
+
* relocating the trust rather than closing it.
|
|
41
|
+
*/
|
|
42
|
+
import { verify as edVerify } from "./ed25519.js";
|
|
43
|
+
const ENC = new TextEncoder();
|
|
44
|
+
/** Domain separation. Versioned, so a future change is a new label rather than a reinterpretation. */
|
|
45
|
+
const EPHEMERAL_SIG_LABEL = ENC.encode("cello/session/v1/ephemeral");
|
|
46
|
+
/** Ed25519 signatures are 64 bytes; X25519 publics are 32. */
|
|
47
|
+
export const EPHEMERAL_SIG_BYTES = 64;
|
|
48
|
+
export const EPHEMERAL_PUBLIC_BYTES = 32;
|
|
49
|
+
/**
|
|
50
|
+
* The exact bytes both sides sign and verify.
|
|
51
|
+
*
|
|
52
|
+
* Written once and used by BOTH directions on purpose. A signer and a verifier that each build the
|
|
53
|
+
* message from their own reading of a spec is how two implementations end up disagreeing about a
|
|
54
|
+
* byte and rejecting each other for a reason neither can see.
|
|
55
|
+
*/
|
|
56
|
+
export function ephemeralSigningMessage(sessionId, ephemeralPublic) {
|
|
57
|
+
// The session id is LENGTH-PREFIXED (4 bytes, big-endian). Concatenating two variable-length
|
|
58
|
+
// fields lets a crafted pair re-split at a different boundary and produce identical bytes from
|
|
59
|
+
// different inputs — the signature would then cover something other than what it appears to.
|
|
60
|
+
const out = new Uint8Array(EPHEMERAL_SIG_LABEL.length + 4 + sessionId.length + ephemeralPublic.length);
|
|
61
|
+
let o = 0;
|
|
62
|
+
out.set(EPHEMERAL_SIG_LABEL, o);
|
|
63
|
+
o += EPHEMERAL_SIG_LABEL.length;
|
|
64
|
+
out[o++] = (sessionId.length >>> 24) & 0xff;
|
|
65
|
+
out[o++] = (sessionId.length >>> 16) & 0xff;
|
|
66
|
+
out[o++] = (sessionId.length >>> 8) & 0xff;
|
|
67
|
+
out[o++] = sessionId.length & 0xff;
|
|
68
|
+
out.set(sessionId, o);
|
|
69
|
+
o += sessionId.length;
|
|
70
|
+
out.set(ephemeralPublic, o);
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** Sign this side's throwaway public key with the agent's long-term identity key. */
|
|
74
|
+
export async function signSessionEphemeral(signer, sessionId, ephemeralPublic) {
|
|
75
|
+
return signer.sign(ephemeralSigningMessage(sessionId, ephemeralPublic));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Why a peer's ephemeral was refused. A CLOSED set — a free-form string is what lets a new code
|
|
79
|
+
* reach a caller with nothing to act on (`refusal-reasons.ts` records what that cost).
|
|
80
|
+
*/
|
|
81
|
+
export const EPHEMERAL_AUTH_REFUSALS = {
|
|
82
|
+
/** No signature at all. */
|
|
83
|
+
SIGNATURE_MISSING: "ephemeral_signature_missing",
|
|
84
|
+
/** Present but the wrong width, or the public key is the wrong width. */
|
|
85
|
+
MALFORMED: "ephemeral_malformed",
|
|
86
|
+
/** Verified against the expected identity and did not match. */
|
|
87
|
+
SIGNATURE_MISMATCH: "ephemeral_signature_mismatch",
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* VERIFY A PEER'S EPHEMERAL BEFORE ANYTHING IS DERIVED FROM IT.
|
|
91
|
+
*
|
|
92
|
+
* 🚨 MISSING, MALFORMED AND MISMATCHED ALL FAIL. They are separate REASONS because they send an
|
|
93
|
+
* operator to different places, and they are the same OUTCOME because an attacker evading a mismatch
|
|
94
|
+
* check simply supplies no signature at all. "We could not tell" must never be more forgiving than
|
|
95
|
+
* "we proved it wrong" — that is the hole, not the mismatch.
|
|
96
|
+
*
|
|
97
|
+
* Called BEFORE `deriveSessionSecrets`, never alongside it. Deriving first and checking after means
|
|
98
|
+
* a key exists, briefly, that was agreed with someone unproven — and every "briefly" in this
|
|
99
|
+
* codebase eventually turns out to be long enough for something to read it.
|
|
100
|
+
*/
|
|
101
|
+
export function verifySessionEphemeral(opts) {
|
|
102
|
+
if (opts.peerSignature === undefined) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
reason: EPHEMERAL_AUTH_REFUSALS.SIGNATURE_MISSING,
|
|
106
|
+
detail: "the peer sent a session key with no signature over it, so there is nothing to tie it to " +
|
|
107
|
+
"your counterparty. An unsigned key is exactly what a relay substituting its own would send, " +
|
|
108
|
+
"and it is refused on the same path as a wrong signature — a check that is lenient about a " +
|
|
109
|
+
"missing proof is a check an attacker skips.",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (opts.peerSignature.length !== EPHEMERAL_SIG_BYTES ||
|
|
113
|
+
opts.peerEphemeralPublic === undefined ||
|
|
114
|
+
opts.peerEphemeralPublic.length !== EPHEMERAL_PUBLIC_BYTES) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
reason: EPHEMERAL_AUTH_REFUSALS.MALFORMED,
|
|
118
|
+
detail: `the peer's session key material is the wrong shape — signature ${opts.peerSignature.length} ` +
|
|
119
|
+
`bytes (expected ${EPHEMERAL_SIG_BYTES}), key ${opts.peerEphemeralPublic?.length ?? 0} bytes ` +
|
|
120
|
+
`(expected ${EPHEMERAL_PUBLIC_BYTES}). Refusing rather than padding: a short value silently ` +
|
|
121
|
+
"zero-extended is an agreement with something that is not the peer.",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const message = ephemeralSigningMessage(opts.sessionId, opts.peerEphemeralPublic);
|
|
125
|
+
if (!edVerify(opts.expectedIdentityPublic, message, opts.peerSignature)) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
reason: EPHEMERAL_AUTH_REFUSALS.SIGNATURE_MISMATCH,
|
|
129
|
+
detail: "the peer's session key is signed, but not by the counterparty this session is with. That " +
|
|
130
|
+
"is what a relay substituting its own key looks like: it would then share one secret with " +
|
|
131
|
+
"you and another with them, and read everything in between while both sides saw a working " +
|
|
132
|
+
"conversation. Refusing.",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { ok: true };
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=session-ephemeral-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-ephemeral-auth.js","sourceRoot":"","sources":["../src/session-ephemeral-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,16 +54,20 @@
|
|
|
54
54
|
*
|
|
55
55
|
* ─── WHAT THIS DOES NOT DEFEND AGAINST, stated plainly (review F6) ─────────────────────────────
|
|
56
56
|
*
|
|
57
|
-
* The ephemerals
|
|
58
|
-
*
|
|
57
|
+
* The ephemerals reaching THIS FUNCTION are raw public keys — nothing in this API takes an identity
|
|
58
|
+
* key, so it cannot tell whose they are. On its own that is sufficient only against the threat the
|
|
59
|
+
* DoD line names: harvest-now-decrypt-later, i.e. a PASSIVE recorder, which is what a relay storing
|
|
60
|
+
* traffic is. It is NOT sufficient against an ACTIVE on-path relay, which substitutes both
|
|
61
|
+
* ephemerals and reads everything while both ends see a working conversation.
|
|
59
62
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
63
|
+
* ✅ THE SYSTEM DOES DEFEND AGAINST THAT NOW — `DOD-M15-EPHEMERAL-AUTH-1` shipped it. The ephemeral
|
|
64
|
+
* public is signed with the agent's Ed25519 identity, and `verifySessionEphemeral` checks the peer's
|
|
65
|
+
* against the counterparty identity the SESSION was opened with, BEFORE this function is called. A
|
|
66
|
+
* failure stops the session rather than continuing in the open.
|
|
64
67
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
68
|
+
* The distinction is kept rather than replaced with "MITM is covered", because it is a property of
|
|
69
|
+
* the CALL ORDER and not of this module: a future caller that derives before verifying has an
|
|
70
|
+
* agreement with whoever sent the bytes, and this file would not notice.
|
|
67
71
|
*
|
|
68
72
|
* ─── ONE OUTPUT. The salt used to live here, and that was the defect. ─────────────────────────
|
|
69
73
|
*
|
|
@@ -83,12 +87,49 @@
|
|
|
83
87
|
*
|
|
84
88
|
* ─── The remaining output, and its lifetime ───────────────────────────────────────────────────
|
|
85
89
|
*
|
|
86
|
-
**The envelope key NEVER touches disk
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
90
|
+
**The envelope key NEVER touches disk**, and `destroySessionEphemeral` is how a caller discards the
|
|
91
|
+
* secret behind it at session close. A revived session RE-KEYS (Decisions Carried #5) — and because
|
|
92
|
+
* the salt no longer rides on this secret, re-keying no longer disturbs the transcript's
|
|
93
|
+
* verifiability.
|
|
94
|
+
*
|
|
95
|
+
* ⚠️ ALL THREE ARE WIRED NOW, and the history is kept because this file has been wrong in BOTH
|
|
96
|
+
* directions. It first claimed *"that is the forward secrecy, and `destroySessionEphemeral` is what
|
|
97
|
+
* makes it real"* while nothing called it. The correction over-swung to *"nothing in the daemon
|
|
98
|
+
* mints an ephemeral, derives a content key, or destroys one"*, which `006-CRYPTO` made two thirds
|
|
99
|
+
* false the same day. Both were confident; neither was measured.
|
|
100
|
+
*
|
|
101
|
+
* What is true today, and it is checkable in one grep each:
|
|
102
|
+
*
|
|
103
|
+
* `generateSessionEphemeral` / `destroySessionEphemeral` — `session-node-manager.ts` mints one
|
|
104
|
+
* per session at activation and destroys it at every site that drops the session's entry, and at
|
|
105
|
+
* shutdown (`006-CRYPTO`).
|
|
106
|
+
* `deriveSessionSecrets` — called from the same file, on an inbound signed ephemeral, AFTER the
|
|
107
|
+
* peer's signature is verified against the counterparty identity the session was opened with
|
|
108
|
+
* (`007-CRYPTO`). Its output encrypts the message body in `session-content-seal.ts`.
|
|
109
|
+
*
|
|
110
|
+
* ─── WHAT THIS FILE ALONE STILL DOES NOT DEFEND AGAINST ───────────────────────────────────────
|
|
111
|
+
*
|
|
112
|
+
* The ephemerals ARRIVING here are unauthenticated — this module takes raw public keys and cannot
|
|
113
|
+
* tell whose they are. That check is real but it lives one layer up, in
|
|
114
|
+
* `session-ephemeral-auth.ts`, which the daemon runs BEFORE calling this. So the property holds for
|
|
115
|
+
* the system and not for this function: a caller that derives from an unverified peer key has an
|
|
116
|
+
* agreement with whoever sent it, and against an on-path relay that is a relay who can read
|
|
117
|
+
* everything.
|
|
118
|
+
*
|
|
119
|
+
* Said this precisely because `session-salt.ts` states its own reachability boundary and this file
|
|
120
|
+
* once stated none, so the two halves of the same exchange read as though both were live.
|
|
121
|
+
*
|
|
122
|
+
* ─── The key and the salt must never be EQUAL, and what actually keeps them apart ──────────────
|
|
123
|
+
*
|
|
124
|
+
* The salt travels wherever a content hash does and the relay sees it, so a salt that equalled the
|
|
125
|
+
* key would hand the key to everyone who can see a hash.
|
|
126
|
+
*
|
|
127
|
+
* An earlier version said *"domain separation by label is what keeps them independent."* That was
|
|
128
|
+
* true when the salt was a second HKDF output of this function and is not true now. The salt is
|
|
129
|
+
* computed in `session-salt.ts` from a DIFFERENT input — the two sides' random contributions, not
|
|
130
|
+
* this ECDH secret — under its own label, with no HKDF salt. Different inputs, different module,
|
|
131
|
+
* different function. Label separation is not the mechanism; it is not even reachable, because
|
|
132
|
+
* there is only one label here.
|
|
92
133
|
*/
|
|
93
134
|
export declare const SESSION_KEY_BYTES = 32;
|
|
94
135
|
export interface SessionEphemeral {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-key-agreement.d.ts","sourceRoot":"","sources":["../src/session-key-agreement.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-key-agreement.d.ts","sourceRoot":"","sources":["../src/session-key-agreement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoIG;AAQH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAepC,MAAM,WAAW,gBAAgB;IAC/B,sGAAsG;IACtG,SAAS,EAAE,UAAU,CAAC;IACtB,gEAAgE;IAChE,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,UAAU,EAAE,UAAU,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,gBAAgB,CAG3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAEjE;AAmBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,kBAAkB,EAAE,UAAU,CAAC;IAC/B,mBAAmB,EAAE,UAAU,CAAC;IAChC,oEAAoE;IACpE,SAAS,EAAE,UAAU,CAAC;IACtB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAC/B;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,UAAU,CAAC;CAC3B,GAAG,cAAc,CAgJjB"}
|
|
@@ -54,16 +54,20 @@
|
|
|
54
54
|
*
|
|
55
55
|
* ─── WHAT THIS DOES NOT DEFEND AGAINST, stated plainly (review F6) ─────────────────────────────
|
|
56
56
|
*
|
|
57
|
-
* The ephemerals
|
|
58
|
-
*
|
|
57
|
+
* The ephemerals reaching THIS FUNCTION are raw public keys — nothing in this API takes an identity
|
|
58
|
+
* key, so it cannot tell whose they are. On its own that is sufficient only against the threat the
|
|
59
|
+
* DoD line names: harvest-now-decrypt-later, i.e. a PASSIVE recorder, which is what a relay storing
|
|
60
|
+
* traffic is. It is NOT sufficient against an ACTIVE on-path relay, which substitutes both
|
|
61
|
+
* ephemerals and reads everything while both ends see a working conversation.
|
|
59
62
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
63
|
+
* ✅ THE SYSTEM DOES DEFEND AGAINST THAT NOW — `DOD-M15-EPHEMERAL-AUTH-1` shipped it. The ephemeral
|
|
64
|
+
* public is signed with the agent's Ed25519 identity, and `verifySessionEphemeral` checks the peer's
|
|
65
|
+
* against the counterparty identity the SESSION was opened with, BEFORE this function is called. A
|
|
66
|
+
* failure stops the session rather than continuing in the open.
|
|
64
67
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
68
|
+
* The distinction is kept rather than replaced with "MITM is covered", because it is a property of
|
|
69
|
+
* the CALL ORDER and not of this module: a future caller that derives before verifying has an
|
|
70
|
+
* agreement with whoever sent the bytes, and this file would not notice.
|
|
67
71
|
*
|
|
68
72
|
* ─── ONE OUTPUT. The salt used to live here, and that was the defect. ─────────────────────────
|
|
69
73
|
*
|
|
@@ -83,12 +87,49 @@
|
|
|
83
87
|
*
|
|
84
88
|
* ─── The remaining output, and its lifetime ───────────────────────────────────────────────────
|
|
85
89
|
*
|
|
86
|
-
**The envelope key NEVER touches disk
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
90
|
+
**The envelope key NEVER touches disk**, and `destroySessionEphemeral` is how a caller discards the
|
|
91
|
+
* secret behind it at session close. A revived session RE-KEYS (Decisions Carried #5) — and because
|
|
92
|
+
* the salt no longer rides on this secret, re-keying no longer disturbs the transcript's
|
|
93
|
+
* verifiability.
|
|
94
|
+
*
|
|
95
|
+
* ⚠️ ALL THREE ARE WIRED NOW, and the history is kept because this file has been wrong in BOTH
|
|
96
|
+
* directions. It first claimed *"that is the forward secrecy, and `destroySessionEphemeral` is what
|
|
97
|
+
* makes it real"* while nothing called it. The correction over-swung to *"nothing in the daemon
|
|
98
|
+
* mints an ephemeral, derives a content key, or destroys one"*, which `006-CRYPTO` made two thirds
|
|
99
|
+
* false the same day. Both were confident; neither was measured.
|
|
100
|
+
*
|
|
101
|
+
* What is true today, and it is checkable in one grep each:
|
|
102
|
+
*
|
|
103
|
+
* `generateSessionEphemeral` / `destroySessionEphemeral` — `session-node-manager.ts` mints one
|
|
104
|
+
* per session at activation and destroys it at every site that drops the session's entry, and at
|
|
105
|
+
* shutdown (`006-CRYPTO`).
|
|
106
|
+
* `deriveSessionSecrets` — called from the same file, on an inbound signed ephemeral, AFTER the
|
|
107
|
+
* peer's signature is verified against the counterparty identity the session was opened with
|
|
108
|
+
* (`007-CRYPTO`). Its output encrypts the message body in `session-content-seal.ts`.
|
|
109
|
+
*
|
|
110
|
+
* ─── WHAT THIS FILE ALONE STILL DOES NOT DEFEND AGAINST ───────────────────────────────────────
|
|
111
|
+
*
|
|
112
|
+
* The ephemerals ARRIVING here are unauthenticated — this module takes raw public keys and cannot
|
|
113
|
+
* tell whose they are. That check is real but it lives one layer up, in
|
|
114
|
+
* `session-ephemeral-auth.ts`, which the daemon runs BEFORE calling this. So the property holds for
|
|
115
|
+
* the system and not for this function: a caller that derives from an unverified peer key has an
|
|
116
|
+
* agreement with whoever sent it, and against an on-path relay that is a relay who can read
|
|
117
|
+
* everything.
|
|
118
|
+
*
|
|
119
|
+
* Said this precisely because `session-salt.ts` states its own reachability boundary and this file
|
|
120
|
+
* once stated none, so the two halves of the same exchange read as though both were live.
|
|
121
|
+
*
|
|
122
|
+
* ─── The key and the salt must never be EQUAL, and what actually keeps them apart ──────────────
|
|
123
|
+
*
|
|
124
|
+
* The salt travels wherever a content hash does and the relay sees it, so a salt that equalled the
|
|
125
|
+
* key would hand the key to everyone who can see a hash.
|
|
126
|
+
*
|
|
127
|
+
* An earlier version said *"domain separation by label is what keeps them independent."* That was
|
|
128
|
+
* true when the salt was a second HKDF output of this function and is not true now. The salt is
|
|
129
|
+
* computed in `session-salt.ts` from a DIFFERENT input — the two sides' random contributions, not
|
|
130
|
+
* this ECDH secret — under its own label, with no HKDF salt. Different inputs, different module,
|
|
131
|
+
* different function. Label separation is not the mechanism; it is not even reachable, because
|
|
132
|
+
* there is only one label here.
|
|
92
133
|
*/
|
|
93
134
|
import { x25519 } from "@noble/curves/ed25519.js";
|
|
94
135
|
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
@@ -98,9 +139,14 @@ const X25519_KEY_BYTES = 32;
|
|
|
98
139
|
export const SESSION_KEY_BYTES = 32;
|
|
99
140
|
const ENC = new TextEncoder();
|
|
100
141
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
142
|
+
* THE ONE label. Versioned so a future derivation change is a new label rather than a silent
|
|
143
|
+
* reinterpretation of the same bytes.
|
|
144
|
+
*
|
|
145
|
+
* An earlier version read *"distinct labels are the ONLY thing separating the two outputs — same
|
|
146
|
+
* IKM, same salt, same binding."* There are no longer two outputs to separate: the content-hash
|
|
147
|
+
* salt moved to `session-salt.ts` and is derived from different inputs entirely (Decisions Carried
|
|
148
|
+
* #8). The label still earns its place — it is bound into `info`, so it is what a future second
|
|
149
|
+
* output WOULD be separated by — but it is not currently holding two values apart.
|
|
104
150
|
*/
|
|
105
151
|
const INFO_CONTENT_KEY = ENC.encode("cello/session/v1/content-key");
|
|
106
152
|
/**
|
|
@@ -150,10 +196,11 @@ export function deriveSessionSecrets(opts) {
|
|
|
150
196
|
if (opts.peerEphemeralPublic.length !== X25519_KEY_BYTES) {
|
|
151
197
|
throw new Error(`KEYAGREE: peer ephemeral public key must be ${X25519_KEY_BYTES} bytes, got ${opts.peerEphemeralPublic.length}. ` +
|
|
152
198
|
"Refusing rather than padding — a short key silently zero-extended is an agreement with " +
|
|
153
|
-
"something that is not the peer. This check ALSO
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
199
|
+
"something that is not the peer. This check ALSO keeps the HKDF `info` unambiguous (review " +
|
|
200
|
+
"F9): with a fixed-length label, two exactly-32-byte public keys and a trailing transcript, " +
|
|
201
|
+
"no two different (keys, transcript) inputs can encode to the same info bytes. A " +
|
|
202
|
+
"variable-length public would break that, and two peers whose info collided would derive the " +
|
|
203
|
+
"same key from different material.");
|
|
157
204
|
}
|
|
158
205
|
if (opts.ownEphemeralSecret.length !== X25519_KEY_BYTES) {
|
|
159
206
|
// Review F11: symmetric with the peer check above. `@noble` catches it, but names its own
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-key-agreement.js","sourceRoot":"","sources":["../src/session-key-agreement.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-key-agreement.js","sourceRoot":"","sources":["../src/session-key-agreement.ts"],"names":[],"mappings":"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"}
|