@synoi/sraid 0.2.0

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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/PROJECTION_SPEC.md +245 -0
  3. package/README.md +149 -0
  4. package/SPEC.md +216 -0
  5. package/dist/attestation.d.ts +92 -0
  6. package/dist/attestation.d.ts.map +1 -0
  7. package/dist/attestation.js +155 -0
  8. package/dist/attestation.js.map +1 -0
  9. package/dist/authority.d.ts +351 -0
  10. package/dist/authority.d.ts.map +1 -0
  11. package/dist/authority.js +563 -0
  12. package/dist/authority.js.map +1 -0
  13. package/dist/canonicalize.d.ts +70 -0
  14. package/dist/canonicalize.d.ts.map +1 -0
  15. package/dist/canonicalize.js +151 -0
  16. package/dist/canonicalize.js.map +1 -0
  17. package/dist/ed25519.d.ts +26 -0
  18. package/dist/ed25519.d.ts.map +1 -0
  19. package/dist/ed25519.js +53 -0
  20. package/dist/ed25519.js.map +1 -0
  21. package/dist/index.d.ts +28 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +46 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/internal/base64.d.ts +28 -0
  26. package/dist/internal/base64.d.ts.map +1 -0
  27. package/dist/internal/base64.js +45 -0
  28. package/dist/internal/base64.js.map +1 -0
  29. package/dist/internal/key-cache.d.ts +47 -0
  30. package/dist/internal/key-cache.d.ts.map +1 -0
  31. package/dist/internal/key-cache.js +77 -0
  32. package/dist/internal/key-cache.js.map +1 -0
  33. package/dist/lineage.d.ts +104 -0
  34. package/dist/lineage.d.ts.map +1 -0
  35. package/dist/lineage.js +0 -0
  36. package/dist/lineage.js.map +1 -0
  37. package/dist/mldsa.d.ts +41 -0
  38. package/dist/mldsa.d.ts.map +1 -0
  39. package/dist/mldsa.js +119 -0
  40. package/dist/mldsa.js.map +1 -0
  41. package/dist/oid.d.ts +109 -0
  42. package/dist/oid.d.ts.map +1 -0
  43. package/dist/oid.js +140 -0
  44. package/dist/oid.js.map +1 -0
  45. package/dist/sensitivity.d.ts +104 -0
  46. package/dist/sensitivity.d.ts.map +1 -0
  47. package/dist/sensitivity.js +117 -0
  48. package/dist/sensitivity.js.map +1 -0
  49. package/dist/signature.d.ts +46 -0
  50. package/dist/signature.d.ts.map +1 -0
  51. package/dist/signature.js +89 -0
  52. package/dist/signature.js.map +1 -0
  53. package/dist/types.d.ts +313 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +16 -0
  56. package/dist/types.js.map +1 -0
  57. package/dist/validate.d.ts +88 -0
  58. package/dist/validate.d.ts.map +1 -0
  59. package/dist/validate.js +339 -0
  60. package/dist/validate.js.map +1 -0
  61. package/package.json +69 -0
  62. package/src/attestation.ts +204 -0
  63. package/src/authority.ts +849 -0
  64. package/src/canonicalize.ts +167 -0
  65. package/src/ed25519.ts +60 -0
  66. package/src/index.ts +118 -0
  67. package/src/internal/base64.ts +44 -0
  68. package/src/internal/key-cache.ts +79 -0
  69. package/src/lineage.ts +0 -0
  70. package/src/mldsa.ts +131 -0
  71. package/src/oid.ts +146 -0
  72. package/src/sensitivity.ts +154 -0
  73. package/src/signature.ts +119 -0
  74. package/src/types.ts +351 -0
  75. package/src/validate.ts +402 -0
@@ -0,0 +1,563 @@
1
+ /**
2
+ * @synoi/sraid — authority.ts
3
+ *
4
+ * L4 authority VERIFIER for CDRO objects (the authorized axis).
5
+ *
6
+ * This is deliberately more than a shape check. Per the Adversary review
7
+ * (panel A6: "ship a verifier, not just a schema"), an attacker who can
8
+ * attach any well-shaped `authority` block to an object would otherwise
9
+ * forge authorization for free. So this module verifies, locally and
10
+ * offline, the parts of authorization that ARE locally checkable:
11
+ *
12
+ * 1. STRUCTURE — the object carries a present, well-formed authority
13
+ * block (grant reference present; decision verb, if any, is a valid
14
+ * GAP verb; intent ref well-formed).
15
+ * 2. BINDING — the object's authority block actually references the
16
+ * supplied grant (the grant's OID matches `authority.grant_oid`),
17
+ * AND the grant is itself a hash-honest CDRO whose recomputed OID
18
+ * equals its claimed OID (so the grant body cannot be swapped under
19
+ * a fixed OID reference).
20
+ * 3. SIGNATURE — the grant carries a valid hybrid (Ed25519 + ML-DSA-65)
21
+ * signature over its own content core, when grant signing material
22
+ * and verifier public keys are supplied. A grant with no/invalid
23
+ * signature does not authorize.
24
+ * 4. COVERAGE — the grant's capability scopes cover the requested
25
+ * object type / action (dotted-taxonomy match with segment-boundary
26
+ * wildcards), and the grant has not expired relative to the object's
27
+ * creation time. Both are computable from the bytes in hand.
28
+ *
29
+ * What this module does NOT and CANNOT do offline — stated honestly,
30
+ * never silently assumed (CLAIMS_DISCIPLINE):
31
+ *
32
+ * - LIVE REVOCATION — whether the grant has since been revoked.
33
+ * - EXISTENCE — whether the grant OID actually resolves to a
34
+ * published, retrievable grant at all (when the
35
+ * caller did not supply the grant material).
36
+ *
37
+ * Both require the OID Resolver, which is presently undeployed
38
+ * (SRAID_FOUNDATION_PUNCHLIST C). This module DEFINES the resolver
39
+ * interface (`AuthorityResolver`) and, when a resolver is supplied, calls
40
+ * it and folds its answer into the result — but when no resolver is
41
+ * supplied, the result is explicitly marked `revocation_checked: false`
42
+ * and `existence_checked: false` so a caller can never mistake a
43
+ * locally-passing verification for a live one.
44
+ *
45
+ * DELEGATION CHAINS (`verifyDelegationChain`, K2) — VERIFY-ONLY. This module
46
+ * also carries a synchronous, offline delegation-chain verifier. It is
47
+ * deliberately scoped to verification with NO enforcement wiring and NO
48
+ * resolver: the caller supplies the ordered ancestors and the per-link
49
+ * verifier keys, and the function makes NO live claim. It ALWAYS returns
50
+ * `revocation_checked: false`, `not_revoked: false`, and
51
+ * `existence_checked: false` — those fields are LITERAL-TYPED `false` so the
52
+ * type system itself forbids a future edit from quietly asserting a live,
53
+ * resolver-backed claim from an offline function (CLAIMS_DISCIPLINE made
54
+ * structural). Synchronicity is intentional: with no Promise overload there is
55
+ * no place for a resolver call, which is what makes the no-live-claim
56
+ * guarantee structural rather than merely documented.
57
+ */
58
+ import { cdroOid, cdroContentCore } from './oid.js';
59
+ import { verifySignature } from './signature.js';
60
+ import { verifyAttestation } from './attestation.js';
61
+ // ── Valid verb set (mirrors AuthorityDecision in types.ts) ────────────────────
62
+ const VALID_DECISIONS = new Set([
63
+ 'allow',
64
+ 'deny',
65
+ 'defer',
66
+ 'step_up',
67
+ 'delegate',
68
+ 'revoke',
69
+ ]);
70
+ // ── Capability pattern matching ───────────────────────────────────────────────
71
+ /**
72
+ * Match a capability `target` against a grant `pattern`. Pure string logic,
73
+ * re-stated here so L0 stays dependency-free (the same rule lives in
74
+ * `@synoi/gap-types` `capabilityMatches`; L0 must not depend on L3).
75
+ *
76
+ * - exact match → true
77
+ * - '*' → match-all
78
+ * - 'skill.*' matches 'skill.create' and deeper (segment-boundary only).
79
+ * A non-boundary 'admin.us*' must NOT match 'admin.users.delete'
80
+ * (privilege-escalation footgun) — only a '.'-anchored '*' is a wildcard.
81
+ */
82
+ export function capabilityCovers(pattern, target) {
83
+ if (pattern === target)
84
+ return true;
85
+ if (pattern === '*')
86
+ return true;
87
+ if (pattern.endsWith('.*')) {
88
+ const prefix = pattern.slice(0, -1); // keep trailing '.', e.g. 'skill.'
89
+ return target.startsWith(prefix);
90
+ }
91
+ return false;
92
+ }
93
+ const DEFAULT_GRANT_PAYLOAD_TYPE = 'application/vnd.synoi.sraid+json';
94
+ export function verifyAuthority(input) {
95
+ const reasons = [];
96
+ const auth = input.object?.authority;
97
+ // ── 1. STRUCTURE ──────────────────────────────────────────────────────────
98
+ let structure_ok = true;
99
+ if (auth === undefined || auth === null || typeof auth !== 'object') {
100
+ structure_ok = false;
101
+ reasons.push('authority block absent — object asserts no authority');
102
+ }
103
+ else {
104
+ if (auth.grant_oid !== undefined) {
105
+ if (typeof auth.grant_oid !== 'string' || !auth.grant_oid.startsWith('sha256:')) {
106
+ structure_ok = false;
107
+ reasons.push('authority.grant_oid must be a "sha256:" OID');
108
+ }
109
+ }
110
+ else {
111
+ // grant_oid is the only thing that ties an object to an authorizer.
112
+ // Its absence is allowed ONLY for an uncorrelated state-change event,
113
+ // which is the orphan case — not "authorized".
114
+ structure_ok = false;
115
+ reasons.push('authority.grant_oid absent — orphaned / uncorrelated authority');
116
+ }
117
+ if (auth.decision !== undefined &&
118
+ auth.decision !== null &&
119
+ !VALID_DECISIONS.has(auth.decision)) {
120
+ structure_ok = false;
121
+ reasons.push(`authority.decision "${String(auth.decision)}" is not a valid GAP verb`);
122
+ }
123
+ if (auth.intent_oid !== undefined &&
124
+ (typeof auth.intent_oid !== 'string' || !auth.intent_oid.startsWith('sha256:'))) {
125
+ structure_ok = false;
126
+ reasons.push('authority.intent_oid, if present, must be a "sha256:" OID');
127
+ }
128
+ }
129
+ // ── 2-4. BINDING / SIGNATURE / COVERAGE (need the grant) ─────────────────────
130
+ const grant = input.grant;
131
+ const grant_supplied = grant !== undefined && grant !== null;
132
+ let binding_ok = false;
133
+ let signature_ok = false;
134
+ let signature_checked = false;
135
+ let coverage_ok = false;
136
+ if (structure_ok && grant_supplied) {
137
+ // BINDING — recompute the grant's OID over its content core and require it
138
+ // to equal both the grant's own claimed OID and the object's reference.
139
+ // This defeats "swap the grant body under a fixed OID reference".
140
+ let recomputed = null;
141
+ try {
142
+ recomputed = cdroOid(grant);
143
+ }
144
+ catch {
145
+ recomputed = null;
146
+ }
147
+ const claimedOid = grant.oid;
148
+ const referenced = auth?.grant_oid;
149
+ if (recomputed === null) {
150
+ reasons.push('grant content core not hashable');
151
+ }
152
+ else if (recomputed !== claimedOid) {
153
+ reasons.push('grant OID does not match its content (tampered grant body)');
154
+ }
155
+ else if (recomputed !== referenced) {
156
+ reasons.push('object.authority.grant_oid does not reference the supplied grant');
157
+ }
158
+ else {
159
+ binding_ok = true;
160
+ }
161
+ // SIGNATURE — verify the grant's hybrid signature over its content core.
162
+ // BINDING and SIGNATURE must hash identical bytes: both route through
163
+ // cdroContentCore (strips oid, signature, attestation) so they are
164
+ // provably the same bytes.
165
+ if (input.grant_ed25519_pub !== undefined &&
166
+ input.grant_ml_dsa_pub !== undefined) {
167
+ signature_checked = true;
168
+ const att = grant.attestation;
169
+ const sig = grant.signature;
170
+ // Compute the canonical content core once — shared by both paths.
171
+ let canonical = null;
172
+ try {
173
+ canonical = canonicalize(cdroContentCore(grant));
174
+ }
175
+ catch {
176
+ canonical = null;
177
+ }
178
+ if (canonical === null) {
179
+ reasons.push('grant content core not canonicalizable for signature check');
180
+ }
181
+ else if (att) {
182
+ // DSSE attestation path — prefer when present.
183
+ // payload-swap guard: the signed payload MUST equal this grant's content core.
184
+ if (att.payload !== canonical) {
185
+ signature_ok = false;
186
+ reasons.push('grant attestation payload does not match its content core (payload swap)');
187
+ }
188
+ else {
189
+ const expectedPayloadType = input.grant_payload_type ?? DEFAULT_GRANT_PAYLOAD_TYPE;
190
+ const r = verifyAttestation({
191
+ envelope: att,
192
+ ed25519_pub: input.grant_ed25519_pub,
193
+ ml_dsa_pub: input.grant_ml_dsa_pub,
194
+ expectedPayloadType,
195
+ });
196
+ signature_ok = r.valid;
197
+ if (!r.valid)
198
+ reasons.push(`grant attestation invalid: ${r.reasons.join(',')}`);
199
+ }
200
+ }
201
+ else if (sig) {
202
+ // Legacy signature envelope path.
203
+ const r = verifySignature({
204
+ canonical,
205
+ envelope: sig,
206
+ ed25519_pub: input.grant_ed25519_pub,
207
+ ml_dsa_pub: input.grant_ml_dsa_pub,
208
+ });
209
+ signature_ok = r.valid;
210
+ if (!r.valid)
211
+ reasons.push(`grant signature invalid: ${r.reasons.join(',')}`);
212
+ }
213
+ else {
214
+ reasons.push('grant carries no signature or attestation');
215
+ }
216
+ }
217
+ // COVERAGE — scope covers the action, and grant not expired at object time.
218
+ const action = input.action ?? input.object.type;
219
+ const scopes = grant.body?.capability_scopes;
220
+ let covered = false;
221
+ if (Array.isArray(scopes)) {
222
+ for (const s of scopes) {
223
+ if (s && typeof s.capability === 'string' && capabilityCovers(s.capability, action)) {
224
+ covered = true;
225
+ break;
226
+ }
227
+ }
228
+ }
229
+ if (!covered) {
230
+ reasons.push(`grant scope does not cover action "${action}"`);
231
+ }
232
+ const expires = grant.body?.expires_at_ms;
233
+ let notExpired = true;
234
+ if (typeof expires === 'number') {
235
+ if (input.object.created_at_ms > expires) {
236
+ notExpired = false;
237
+ reasons.push('grant had expired at the object creation time');
238
+ }
239
+ }
240
+ coverage_ok = covered && notExpired;
241
+ }
242
+ else if (structure_ok && !grant_supplied) {
243
+ reasons.push('grant not supplied — binding/signature/coverage not checked');
244
+ }
245
+ // Local verdict: every attempted local check passed. Signature counts only
246
+ // if it was checked; resolver checks are handled below.
247
+ const localAuthorized = structure_ok &&
248
+ grant_supplied &&
249
+ binding_ok &&
250
+ coverage_ok &&
251
+ (!signature_checked || signature_ok);
252
+ const base = {
253
+ authorized: localAuthorized,
254
+ structure_ok,
255
+ grant_supplied,
256
+ binding_ok,
257
+ signature_ok,
258
+ signature_checked,
259
+ coverage_ok,
260
+ existence_checked: false,
261
+ existence_ok: false,
262
+ revocation_checked: false,
263
+ not_revoked: false,
264
+ reasons,
265
+ };
266
+ // ── 5. RESOLVER (live revocation + existence) — resolver-dependent ──────────
267
+ if (input.resolver && auth?.grant_oid) {
268
+ const grantOid = auth.grant_oid;
269
+ return Promise.resolve(input.resolver.resolveGrantStatus(grantOid)).then((status) => {
270
+ const existence_ok = status.exists === true;
271
+ const not_revoked = existence_ok && status.revoked !== true;
272
+ if (!existence_ok)
273
+ base.reasons.push('resolver: grant does not exist');
274
+ if (existence_ok && status.revoked === true) {
275
+ base.reasons.push('resolver: grant has been revoked');
276
+ }
277
+ return {
278
+ ...base,
279
+ existence_checked: true,
280
+ existence_ok,
281
+ revocation_checked: true,
282
+ not_revoked,
283
+ authorized: localAuthorized && existence_ok && not_revoked,
284
+ };
285
+ });
286
+ }
287
+ return base;
288
+ }
289
+ // ── verifyDelegationChain (K2) — VERIFY-ONLY delegation-chain verifier ────────
290
+ /**
291
+ * Hard cap on delegation depth. Checked BEFORE any hashing or signature work,
292
+ * so an over-long chain is a cheap rejection (DoS guard) and never triggers
293
+ * crypto. A chain of `links.length > MAX_DELEGATION_DEPTH` fails closed.
294
+ */
295
+ export const MAX_DELEGATION_DEPTH = 8;
296
+ function bytesEqual(a, b) {
297
+ if (a.length !== b.length)
298
+ return false;
299
+ let diff = 0;
300
+ for (let i = 0; i < a.length; i++)
301
+ diff |= a[i] ^ b[i];
302
+ return diff === 0;
303
+ }
304
+ /** Extract string capabilities from a grant body's scope array. */
305
+ function scopeCaps(grant) {
306
+ const scopes = grant?.body?.capability_scopes;
307
+ if (!Array.isArray(scopes))
308
+ return [];
309
+ const out = [];
310
+ for (const s of scopes) {
311
+ if (s && typeof s.capability === 'string')
312
+ out.push(s.capability);
313
+ }
314
+ return out;
315
+ }
316
+ /**
317
+ * Verify a delegation chain OFFLINE (K2). VERIFY-ONLY: there is no enforcement
318
+ * wiring and no resolver — the caller supplies the ordered ancestors and the
319
+ * per-link verifier keys, and this function makes NO live claim. It ALWAYS
320
+ * returns `revocation_checked: false` and `existence_checked: false`; those
321
+ * remain RESOLVER-DEPENDENT (SRAID_FOUNDATION_PUNCHLIST C) and are explicitly
322
+ * out of scope here. The function is synchronous (no Promise overload), which
323
+ * is what makes "no live claim" structural rather than merely documented.
324
+ *
325
+ * The gate runs in this fixed order; DEPTH is checked BEFORE any crypto:
326
+ *
327
+ * GATE 0 DEPTH CAP — depth in [1, MAX_DELEGATION_DEPTH]; else return with
328
+ * NO hashing or signature work (cheap DoS guard).
329
+ * GATE 1 OID HONESTY — every link's recomputed content-core OID equals its
330
+ * claimed `oid` (rejects a body swapped under a fixed
331
+ * OID reference).
332
+ * GATE 2 PER-HOP — for each child links[i] under parent links[i+1]:
333
+ * (a) granted_by linkage, (b) scope attenuation (every
334
+ * child scope covered by some parent scope; empty
335
+ * child scopes = FAIL — a grant of nothing is not
336
+ * vacuously attenuated), (c) expiry monotone narrowing
337
+ * (null parent = unbounded; null child under a bounded
338
+ * parent = widening = FAIL).
339
+ * GATE 3 SIGNATURES — every link carries a hybrid DSSE attestation
340
+ * (ed25519 AND ml-dsa-65) over PAE(payloadType,
341
+ * payload), and the attestation payload equals
342
+ * canonicalize(cdroContentCore(link)) (payload-swap
343
+ * guard). A missing attestation sets signatures_checked
344
+ * false for that link and fails.
345
+ * GATE 4 ROOT ANCHOR — the terminal link's issuer key deep-equals
346
+ * rootPubkeys.
347
+ *
348
+ * `authorized` is the AND of every gate.
349
+ */
350
+ export function verifyDelegationChain(input) {
351
+ const reasons = [];
352
+ const links = [input.leaf, ...input.ancestors];
353
+ const depth = links.length;
354
+ const expectedPayloadType = input.attestationPayloadType ?? DEFAULT_GRANT_PAYLOAD_TYPE;
355
+ const fail = (over) => ({
356
+ authorized: false,
357
+ depth,
358
+ depth_ok: false,
359
+ links_ok: false,
360
+ attenuation_ok: false,
361
+ expiry_ok: false,
362
+ signatures_ok: false,
363
+ signatures_checked: false,
364
+ oids_ok: false,
365
+ root_ok: false,
366
+ action_ok: false,
367
+ action_checked: false,
368
+ revocation_checked: false,
369
+ not_revoked: false,
370
+ existence_checked: false,
371
+ per_hop: [],
372
+ reasons,
373
+ ...over,
374
+ });
375
+ // ── GATE 0 — DEPTH CAP (before any hashing or signature work) ──────────────
376
+ const depth_ok = depth >= 1 && depth <= MAX_DELEGATION_DEPTH;
377
+ if (!depth_ok) {
378
+ if (depth < 1)
379
+ reasons.push('empty chain — no leaf grant supplied');
380
+ else
381
+ reasons.push(`chain depth ${depth} exceeds cap ${MAX_DELEGATION_DEPTH}`);
382
+ // RETURN with no crypto run: signatures_checked stays false.
383
+ return fail({ depth_ok: false });
384
+ }
385
+ if (input.linkPubkeys.length !== depth) {
386
+ reasons.push(`linkPubkeys length ${input.linkPubkeys.length} does not match chain depth ${depth}`);
387
+ return fail({ depth_ok });
388
+ }
389
+ // ── GATE 1 — OID HASH-HONESTY, every link ──────────────────────────────────
390
+ let oids_ok = true;
391
+ for (let i = 0; i < depth; i++) {
392
+ const link = links[i];
393
+ let recomputed = null;
394
+ try {
395
+ recomputed = cdroOid(link);
396
+ }
397
+ catch {
398
+ recomputed = null;
399
+ }
400
+ if (recomputed === null || recomputed !== link.oid) {
401
+ oids_ok = false;
402
+ reasons.push(`link ${i} OID does not match its content (tampered grant body)`);
403
+ }
404
+ }
405
+ // ── GATE 2 — PER-HOP STRUCTURE (child links[i] under parent links[i+1]) ─────
406
+ let links_ok = true;
407
+ let attenuation_ok = true;
408
+ let expiry_ok = true;
409
+ const per_hop = [];
410
+ for (let i = 0; i < depth - 1; i++) {
411
+ const child = links[i];
412
+ const parent = links[i + 1];
413
+ // (a) GRANTED_BY linkage
414
+ const childGrantedBy = child.body?.granted_by;
415
+ const parentGrantee = parent.body?.grantee?.actor_oid;
416
+ const granted_by_ok = typeof childGrantedBy === 'string' &&
417
+ typeof parentGrantee === 'string' &&
418
+ childGrantedBy === parentGrantee;
419
+ if (!granted_by_ok) {
420
+ links_ok = false;
421
+ reasons.push(`hop ${i}: child.granted_by does not equal parent.grantee.actor_oid (broken signer link)`);
422
+ }
423
+ // (b) ATTENUATION — every child scope covered by some parent scope.
424
+ const childCaps = scopeCaps(child);
425
+ const parentCaps = scopeCaps(parent);
426
+ let hopAtten = true;
427
+ if (childCaps.length === 0) {
428
+ // A grant that grants nothing is REJECTED (conservative default), not
429
+ // treated as vacuously attenuated. (Flagged for founder review.)
430
+ hopAtten = false;
431
+ reasons.push(`hop ${i}: child grant has no capability scopes (rejected)`);
432
+ }
433
+ else {
434
+ for (const c of childCaps) {
435
+ const covered = parentCaps.some((p) => capabilityCovers(p, c));
436
+ if (!covered) {
437
+ hopAtten = false;
438
+ reasons.push(`hop ${i}: child scope "${c}" widens parent (not attenuated)`);
439
+ }
440
+ }
441
+ }
442
+ if (!hopAtten)
443
+ attenuation_ok = false;
444
+ // (c) EXPIRY MONOTONE-NARROWING
445
+ const pe = parent.body?.expires_at_ms;
446
+ const ce = child.body?.expires_at_ms;
447
+ let hopExpiry = true;
448
+ if (pe === null || pe === undefined) {
449
+ // unbounded parent: any child OK
450
+ hopExpiry = true;
451
+ }
452
+ else if (typeof pe === 'number') {
453
+ if (ce === null || ce === undefined) {
454
+ hopExpiry = false;
455
+ reasons.push(`hop ${i}: child expiry is unbounded under a bounded parent (widening)`);
456
+ }
457
+ else if (typeof ce === 'number') {
458
+ if (ce > pe) {
459
+ hopExpiry = false;
460
+ reasons.push(`hop ${i}: child expiry ${ce} is later than parent ${pe} (widening)`);
461
+ }
462
+ }
463
+ }
464
+ if (!hopExpiry)
465
+ expiry_ok = false;
466
+ per_hop.push({
467
+ child_index: i,
468
+ granted_by_ok,
469
+ attenuation_ok: hopAtten,
470
+ expiry_ok: hopExpiry,
471
+ });
472
+ }
473
+ // ── GATE 3 — HYBRID DSSE SIGNATURE, every link ─────────────────────────────
474
+ let signatures_ok = true;
475
+ let signatures_checked = true;
476
+ for (let i = 0; i < depth; i++) {
477
+ const link = links[i];
478
+ const att = link.attestation;
479
+ if (!att) {
480
+ signatures_checked = false;
481
+ signatures_ok = false;
482
+ reasons.push(`link ${i} carries no DSSE attestation`);
483
+ continue;
484
+ }
485
+ // payload-swap guard: the signed payload MUST equal this link's content core.
486
+ let expectedPayload = null;
487
+ try {
488
+ expectedPayload = canonicalize(cdroContentCore(link));
489
+ }
490
+ catch {
491
+ expectedPayload = null;
492
+ }
493
+ if (expectedPayload === null || att.payload !== expectedPayload) {
494
+ signatures_ok = false;
495
+ reasons.push(`link ${i} attestation payload does not match its content core (payload swap)`);
496
+ continue;
497
+ }
498
+ const keys = input.linkPubkeys[i];
499
+ const r = verifyAttestation({
500
+ envelope: att,
501
+ ed25519_pub: keys.ed25519,
502
+ ml_dsa_pub: keys.ml_dsa,
503
+ expectedPayloadType,
504
+ });
505
+ if (!r.valid) {
506
+ signatures_ok = false;
507
+ reasons.push(`link ${i} hybrid signature invalid: ${r.reasons.join(',')}`);
508
+ }
509
+ }
510
+ // ── GATE 4 — ROOT ANCHOR ───────────────────────────────────────────────────
511
+ const terminalKeys = input.linkPubkeys[depth - 1];
512
+ const root_ok = bytesEqual(terminalKeys.ed25519, input.rootPubkeys.ed25519) &&
513
+ bytesEqual(terminalKeys.ml_dsa, input.rootPubkeys.ml_dsa);
514
+ if (!root_ok) {
515
+ reasons.push('terminal link signer key does not equal rootPubkeys (forged/unknown root)');
516
+ }
517
+ // ── GATE 5 — ACTION COVERAGE (optional) ────────────────────────────────────
518
+ // When the caller supplies `action`, the leaf's capability scopes MUST cover
519
+ // it. Leaf = links[0]. Attenuation (GATE 2) already proved every ancestor
520
+ // covers the leaf, so leaf coverage + attenuation transitively proves the
521
+ // whole chain covers the action. Checking every hop would be redundant.
522
+ let action_ok = true;
523
+ let action_checked = false;
524
+ if (input.action !== undefined) {
525
+ action_checked = true;
526
+ const leafCaps = scopeCaps(input.leaf);
527
+ const covered = leafCaps.some((s) => capabilityCovers(s, input.action));
528
+ if (!covered) {
529
+ action_ok = false;
530
+ reasons.push(`requested action "${input.action}" not covered by leaf grant`);
531
+ }
532
+ }
533
+ const authorized = depth_ok &&
534
+ oids_ok &&
535
+ links_ok &&
536
+ attenuation_ok &&
537
+ expiry_ok &&
538
+ signatures_ok &&
539
+ signatures_checked &&
540
+ root_ok &&
541
+ action_ok;
542
+ return {
543
+ authorized,
544
+ depth,
545
+ depth_ok,
546
+ links_ok,
547
+ attenuation_ok,
548
+ expiry_ok,
549
+ signatures_ok,
550
+ signatures_checked,
551
+ oids_ok,
552
+ root_ok,
553
+ action_ok,
554
+ action_checked,
555
+ revocation_checked: false,
556
+ not_revoked: false,
557
+ existence_checked: false,
558
+ per_hop,
559
+ reasons,
560
+ };
561
+ }
562
+ import { canonicalize } from './canonicalize.js';
563
+ //# sourceMappingURL=authority.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority.js","sourceRoot":"","sources":["../src/authority.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @synoi/sraid — canonicalize.ts
3
+ *
4
+ * Strict RFC 8785 (JCS) serializer for SRAID objects. Produces
5
+ * byte-identical output to any other conformant RFC 8785 implementation
6
+ * given the same input, making this the normative canonical form for OID
7
+ * derivation and signing.
8
+ *
9
+ * Rules (strict RFC 8785 JCS):
10
+ * - Primitives are emitted via JSON.stringify (strings are escaped with
11
+ * standard JSON string escaping; booleans and null are literals).
12
+ * - Numbers must be FINITE INTEGERS (ADR_019 decision 2). NaN, Infinity
13
+ * (positive or negative), and any non-integer (float) are REJECTED with
14
+ * a thrown TypeError BEFORE hashing. Forbidding floats everywhere matches
15
+ * GAP-TS / GAP-Python and removes the hardest RFC 8785 cross-language
16
+ * serialization trap from the signed byte string; the product only emits
17
+ * integer minor-unit money and integer ms timestamps. Legal integers use
18
+ * the ECMAScript number-to-string serialization of RFC 8785 §3.2.2.3
19
+ * (equivalent to JS's default toString for integer values).
20
+ * - Arrays preserve element order; elements are recursively canonicalized.
21
+ * - Objects emit their keys sorted in ascending lexicographic
22
+ * (UTF-16 code-unit) order — this is the order produced by
23
+ * Array.prototype.sort(), which is exactly what RFC 8785 §3.2.3
24
+ * specifies for implementations whose host language uses UTF-16
25
+ * string encoding (including ECMAScript/TypeScript).
26
+ * - Object properties whose value is `undefined` are OMITTED entirely.
27
+ * (JSON has no undefined; this matches JSON.stringify semantics.)
28
+ * - No whitespace anywhere. Separators are bare `,` and `:`.
29
+ * - `null` is preserved as `null`.
30
+ *
31
+ * Stability invariant: any change to this function breaks every signed
32
+ * receipt, every OID, every grant — so it must NEVER be changed without
33
+ * a coordinated migration. The output bytes for a given input are a
34
+ * cross-package contract.
35
+ */
36
+ /**
37
+ * Canonicalize an arbitrary JSON-compatible value into a deterministic
38
+ * UTF-8 string following RFC 8785 (JCS). The output is suitable as input
39
+ * to SHA-256 (or any other hash) for OID derivation and as the byte string
40
+ * signed by Ed25519 / ML-DSA-65 envelopes.
41
+ *
42
+ * Reject-loud contract: a TypeError is thrown for any value that is not a
43
+ * JSON value, rather than silently producing a wrong or invalid canonical
44
+ * form (which would corrupt an OID or signature):
45
+ * - NaN / Infinity / -Infinity (RFC 8785 forbids non-finite numbers).
46
+ * - non-integer numbers / floats (ADR_019 forbids them; use integer minor
47
+ * units). e.g. 1.5, 0.1, 2e-3 all throw.
48
+ * - undefined / function / symbol / bigint anywhere they appear as a value,
49
+ * including as an ARRAY element (previously these produced invalid JSON
50
+ * like "[1,,2]").
51
+ * - objects with a toJSON() method (e.g. Date), which a bare keys-walk would
52
+ * silently mis-serialize (a Date became "{}"). Convert to a JSON value
53
+ * (e.g. an ISO string) first.
54
+ * Object properties whose value is `undefined` are OMITTED (matches
55
+ * JSON.stringify); an undefined array element throws (it cannot be omitted).
56
+ *
57
+ * @example
58
+ * canonicalize({ b: 2, a: 1 }) // '{"a":1,"b":2}'
59
+ * canonicalize({ a: undefined, b: 1 }) // '{"b":1}' (object undefined omitted)
60
+ * canonicalize([3, 1, 2]) // '[3,1,2]'
61
+ * canonicalize([1, null, 2]) // '[1,null,2]'
62
+ * canonicalize({ foo: 1, bar: 'hi' }) // '{"bar":"hi","foo":1}'
63
+ * canonicalize(NaN) // throws TypeError
64
+ * canonicalize(Infinity) // throws TypeError
65
+ * canonicalize(1.5) // throws TypeError (floats forbidden, ADR_019)
66
+ * canonicalize([1, undefined, 2]) // throws TypeError (no empty slots)
67
+ * canonicalize(new Date()) // throws TypeError (serialize first)
68
+ */
69
+ export declare function canonicalize(value: unknown): string;
70
+ //# sourceMappingURL=canonicalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalize.d.ts","sourceRoot":"","sources":["../src/canonicalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiGnD"}