@fabricorg/experiments-manifest-signing 0.0.1 → 0.1.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.
- package/dist/index.cjs +167 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -8
- package/dist/index.d.ts +39 -8
- package/dist/index.js +166 -46
- package/dist/index.js.map +1 -1
- package/package.json +10 -18
- package/LICENSE +0 -21
package/dist/index.cjs
CHANGED
|
@@ -17,6 +17,14 @@ function canonicalSignedBytes(m) {
|
|
|
17
17
|
return new TextEncoder().encode(canonicalSignedPayload(m));
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// src/subtle.ts
|
|
21
|
+
async function getSubtle() {
|
|
22
|
+
if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;
|
|
23
|
+
const nodeCryptoSpecifier = "node:crypto";
|
|
24
|
+
const { webcrypto } = await import(nodeCryptoSpecifier);
|
|
25
|
+
return webcrypto.subtle;
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
// src/hash.ts
|
|
21
29
|
async function sha256(data) {
|
|
22
30
|
const subtle = await getSubtle();
|
|
@@ -28,15 +36,10 @@ function hexEncode(bytes) {
|
|
|
28
36
|
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, "0");
|
|
29
37
|
return s;
|
|
30
38
|
}
|
|
31
|
-
async function getSubtle() {
|
|
32
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
33
|
-
const { webcrypto } = await import('crypto');
|
|
34
|
-
return webcrypto.subtle;
|
|
35
|
-
}
|
|
36
39
|
|
|
37
40
|
// src/sign.ts
|
|
38
41
|
async function signManifest(manifest, key) {
|
|
39
|
-
const subtle = await
|
|
42
|
+
const subtle = await getSubtle();
|
|
40
43
|
const payload = canonicalSignedBytes(manifest);
|
|
41
44
|
const signedHash = await sha256(payload);
|
|
42
45
|
const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));
|
|
@@ -55,11 +58,6 @@ async function signManifest(manifest, key) {
|
|
|
55
58
|
signedHash: hexEncode(signedHash)
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
|
-
async function getSubtle2() {
|
|
59
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
60
|
-
const { webcrypto } = await import('crypto');
|
|
61
|
-
return webcrypto.subtle;
|
|
62
|
-
}
|
|
63
61
|
function hexDecode(hex) {
|
|
64
62
|
if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
|
|
65
63
|
const out = new Uint8Array(hex.length / 2);
|
|
@@ -102,7 +100,7 @@ async function verifyManifest(manifest, options) {
|
|
|
102
100
|
if (expectedHash !== manifest.signature.signedHash) {
|
|
103
101
|
return { ok: false, reason: "signedHash-mismatch" };
|
|
104
102
|
}
|
|
105
|
-
const subtle = await
|
|
103
|
+
const subtle = await getSubtle();
|
|
106
104
|
const pubKey = await subtle.importKey(
|
|
107
105
|
"raw",
|
|
108
106
|
hexDecode2(options.publicKeyHex),
|
|
@@ -118,11 +116,6 @@ async function verifyManifest(manifest, options) {
|
|
|
118
116
|
);
|
|
119
117
|
return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
|
|
120
118
|
}
|
|
121
|
-
async function getSubtle3() {
|
|
122
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
123
|
-
const { webcrypto } = await import('crypto');
|
|
124
|
-
return webcrypto.subtle;
|
|
125
|
-
}
|
|
126
119
|
function hexDecode2(hex) {
|
|
127
120
|
if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
|
|
128
121
|
const out = new Uint8Array(hex.length / 2);
|
|
@@ -130,28 +123,6 @@ function hexDecode2(hex) {
|
|
|
130
123
|
return out;
|
|
131
124
|
}
|
|
132
125
|
|
|
133
|
-
// src/keypair.ts
|
|
134
|
-
async function generateKeypair(keyId) {
|
|
135
|
-
const subtle = await getSubtle4();
|
|
136
|
-
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
137
|
-
"sign",
|
|
138
|
-
"verify"
|
|
139
|
-
]);
|
|
140
|
-
const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
|
|
141
|
-
const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
|
|
142
|
-
const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
|
|
143
|
-
return {
|
|
144
|
-
publicKeyHex: hexEncode(rawPub),
|
|
145
|
-
privateKeyHex: hexEncode(seed),
|
|
146
|
-
keyId
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
async function getSubtle4() {
|
|
150
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
151
|
-
const { webcrypto } = await import('crypto');
|
|
152
|
-
return webcrypto.subtle;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
126
|
// src/jwks.ts
|
|
156
127
|
function jwksFromKeys(keys) {
|
|
157
128
|
return {
|
|
@@ -181,7 +152,7 @@ async function verifyAgainstJwks(manifest, options) {
|
|
|
181
152
|
const payload = canonicalSignedBytes(manifest);
|
|
182
153
|
const expectedHash = hexEncode(await sha256(payload));
|
|
183
154
|
if (expectedHash !== sig.signedHash) return { ok: false, reason: "signedHash-mismatch" };
|
|
184
|
-
const subtle = await
|
|
155
|
+
const subtle = await getSubtle();
|
|
185
156
|
const pubKey = await subtle.importKey(
|
|
186
157
|
"raw",
|
|
187
158
|
base64UrlToBytes(entry.x),
|
|
@@ -197,11 +168,6 @@ async function verifyAgainstJwks(manifest, options) {
|
|
|
197
168
|
);
|
|
198
169
|
return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
|
|
199
170
|
}
|
|
200
|
-
async function getSubtle5() {
|
|
201
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
202
|
-
const { webcrypto } = await import('crypto');
|
|
203
|
-
return webcrypto.subtle;
|
|
204
|
-
}
|
|
205
171
|
function hexToBytes(hex) {
|
|
206
172
|
const out = new Uint8Array(hex.length / 2);
|
|
207
173
|
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
@@ -224,12 +190,168 @@ function base64UrlToBytes(s) {
|
|
|
224
190
|
return out;
|
|
225
191
|
}
|
|
226
192
|
|
|
193
|
+
// src/delivery.ts
|
|
194
|
+
async function verifyManifestDelivery(input, options) {
|
|
195
|
+
const parsed = parseDeliveryManifest(input);
|
|
196
|
+
if (!parsed.ok) return parsed;
|
|
197
|
+
if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {
|
|
198
|
+
return { ok: false, reason: "manifest-revoked" };
|
|
199
|
+
}
|
|
200
|
+
const payload = {
|
|
201
|
+
schemaVersion: parsed.manifest.schemaVersion,
|
|
202
|
+
manifestVersion: parsed.manifest.manifestVersion,
|
|
203
|
+
tenantId: parsed.manifest.tenantId,
|
|
204
|
+
experiments: parsed.manifest.experiments
|
|
205
|
+
};
|
|
206
|
+
const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));
|
|
207
|
+
if (actualHash !== parsed.manifest.contentHash) {
|
|
208
|
+
return { ok: false, reason: "content-hash-mismatch" };
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const signature = await verifyAgainstJwks(parsed.manifest, options);
|
|
212
|
+
return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
ok: false,
|
|
216
|
+
reason: "schema-invalid",
|
|
217
|
+
details: error instanceof Error ? error.message : String(error)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function parseManifestRevocations(input) {
|
|
222
|
+
if (!isRecord(input) || !Array.isArray(input.versions)) {
|
|
223
|
+
throw new Error("manifest revocations must contain a versions array");
|
|
224
|
+
}
|
|
225
|
+
const versions = input.versions;
|
|
226
|
+
if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {
|
|
227
|
+
throw new Error("manifest revocation versions must be non-negative integers");
|
|
228
|
+
}
|
|
229
|
+
return [...new Set(versions)];
|
|
230
|
+
}
|
|
231
|
+
function parseDeliveryManifest(input) {
|
|
232
|
+
if (!isRecord(input)) return invalid("manifest must be an object");
|
|
233
|
+
if (input.schemaVersion !== 1) return invalid("unsupported schemaVersion");
|
|
234
|
+
if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {
|
|
235
|
+
return invalid("manifestVersion must be a non-negative integer");
|
|
236
|
+
}
|
|
237
|
+
if (!isHex(input.contentHash, 64)) return invalid("contentHash must be SHA-256 hex");
|
|
238
|
+
if (!isNonEmptyString(input.tenantId)) return invalid("tenantId is required");
|
|
239
|
+
if (!isIsoDate(input.builtAt)) return invalid("builtAt must be an ISO timestamp");
|
|
240
|
+
if (!Array.isArray(input.experiments)) return invalid("experiments must be an array");
|
|
241
|
+
if (!isSignature(input.signature)) return invalid("a valid Ed25519 signature is required");
|
|
242
|
+
const experimentIds = /* @__PURE__ */ new Set();
|
|
243
|
+
for (const [index, experiment] of input.experiments.entries()) {
|
|
244
|
+
const issue = validateExperiment(experiment, experimentIds);
|
|
245
|
+
if (issue) return invalid(`experiments[${index}]: ${issue}`);
|
|
246
|
+
}
|
|
247
|
+
return { ok: true, manifest: input };
|
|
248
|
+
}
|
|
249
|
+
function validateExperiment(value, experimentIds) {
|
|
250
|
+
if (!isRecord(value)) return "must be an object";
|
|
251
|
+
for (const key of ["id", "name", "salt"]) {
|
|
252
|
+
if (!isNonEmptyString(value[key])) return `${key} is required`;
|
|
253
|
+
}
|
|
254
|
+
if (experimentIds.has(value.id)) return `duplicate experiment id ${value.id}`;
|
|
255
|
+
experimentIds.add(value.id);
|
|
256
|
+
if (value.state !== "running" && value.state !== "paused") return "invalid state";
|
|
257
|
+
if (!isRate(value.sampleRate)) return "sampleRate must be between 0 and 1";
|
|
258
|
+
if (typeof value.applyVariants !== "boolean") return "applyVariants must be boolean";
|
|
259
|
+
if (!Array.isArray(value.variants) || value.variants.length < 2) {
|
|
260
|
+
return "at least two variants are required";
|
|
261
|
+
}
|
|
262
|
+
const variantKeys = /* @__PURE__ */ new Set();
|
|
263
|
+
let declaredWeight = 0;
|
|
264
|
+
let weightedVariants = 0;
|
|
265
|
+
for (const [index, variant] of value.variants.entries()) {
|
|
266
|
+
if (!isRecord(variant) || !isNonEmptyString(variant.key)) {
|
|
267
|
+
return `variants[${index}].key is required`;
|
|
268
|
+
}
|
|
269
|
+
if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;
|
|
270
|
+
if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;
|
|
271
|
+
variantKeys.add(variant.key);
|
|
272
|
+
if (variant.weight !== void 0) {
|
|
273
|
+
if (!Number.isInteger(variant.weight) || Number(variant.weight) < 0 || Number(variant.weight) > 1e4) {
|
|
274
|
+
return `variants[${index}].weight must be an integer between 0 and 10000`;
|
|
275
|
+
}
|
|
276
|
+
weightedVariants += 1;
|
|
277
|
+
declaredWeight += Number(variant.weight);
|
|
278
|
+
}
|
|
279
|
+
if (variant.recipeSampleRate !== void 0 && !isRate(variant.recipeSampleRate)) {
|
|
280
|
+
return `variants[${index}].recipeSampleRate must be between 0 and 1`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (weightedVariants > 0 && weightedVariants !== value.variants.length) {
|
|
284
|
+
return "variant weights must be declared for every variant or none";
|
|
285
|
+
}
|
|
286
|
+
if (weightedVariants > 0 && declaredWeight !== 1e4) {
|
|
287
|
+
return "variant weights must sum to 10000";
|
|
288
|
+
}
|
|
289
|
+
for (const key of ["defaultVariantKey", "divertTo"]) {
|
|
290
|
+
if (value[key] !== void 0 && (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))) {
|
|
291
|
+
return `${key} must reference an existing variant`;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
function canonicalJson(value) {
|
|
297
|
+
return JSON.stringify(sortDeep(value));
|
|
298
|
+
}
|
|
299
|
+
function sortDeep(value) {
|
|
300
|
+
if (Array.isArray(value)) return value.map(sortDeep);
|
|
301
|
+
if (isRecord(value)) {
|
|
302
|
+
const sorted = {};
|
|
303
|
+
for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);
|
|
304
|
+
return sorted;
|
|
305
|
+
}
|
|
306
|
+
return value;
|
|
307
|
+
}
|
|
308
|
+
function invalid(details) {
|
|
309
|
+
return { ok: false, reason: "schema-invalid", details };
|
|
310
|
+
}
|
|
311
|
+
function isRecord(value) {
|
|
312
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
313
|
+
}
|
|
314
|
+
function isNonEmptyString(value) {
|
|
315
|
+
return typeof value === "string" && value.length > 0;
|
|
316
|
+
}
|
|
317
|
+
function isRate(value) {
|
|
318
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
319
|
+
}
|
|
320
|
+
function isHex(value, length) {
|
|
321
|
+
return typeof value === "string" && new RegExp(`^[0-9a-f]{${length}}$`).test(value);
|
|
322
|
+
}
|
|
323
|
+
function isIsoDate(value) {
|
|
324
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
325
|
+
}
|
|
326
|
+
function isSignature(value) {
|
|
327
|
+
return isRecord(value) && isNonEmptyString(value.keyId) && value.alg === "ed25519" && isHex(value.sig, 128) && isHex(value.signedHash, 64);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/keypair.ts
|
|
331
|
+
async function generateKeypair(keyId) {
|
|
332
|
+
const subtle = await getSubtle();
|
|
333
|
+
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
334
|
+
"sign",
|
|
335
|
+
"verify"
|
|
336
|
+
]);
|
|
337
|
+
const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
|
|
338
|
+
const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
|
|
339
|
+
const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
|
|
340
|
+
return {
|
|
341
|
+
publicKeyHex: hexEncode(rawPub),
|
|
342
|
+
privateKeyHex: hexEncode(seed),
|
|
343
|
+
keyId
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
227
347
|
exports.canonicalSignedBytes = canonicalSignedBytes;
|
|
228
348
|
exports.canonicalSignedPayload = canonicalSignedPayload;
|
|
229
349
|
exports.generateKeypair = generateKeypair;
|
|
230
350
|
exports.jwksFromKeys = jwksFromKeys;
|
|
351
|
+
exports.parseManifestRevocations = parseManifestRevocations;
|
|
231
352
|
exports.signManifest = signManifest;
|
|
232
353
|
exports.verifyAgainstJwks = verifyAgainstJwks;
|
|
233
354
|
exports.verifyManifest = verifyManifest;
|
|
355
|
+
exports.verifyManifestDelivery = verifyManifestDelivery;
|
|
234
356
|
//# sourceMappingURL=index.cjs.map
|
|
235
357
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/canonical.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/keypair.ts","../src/jwks.ts"],"names":["getSubtle","hexDecode"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","export async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/canonical.ts","../src/subtle.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/jwks.ts","../src/delivery.ts","../src/keypair.ts"],"names":["hexDecode"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","/**\n * Resolve WebCrypto without forcing browser bundlers to resolve Node's built-in\n * crypto module. Modern browsers and supported Node releases use the global\n * implementation; the runtime-only import is a compatibility fallback.\n */\nexport async function getSubtle(): Promise<SubtleCrypto> {\n if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;\n const nodeCryptoSpecifier = 'node:crypto';\n const { webcrypto } = await import(nodeCryptoSpecifier);\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { getSubtle } from './subtle.js';\n\nexport async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n","import { hexEncode, sha256 } from './hash.js';\nimport { type Jwks, type JwksVerifyResult, verifyAgainstJwks } from './jwks.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface DeliveryManifest extends SignableManifest {\n builtAt: string;\n experiments: Array<Record<string, unknown>>;\n signature: ManifestSignature;\n}\n\nexport interface VerifyManifestDeliveryOptions {\n jwks: Jwks;\n expectedKeyIds?: string[];\n revokedVersions?: readonly number[];\n maxAgeMs?: number;\n now?: () => Date;\n}\n\ntype JwksFailureReason = Extract<JwksVerifyResult, { ok: false }>['reason'];\n\nexport type ManifestDeliveryFailure =\n | 'schema-invalid'\n | 'content-hash-mismatch'\n | 'manifest-revoked'\n | JwksFailureReason;\n\nexport type ManifestDeliveryResult =\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: ManifestDeliveryFailure; details?: string };\n\n/**\n * Validate and authenticate an untrusted manifest response before an SDK uses it.\n * This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.\n */\nexport async function verifyManifestDelivery(\n input: unknown,\n options: VerifyManifestDeliveryOptions,\n): Promise<ManifestDeliveryResult> {\n const parsed = parseDeliveryManifest(input);\n if (!parsed.ok) return parsed;\n if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {\n return { ok: false, reason: 'manifest-revoked' };\n }\n\n const payload = {\n schemaVersion: parsed.manifest.schemaVersion,\n manifestVersion: parsed.manifest.manifestVersion,\n tenantId: parsed.manifest.tenantId,\n experiments: parsed.manifest.experiments,\n };\n const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));\n if (actualHash !== parsed.manifest.contentHash) {\n return { ok: false, reason: 'content-hash-mismatch' };\n }\n\n try {\n const signature = await verifyAgainstJwks(parsed.manifest, options);\n return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;\n } catch (error) {\n return {\n ok: false,\n reason: 'schema-invalid',\n details: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport function parseManifestRevocations(input: unknown): number[] {\n if (!isRecord(input) || !Array.isArray(input.versions)) {\n throw new Error('manifest revocations must contain a versions array');\n }\n const versions = input.versions;\n if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {\n throw new Error('manifest revocation versions must be non-negative integers');\n }\n return [...new Set(versions as number[])];\n}\n\nfunction parseDeliveryManifest(\n input: unknown,\n):\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: 'schema-invalid'; details: string } {\n if (!isRecord(input)) return invalid('manifest must be an object');\n if (input.schemaVersion !== 1) return invalid('unsupported schemaVersion');\n if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {\n return invalid('manifestVersion must be a non-negative integer');\n }\n if (!isHex(input.contentHash, 64)) return invalid('contentHash must be SHA-256 hex');\n if (!isNonEmptyString(input.tenantId)) return invalid('tenantId is required');\n if (!isIsoDate(input.builtAt)) return invalid('builtAt must be an ISO timestamp');\n if (!Array.isArray(input.experiments)) return invalid('experiments must be an array');\n if (!isSignature(input.signature)) return invalid('a valid Ed25519 signature is required');\n\n const experimentIds = new Set<string>();\n for (const [index, experiment] of input.experiments.entries()) {\n const issue = validateExperiment(experiment, experimentIds);\n if (issue) return invalid(`experiments[${index}]: ${issue}`);\n }\n\n return { ok: true, manifest: input as unknown as DeliveryManifest };\n}\n\nfunction validateExperiment(value: unknown, experimentIds: Set<string>): string | null {\n if (!isRecord(value)) return 'must be an object';\n for (const key of ['id', 'name', 'salt'] as const) {\n if (!isNonEmptyString(value[key])) return `${key} is required`;\n }\n if (experimentIds.has(value.id as string)) return `duplicate experiment id ${value.id as string}`;\n experimentIds.add(value.id as string);\n if (value.state !== 'running' && value.state !== 'paused') return 'invalid state';\n if (!isRate(value.sampleRate)) return 'sampleRate must be between 0 and 1';\n if (typeof value.applyVariants !== 'boolean') return 'applyVariants must be boolean';\n if (!Array.isArray(value.variants) || value.variants.length < 2) {\n return 'at least two variants are required';\n }\n\n const variantKeys = new Set<string>();\n let declaredWeight = 0;\n let weightedVariants = 0;\n for (const [index, variant] of value.variants.entries()) {\n if (!isRecord(variant) || !isNonEmptyString(variant.key)) {\n return `variants[${index}].key is required`;\n }\n if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;\n if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;\n variantKeys.add(variant.key);\n if (variant.weight !== undefined) {\n if (\n !Number.isInteger(variant.weight) ||\n Number(variant.weight) < 0 ||\n Number(variant.weight) > 10000\n ) {\n return `variants[${index}].weight must be an integer between 0 and 10000`;\n }\n weightedVariants += 1;\n declaredWeight += Number(variant.weight);\n }\n if (variant.recipeSampleRate !== undefined && !isRate(variant.recipeSampleRate)) {\n return `variants[${index}].recipeSampleRate must be between 0 and 1`;\n }\n }\n if (weightedVariants > 0 && weightedVariants !== value.variants.length) {\n return 'variant weights must be declared for every variant or none';\n }\n if (weightedVariants > 0 && declaredWeight !== 10000) {\n return 'variant weights must sum to 10000';\n }\n for (const key of ['defaultVariantKey', 'divertTo'] as const) {\n if (\n value[key] !== undefined &&\n (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))\n ) {\n return `${key} must reference an existing variant`;\n }\n }\n return null;\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(sortDeep(value));\n}\n\nfunction sortDeep(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortDeep);\n if (isRecord(value)) {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);\n return sorted;\n }\n return value;\n}\n\nfunction invalid(details: string) {\n return { ok: false as const, reason: 'schema-invalid' as const, details };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isRate(value: unknown): boolean {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;\n}\n\nfunction isHex(value: unknown, length: number): value is string {\n return typeof value === 'string' && new RegExp(`^[0-9a-f]{${length}}$`).test(value);\n}\n\nfunction isIsoDate(value: unknown): value is string {\n return typeof value === 'string' && Number.isFinite(Date.parse(value));\n}\n\nfunction isSignature(value: unknown): value is ManifestSignature {\n return (\n isRecord(value) &&\n isNonEmptyString(value.keyId) &&\n value.alg === 'ed25519' &&\n isHex(value.sig, 128) &&\n isHex(value.signedHash, 64)\n );\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\nimport { getSubtle } from './subtle.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -58,13 +58,6 @@ declare function verifyManifest(manifest: SignableManifest & {
|
|
|
58
58
|
signature: ManifestSignature;
|
|
59
59
|
}, options: VerifyOptions): Promise<VerifyResult>;
|
|
60
60
|
|
|
61
|
-
interface Ed25519Keypair {
|
|
62
|
-
publicKeyHex: string;
|
|
63
|
-
privateKeyHex: string;
|
|
64
|
-
keyId: string;
|
|
65
|
-
}
|
|
66
|
-
declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
|
|
67
|
-
|
|
68
61
|
/**
|
|
69
62
|
* JWKS-style key set for a single org. Each entry is an Ed25519 public key
|
|
70
63
|
* with a kid that matches a `manifest_keys.key_id` in the control plane.
|
|
@@ -112,4 +105,42 @@ declare function verifyAgainstJwks(manifest: SignableManifest & {
|
|
|
112
105
|
builtAt?: string;
|
|
113
106
|
}, options: VerifyAgainstJwksOptions): Promise<JwksVerifyResult>;
|
|
114
107
|
|
|
115
|
-
|
|
108
|
+
interface DeliveryManifest extends SignableManifest {
|
|
109
|
+
builtAt: string;
|
|
110
|
+
experiments: Array<Record<string, unknown>>;
|
|
111
|
+
signature: ManifestSignature;
|
|
112
|
+
}
|
|
113
|
+
interface VerifyManifestDeliveryOptions {
|
|
114
|
+
jwks: Jwks;
|
|
115
|
+
expectedKeyIds?: string[];
|
|
116
|
+
revokedVersions?: readonly number[];
|
|
117
|
+
maxAgeMs?: number;
|
|
118
|
+
now?: () => Date;
|
|
119
|
+
}
|
|
120
|
+
type JwksFailureReason = Extract<JwksVerifyResult, {
|
|
121
|
+
ok: false;
|
|
122
|
+
}>['reason'];
|
|
123
|
+
type ManifestDeliveryFailure = 'schema-invalid' | 'content-hash-mismatch' | 'manifest-revoked' | JwksFailureReason;
|
|
124
|
+
type ManifestDeliveryResult = {
|
|
125
|
+
ok: true;
|
|
126
|
+
manifest: DeliveryManifest;
|
|
127
|
+
} | {
|
|
128
|
+
ok: false;
|
|
129
|
+
reason: ManifestDeliveryFailure;
|
|
130
|
+
details?: string;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Validate and authenticate an untrusted manifest response before an SDK uses it.
|
|
134
|
+
* This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.
|
|
135
|
+
*/
|
|
136
|
+
declare function verifyManifestDelivery(input: unknown, options: VerifyManifestDeliveryOptions): Promise<ManifestDeliveryResult>;
|
|
137
|
+
declare function parseManifestRevocations(input: unknown): number[];
|
|
138
|
+
|
|
139
|
+
interface Ed25519Keypair {
|
|
140
|
+
publicKeyHex: string;
|
|
141
|
+
privateKeyHex: string;
|
|
142
|
+
keyId: string;
|
|
143
|
+
}
|
|
144
|
+
declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
|
|
145
|
+
|
|
146
|
+
export { type DeliveryManifest, type Ed25519Keypair, type Jwks, type JwksEntry, type JwksVerifyResult, type ManifestDeliveryFailure, type ManifestDeliveryResult, type ManifestSignature, type SignKey, type SignableManifest, type SignedManifest, type VerifyAgainstJwksOptions, type VerifyManifestDeliveryOptions, type VerifyOptions, type VerifyResult, canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, parseManifestRevocations, signManifest, verifyAgainstJwks, verifyManifest, verifyManifestDelivery };
|
package/dist/index.d.ts
CHANGED
|
@@ -58,13 +58,6 @@ declare function verifyManifest(manifest: SignableManifest & {
|
|
|
58
58
|
signature: ManifestSignature;
|
|
59
59
|
}, options: VerifyOptions): Promise<VerifyResult>;
|
|
60
60
|
|
|
61
|
-
interface Ed25519Keypair {
|
|
62
|
-
publicKeyHex: string;
|
|
63
|
-
privateKeyHex: string;
|
|
64
|
-
keyId: string;
|
|
65
|
-
}
|
|
66
|
-
declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
|
|
67
|
-
|
|
68
61
|
/**
|
|
69
62
|
* JWKS-style key set for a single org. Each entry is an Ed25519 public key
|
|
70
63
|
* with a kid that matches a `manifest_keys.key_id` in the control plane.
|
|
@@ -112,4 +105,42 @@ declare function verifyAgainstJwks(manifest: SignableManifest & {
|
|
|
112
105
|
builtAt?: string;
|
|
113
106
|
}, options: VerifyAgainstJwksOptions): Promise<JwksVerifyResult>;
|
|
114
107
|
|
|
115
|
-
|
|
108
|
+
interface DeliveryManifest extends SignableManifest {
|
|
109
|
+
builtAt: string;
|
|
110
|
+
experiments: Array<Record<string, unknown>>;
|
|
111
|
+
signature: ManifestSignature;
|
|
112
|
+
}
|
|
113
|
+
interface VerifyManifestDeliveryOptions {
|
|
114
|
+
jwks: Jwks;
|
|
115
|
+
expectedKeyIds?: string[];
|
|
116
|
+
revokedVersions?: readonly number[];
|
|
117
|
+
maxAgeMs?: number;
|
|
118
|
+
now?: () => Date;
|
|
119
|
+
}
|
|
120
|
+
type JwksFailureReason = Extract<JwksVerifyResult, {
|
|
121
|
+
ok: false;
|
|
122
|
+
}>['reason'];
|
|
123
|
+
type ManifestDeliveryFailure = 'schema-invalid' | 'content-hash-mismatch' | 'manifest-revoked' | JwksFailureReason;
|
|
124
|
+
type ManifestDeliveryResult = {
|
|
125
|
+
ok: true;
|
|
126
|
+
manifest: DeliveryManifest;
|
|
127
|
+
} | {
|
|
128
|
+
ok: false;
|
|
129
|
+
reason: ManifestDeliveryFailure;
|
|
130
|
+
details?: string;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Validate and authenticate an untrusted manifest response before an SDK uses it.
|
|
134
|
+
* This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.
|
|
135
|
+
*/
|
|
136
|
+
declare function verifyManifestDelivery(input: unknown, options: VerifyManifestDeliveryOptions): Promise<ManifestDeliveryResult>;
|
|
137
|
+
declare function parseManifestRevocations(input: unknown): number[];
|
|
138
|
+
|
|
139
|
+
interface Ed25519Keypair {
|
|
140
|
+
publicKeyHex: string;
|
|
141
|
+
privateKeyHex: string;
|
|
142
|
+
keyId: string;
|
|
143
|
+
}
|
|
144
|
+
declare function generateKeypair(keyId: string): Promise<Ed25519Keypair>;
|
|
145
|
+
|
|
146
|
+
export { type DeliveryManifest, type Ed25519Keypair, type Jwks, type JwksEntry, type JwksVerifyResult, type ManifestDeliveryFailure, type ManifestDeliveryResult, type ManifestSignature, type SignKey, type SignableManifest, type SignedManifest, type VerifyAgainstJwksOptions, type VerifyManifestDeliveryOptions, type VerifyOptions, type VerifyResult, canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, parseManifestRevocations, signManifest, verifyAgainstJwks, verifyManifest, verifyManifestDelivery };
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,14 @@ function canonicalSignedBytes(m) {
|
|
|
15
15
|
return new TextEncoder().encode(canonicalSignedPayload(m));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// src/subtle.ts
|
|
19
|
+
async function getSubtle() {
|
|
20
|
+
if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;
|
|
21
|
+
const nodeCryptoSpecifier = "node:crypto";
|
|
22
|
+
const { webcrypto } = await import(nodeCryptoSpecifier);
|
|
23
|
+
return webcrypto.subtle;
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
// src/hash.ts
|
|
19
27
|
async function sha256(data) {
|
|
20
28
|
const subtle = await getSubtle();
|
|
@@ -26,15 +34,10 @@ function hexEncode(bytes) {
|
|
|
26
34
|
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, "0");
|
|
27
35
|
return s;
|
|
28
36
|
}
|
|
29
|
-
async function getSubtle() {
|
|
30
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
31
|
-
const { webcrypto } = await import('crypto');
|
|
32
|
-
return webcrypto.subtle;
|
|
33
|
-
}
|
|
34
37
|
|
|
35
38
|
// src/sign.ts
|
|
36
39
|
async function signManifest(manifest, key) {
|
|
37
|
-
const subtle = await
|
|
40
|
+
const subtle = await getSubtle();
|
|
38
41
|
const payload = canonicalSignedBytes(manifest);
|
|
39
42
|
const signedHash = await sha256(payload);
|
|
40
43
|
const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));
|
|
@@ -53,11 +56,6 @@ async function signManifest(manifest, key) {
|
|
|
53
56
|
signedHash: hexEncode(signedHash)
|
|
54
57
|
};
|
|
55
58
|
}
|
|
56
|
-
async function getSubtle2() {
|
|
57
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
58
|
-
const { webcrypto } = await import('crypto');
|
|
59
|
-
return webcrypto.subtle;
|
|
60
|
-
}
|
|
61
59
|
function hexDecode(hex) {
|
|
62
60
|
if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
|
|
63
61
|
const out = new Uint8Array(hex.length / 2);
|
|
@@ -100,7 +98,7 @@ async function verifyManifest(manifest, options) {
|
|
|
100
98
|
if (expectedHash !== manifest.signature.signedHash) {
|
|
101
99
|
return { ok: false, reason: "signedHash-mismatch" };
|
|
102
100
|
}
|
|
103
|
-
const subtle = await
|
|
101
|
+
const subtle = await getSubtle();
|
|
104
102
|
const pubKey = await subtle.importKey(
|
|
105
103
|
"raw",
|
|
106
104
|
hexDecode2(options.publicKeyHex),
|
|
@@ -116,11 +114,6 @@ async function verifyManifest(manifest, options) {
|
|
|
116
114
|
);
|
|
117
115
|
return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
|
|
118
116
|
}
|
|
119
|
-
async function getSubtle3() {
|
|
120
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
121
|
-
const { webcrypto } = await import('crypto');
|
|
122
|
-
return webcrypto.subtle;
|
|
123
|
-
}
|
|
124
117
|
function hexDecode2(hex) {
|
|
125
118
|
if (hex.length % 2 !== 0) throw new Error("hexDecode: odd length");
|
|
126
119
|
const out = new Uint8Array(hex.length / 2);
|
|
@@ -128,28 +121,6 @@ function hexDecode2(hex) {
|
|
|
128
121
|
return out;
|
|
129
122
|
}
|
|
130
123
|
|
|
131
|
-
// src/keypair.ts
|
|
132
|
-
async function generateKeypair(keyId) {
|
|
133
|
-
const subtle = await getSubtle4();
|
|
134
|
-
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
135
|
-
"sign",
|
|
136
|
-
"verify"
|
|
137
|
-
]);
|
|
138
|
-
const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
|
|
139
|
-
const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
|
|
140
|
-
const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
|
|
141
|
-
return {
|
|
142
|
-
publicKeyHex: hexEncode(rawPub),
|
|
143
|
-
privateKeyHex: hexEncode(seed),
|
|
144
|
-
keyId
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
async function getSubtle4() {
|
|
148
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
149
|
-
const { webcrypto } = await import('crypto');
|
|
150
|
-
return webcrypto.subtle;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
124
|
// src/jwks.ts
|
|
154
125
|
function jwksFromKeys(keys) {
|
|
155
126
|
return {
|
|
@@ -179,7 +150,7 @@ async function verifyAgainstJwks(manifest, options) {
|
|
|
179
150
|
const payload = canonicalSignedBytes(manifest);
|
|
180
151
|
const expectedHash = hexEncode(await sha256(payload));
|
|
181
152
|
if (expectedHash !== sig.signedHash) return { ok: false, reason: "signedHash-mismatch" };
|
|
182
|
-
const subtle = await
|
|
153
|
+
const subtle = await getSubtle();
|
|
183
154
|
const pubKey = await subtle.importKey(
|
|
184
155
|
"raw",
|
|
185
156
|
base64UrlToBytes(entry.x),
|
|
@@ -195,11 +166,6 @@ async function verifyAgainstJwks(manifest, options) {
|
|
|
195
166
|
);
|
|
196
167
|
return valid ? { ok: true } : { ok: false, reason: "sig-invalid" };
|
|
197
168
|
}
|
|
198
|
-
async function getSubtle5() {
|
|
199
|
-
if (typeof crypto !== "undefined" && crypto.subtle) return crypto.subtle;
|
|
200
|
-
const { webcrypto } = await import('crypto');
|
|
201
|
-
return webcrypto.subtle;
|
|
202
|
-
}
|
|
203
169
|
function hexToBytes(hex) {
|
|
204
170
|
const out = new Uint8Array(hex.length / 2);
|
|
205
171
|
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
@@ -222,6 +188,160 @@ function base64UrlToBytes(s) {
|
|
|
222
188
|
return out;
|
|
223
189
|
}
|
|
224
190
|
|
|
225
|
-
|
|
191
|
+
// src/delivery.ts
|
|
192
|
+
async function verifyManifestDelivery(input, options) {
|
|
193
|
+
const parsed = parseDeliveryManifest(input);
|
|
194
|
+
if (!parsed.ok) return parsed;
|
|
195
|
+
if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {
|
|
196
|
+
return { ok: false, reason: "manifest-revoked" };
|
|
197
|
+
}
|
|
198
|
+
const payload = {
|
|
199
|
+
schemaVersion: parsed.manifest.schemaVersion,
|
|
200
|
+
manifestVersion: parsed.manifest.manifestVersion,
|
|
201
|
+
tenantId: parsed.manifest.tenantId,
|
|
202
|
+
experiments: parsed.manifest.experiments
|
|
203
|
+
};
|
|
204
|
+
const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));
|
|
205
|
+
if (actualHash !== parsed.manifest.contentHash) {
|
|
206
|
+
return { ok: false, reason: "content-hash-mismatch" };
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const signature = await verifyAgainstJwks(parsed.manifest, options);
|
|
210
|
+
return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
reason: "schema-invalid",
|
|
215
|
+
details: error instanceof Error ? error.message : String(error)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function parseManifestRevocations(input) {
|
|
220
|
+
if (!isRecord(input) || !Array.isArray(input.versions)) {
|
|
221
|
+
throw new Error("manifest revocations must contain a versions array");
|
|
222
|
+
}
|
|
223
|
+
const versions = input.versions;
|
|
224
|
+
if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {
|
|
225
|
+
throw new Error("manifest revocation versions must be non-negative integers");
|
|
226
|
+
}
|
|
227
|
+
return [...new Set(versions)];
|
|
228
|
+
}
|
|
229
|
+
function parseDeliveryManifest(input) {
|
|
230
|
+
if (!isRecord(input)) return invalid("manifest must be an object");
|
|
231
|
+
if (input.schemaVersion !== 1) return invalid("unsupported schemaVersion");
|
|
232
|
+
if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {
|
|
233
|
+
return invalid("manifestVersion must be a non-negative integer");
|
|
234
|
+
}
|
|
235
|
+
if (!isHex(input.contentHash, 64)) return invalid("contentHash must be SHA-256 hex");
|
|
236
|
+
if (!isNonEmptyString(input.tenantId)) return invalid("tenantId is required");
|
|
237
|
+
if (!isIsoDate(input.builtAt)) return invalid("builtAt must be an ISO timestamp");
|
|
238
|
+
if (!Array.isArray(input.experiments)) return invalid("experiments must be an array");
|
|
239
|
+
if (!isSignature(input.signature)) return invalid("a valid Ed25519 signature is required");
|
|
240
|
+
const experimentIds = /* @__PURE__ */ new Set();
|
|
241
|
+
for (const [index, experiment] of input.experiments.entries()) {
|
|
242
|
+
const issue = validateExperiment(experiment, experimentIds);
|
|
243
|
+
if (issue) return invalid(`experiments[${index}]: ${issue}`);
|
|
244
|
+
}
|
|
245
|
+
return { ok: true, manifest: input };
|
|
246
|
+
}
|
|
247
|
+
function validateExperiment(value, experimentIds) {
|
|
248
|
+
if (!isRecord(value)) return "must be an object";
|
|
249
|
+
for (const key of ["id", "name", "salt"]) {
|
|
250
|
+
if (!isNonEmptyString(value[key])) return `${key} is required`;
|
|
251
|
+
}
|
|
252
|
+
if (experimentIds.has(value.id)) return `duplicate experiment id ${value.id}`;
|
|
253
|
+
experimentIds.add(value.id);
|
|
254
|
+
if (value.state !== "running" && value.state !== "paused") return "invalid state";
|
|
255
|
+
if (!isRate(value.sampleRate)) return "sampleRate must be between 0 and 1";
|
|
256
|
+
if (typeof value.applyVariants !== "boolean") return "applyVariants must be boolean";
|
|
257
|
+
if (!Array.isArray(value.variants) || value.variants.length < 2) {
|
|
258
|
+
return "at least two variants are required";
|
|
259
|
+
}
|
|
260
|
+
const variantKeys = /* @__PURE__ */ new Set();
|
|
261
|
+
let declaredWeight = 0;
|
|
262
|
+
let weightedVariants = 0;
|
|
263
|
+
for (const [index, variant] of value.variants.entries()) {
|
|
264
|
+
if (!isRecord(variant) || !isNonEmptyString(variant.key)) {
|
|
265
|
+
return `variants[${index}].key is required`;
|
|
266
|
+
}
|
|
267
|
+
if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;
|
|
268
|
+
if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;
|
|
269
|
+
variantKeys.add(variant.key);
|
|
270
|
+
if (variant.weight !== void 0) {
|
|
271
|
+
if (!Number.isInteger(variant.weight) || Number(variant.weight) < 0 || Number(variant.weight) > 1e4) {
|
|
272
|
+
return `variants[${index}].weight must be an integer between 0 and 10000`;
|
|
273
|
+
}
|
|
274
|
+
weightedVariants += 1;
|
|
275
|
+
declaredWeight += Number(variant.weight);
|
|
276
|
+
}
|
|
277
|
+
if (variant.recipeSampleRate !== void 0 && !isRate(variant.recipeSampleRate)) {
|
|
278
|
+
return `variants[${index}].recipeSampleRate must be between 0 and 1`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (weightedVariants > 0 && weightedVariants !== value.variants.length) {
|
|
282
|
+
return "variant weights must be declared for every variant or none";
|
|
283
|
+
}
|
|
284
|
+
if (weightedVariants > 0 && declaredWeight !== 1e4) {
|
|
285
|
+
return "variant weights must sum to 10000";
|
|
286
|
+
}
|
|
287
|
+
for (const key of ["defaultVariantKey", "divertTo"]) {
|
|
288
|
+
if (value[key] !== void 0 && (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))) {
|
|
289
|
+
return `${key} must reference an existing variant`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
function canonicalJson(value) {
|
|
295
|
+
return JSON.stringify(sortDeep(value));
|
|
296
|
+
}
|
|
297
|
+
function sortDeep(value) {
|
|
298
|
+
if (Array.isArray(value)) return value.map(sortDeep);
|
|
299
|
+
if (isRecord(value)) {
|
|
300
|
+
const sorted = {};
|
|
301
|
+
for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);
|
|
302
|
+
return sorted;
|
|
303
|
+
}
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
function invalid(details) {
|
|
307
|
+
return { ok: false, reason: "schema-invalid", details };
|
|
308
|
+
}
|
|
309
|
+
function isRecord(value) {
|
|
310
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
311
|
+
}
|
|
312
|
+
function isNonEmptyString(value) {
|
|
313
|
+
return typeof value === "string" && value.length > 0;
|
|
314
|
+
}
|
|
315
|
+
function isRate(value) {
|
|
316
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
317
|
+
}
|
|
318
|
+
function isHex(value, length) {
|
|
319
|
+
return typeof value === "string" && new RegExp(`^[0-9a-f]{${length}}$`).test(value);
|
|
320
|
+
}
|
|
321
|
+
function isIsoDate(value) {
|
|
322
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
323
|
+
}
|
|
324
|
+
function isSignature(value) {
|
|
325
|
+
return isRecord(value) && isNonEmptyString(value.keyId) && value.alg === "ed25519" && isHex(value.sig, 128) && isHex(value.signedHash, 64);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/keypair.ts
|
|
329
|
+
async function generateKeypair(keyId) {
|
|
330
|
+
const subtle = await getSubtle();
|
|
331
|
+
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
332
|
+
"sign",
|
|
333
|
+
"verify"
|
|
334
|
+
]);
|
|
335
|
+
const rawPub = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
|
|
336
|
+
const pkcs8Priv = new Uint8Array(await subtle.exportKey("pkcs8", kp.privateKey));
|
|
337
|
+
const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);
|
|
338
|
+
return {
|
|
339
|
+
publicKeyHex: hexEncode(rawPub),
|
|
340
|
+
privateKeyHex: hexEncode(seed),
|
|
341
|
+
keyId
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export { canonicalSignedBytes, canonicalSignedPayload, generateKeypair, jwksFromKeys, parseManifestRevocations, signManifest, verifyAgainstJwks, verifyManifest, verifyManifestDelivery };
|
|
226
346
|
//# sourceMappingURL=index.js.map
|
|
227
347
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/canonical.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/keypair.ts","../src/jwks.ts"],"names":["getSubtle","hexDecode"],"mappings":"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","file":"index.js","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","export async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n if (typeof crypto !== 'undefined' && crypto.subtle) return crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/canonical.ts","../src/subtle.ts","../src/hash.ts","../src/sign.ts","../src/verify.ts","../src/jwks.ts","../src/delivery.ts","../src/keypair.ts"],"names":["hexDecode"],"mappings":"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","file":"index.js","sourcesContent":["import type { SignableManifest } from './types.js';\n\n/**\n * Canonical JSON of the signed payload — sorted keys, no whitespace.\n *\n * The payload is intentionally NOT the full manifest. It is the minimum that\n * binds a (manifestVersion, contentHash) pair to a tenant under a schema. See\n * ADR-0002.\n */\nexport function canonicalSignedPayload(m: SignableManifest): string {\n return JSON.stringify(\n {\n contentHash: m.contentHash,\n manifestVersion: m.manifestVersion,\n schemaVersion: m.schemaVersion,\n tenantId: m.tenantId,\n },\n null,\n 0,\n );\n}\n\nexport function canonicalSignedBytes(m: SignableManifest): Uint8Array {\n return new TextEncoder().encode(canonicalSignedPayload(m));\n}\n","/**\n * Resolve WebCrypto without forcing browser bundlers to resolve Node's built-in\n * crypto module. Modern browsers and supported Node releases use the global\n * implementation; the runtime-only import is a compatibility fallback.\n */\nexport async function getSubtle(): Promise<SubtleCrypto> {\n if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;\n const nodeCryptoSpecifier = 'node:crypto';\n const { webcrypto } = await import(nodeCryptoSpecifier);\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n","import { getSubtle } from './subtle.js';\n\nexport async function sha256(data: Uint8Array): Promise<Uint8Array> {\n const subtle = await getSubtle();\n const buf = await subtle.digest('SHA-256', data as BufferSource);\n return new Uint8Array(buf);\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, '0');\n return s;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/** Universal-runtime Ed25519 signer (Node + Edge + browsers via crypto.subtle). */\nexport interface SignKey {\n /** Raw 32-byte Ed25519 private key as hex. */\n privateKeyHex: string;\n /** Identifier for the key — included in the signature for rotation tracking. */\n keyId: string;\n}\n\nexport async function signManifest(\n manifest: SignableManifest,\n key: SignKey,\n): Promise<ManifestSignature> {\n const subtle = await getSubtle();\n const payload = canonicalSignedBytes(manifest);\n const signedHash = await sha256(payload);\n\n const pkcs8 = ed25519PrivateKeyToPkcs8(hexDecode(key.privateKeyHex));\n const cryptoKey = await subtle.importKey(\n 'pkcs8',\n pkcs8 as BufferSource,\n { name: 'Ed25519' },\n false,\n ['sign'],\n );\n const sigBuf = await subtle.sign({ name: 'Ed25519' }, cryptoKey, payload as BufferSource);\n return {\n keyId: key.keyId,\n alg: 'ed25519',\n sig: hexEncode(new Uint8Array(sigBuf)),\n signedHash: hexEncode(signedHash),\n };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\n/** Wrap a raw 32-byte Ed25519 seed into a minimal PKCS#8 envelope (RFC 8410). */\nfunction ed25519PrivateKeyToPkcs8(seed: Uint8Array): Uint8Array {\n if (seed.length !== 32) throw new Error('ed25519 seed must be 32 bytes');\n const prefix = new Uint8Array([\n 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,\n ]);\n const out = new Uint8Array(prefix.length + 32);\n out.set(prefix, 0);\n out.set(seed, prefix.length);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface VerifyOptions {\n /** Hex-encoded raw 32-byte Ed25519 public key. */\n publicKeyHex: string;\n /** Required keyId — caller pins this at SDK init time. */\n expectedKeyId: string;\n}\n\nexport type VerifyResult =\n | { ok: true }\n | { ok: false; reason: 'keyId-mismatch' | 'signedHash-mismatch' | 'sig-invalid' };\n\nexport async function verifyManifest(\n manifest: SignableManifest & { signature: ManifestSignature },\n options: VerifyOptions,\n): Promise<VerifyResult> {\n if (manifest.signature.keyId !== options.expectedKeyId) {\n return { ok: false, reason: 'keyId-mismatch' };\n }\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== manifest.signature.signedHash) {\n return { ok: false, reason: 'signedHash-mismatch' };\n }\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n hexDecode(options.publicKeyHex) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexDecode(manifest.signature.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexDecode: odd length');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","import { canonicalSignedBytes } from './canonical.js';\nimport { hexEncode, sha256 } from './hash.js';\nimport { getSubtle } from './subtle.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\n/**\n * JWKS-style key set for a single org. Each entry is an Ed25519 public key\n * with a kid that matches a `manifest_keys.key_id` in the control plane.\n */\nexport interface JwksEntry {\n kid: string;\n kty: 'OKP';\n crv: 'Ed25519';\n /** Base64url-encoded raw 32-byte public key. */\n x: string;\n alg: 'EdDSA';\n use: 'sig';\n /** Lifecycle status — extension on top of the JWKS standard. */\n status: 'active' | 'retiring' | 'retired';\n}\n\nexport interface Jwks {\n keys: JwksEntry[];\n}\n\nexport function jwksFromKeys(\n keys: readonly {\n keyId: string;\n publicKeyHex: string;\n status: 'active' | 'retiring' | 'retired';\n }[],\n): Jwks {\n return {\n keys: keys.map((k) => ({\n kid: k.keyId,\n kty: 'OKP',\n crv: 'Ed25519',\n x: hexToBase64Url(k.publicKeyHex),\n alg: 'EdDSA',\n use: 'sig',\n status: k.status,\n })),\n };\n}\n\nexport interface VerifyAgainstJwksOptions {\n jwks: Jwks;\n /** Optional pinning. If set, rejects keys whose kid is not in this list. */\n expectedKeyIds?: string[];\n /** Reject manifests older than this many milliseconds (replay window). */\n maxAgeMs?: number;\n /** Override clock for tests. */\n now?: () => Date;\n}\n\nexport type JwksVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | 'unknown-key'\n | 'retired-key'\n | 'pinned-key-mismatch'\n | 'signedHash-mismatch'\n | 'sig-invalid'\n | 'replay-window';\n };\n\n/**\n * Verify a signed manifest against a JWKS document. Replaces ADR-0002's\n * single-key verifier when SDKs run in M3+ hosted mode.\n */\nexport async function verifyAgainstJwks(\n manifest: SignableManifest & { signature: ManifestSignature; builtAt?: string },\n options: VerifyAgainstJwksOptions,\n): Promise<JwksVerifyResult> {\n const sig = manifest.signature;\n if (options.expectedKeyIds && !options.expectedKeyIds.includes(sig.keyId)) {\n return { ok: false, reason: 'pinned-key-mismatch' };\n }\n const entry = options.jwks.keys.find((k) => k.kid === sig.keyId);\n if (!entry) return { ok: false, reason: 'unknown-key' };\n if (entry.status === 'retired') return { ok: false, reason: 'retired-key' };\n\n if (options.maxAgeMs && manifest.builtAt) {\n const age =\n (options.now ?? (() => new Date()))().getTime() - new Date(manifest.builtAt).getTime();\n if (age > options.maxAgeMs) return { ok: false, reason: 'replay-window' };\n }\n\n const payload = canonicalSignedBytes(manifest);\n const expectedHash = hexEncode(await sha256(payload));\n if (expectedHash !== sig.signedHash) return { ok: false, reason: 'signedHash-mismatch' };\n\n const subtle = await getSubtle();\n const pubKey = await subtle.importKey(\n 'raw',\n base64UrlToBytes(entry.x) as BufferSource,\n { name: 'Ed25519' },\n false,\n ['verify'],\n );\n const valid = await subtle.verify(\n { name: 'Ed25519' },\n pubKey,\n hexToBytes(sig.sig) as BufferSource,\n payload as BufferSource,\n );\n return valid ? { ok: true } : { ok: false, reason: 'sig-invalid' };\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nfunction hexToBase64Url(hex: string): string {\n return bytesToBase64Url(hexToBytes(hex));\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!);\n return btoa(s).replace(/=+$/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\nfunction base64UrlToBytes(s: string): Uint8Array {\n const pad = '='.repeat((4 - (s.length % 4)) % 4);\n const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n","import { hexEncode, sha256 } from './hash.js';\nimport { type Jwks, type JwksVerifyResult, verifyAgainstJwks } from './jwks.js';\nimport type { ManifestSignature, SignableManifest } from './types.js';\n\nexport interface DeliveryManifest extends SignableManifest {\n builtAt: string;\n experiments: Array<Record<string, unknown>>;\n signature: ManifestSignature;\n}\n\nexport interface VerifyManifestDeliveryOptions {\n jwks: Jwks;\n expectedKeyIds?: string[];\n revokedVersions?: readonly number[];\n maxAgeMs?: number;\n now?: () => Date;\n}\n\ntype JwksFailureReason = Extract<JwksVerifyResult, { ok: false }>['reason'];\n\nexport type ManifestDeliveryFailure =\n | 'schema-invalid'\n | 'content-hash-mismatch'\n | 'manifest-revoked'\n | JwksFailureReason;\n\nexport type ManifestDeliveryResult =\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: ManifestDeliveryFailure; details?: string };\n\n/**\n * Validate and authenticate an untrusted manifest response before an SDK uses it.\n * This function is WebCrypto-only and is shared by browser, edge, and Node SDKs.\n */\nexport async function verifyManifestDelivery(\n input: unknown,\n options: VerifyManifestDeliveryOptions,\n): Promise<ManifestDeliveryResult> {\n const parsed = parseDeliveryManifest(input);\n if (!parsed.ok) return parsed;\n if (options.revokedVersions?.includes(parsed.manifest.manifestVersion)) {\n return { ok: false, reason: 'manifest-revoked' };\n }\n\n const payload = {\n schemaVersion: parsed.manifest.schemaVersion,\n manifestVersion: parsed.manifest.manifestVersion,\n tenantId: parsed.manifest.tenantId,\n experiments: parsed.manifest.experiments,\n };\n const actualHash = hexEncode(await sha256(new TextEncoder().encode(canonicalJson(payload))));\n if (actualHash !== parsed.manifest.contentHash) {\n return { ok: false, reason: 'content-hash-mismatch' };\n }\n\n try {\n const signature = await verifyAgainstJwks(parsed.manifest, options);\n return signature.ok ? { ok: true, manifest: parsed.manifest } : signature;\n } catch (error) {\n return {\n ok: false,\n reason: 'schema-invalid',\n details: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport function parseManifestRevocations(input: unknown): number[] {\n if (!isRecord(input) || !Array.isArray(input.versions)) {\n throw new Error('manifest revocations must contain a versions array');\n }\n const versions = input.versions;\n if (!versions.every((version) => Number.isInteger(version) && Number(version) >= 0)) {\n throw new Error('manifest revocation versions must be non-negative integers');\n }\n return [...new Set(versions as number[])];\n}\n\nfunction parseDeliveryManifest(\n input: unknown,\n):\n | { ok: true; manifest: DeliveryManifest }\n | { ok: false; reason: 'schema-invalid'; details: string } {\n if (!isRecord(input)) return invalid('manifest must be an object');\n if (input.schemaVersion !== 1) return invalid('unsupported schemaVersion');\n if (!Number.isInteger(input.manifestVersion) || Number(input.manifestVersion) < 0) {\n return invalid('manifestVersion must be a non-negative integer');\n }\n if (!isHex(input.contentHash, 64)) return invalid('contentHash must be SHA-256 hex');\n if (!isNonEmptyString(input.tenantId)) return invalid('tenantId is required');\n if (!isIsoDate(input.builtAt)) return invalid('builtAt must be an ISO timestamp');\n if (!Array.isArray(input.experiments)) return invalid('experiments must be an array');\n if (!isSignature(input.signature)) return invalid('a valid Ed25519 signature is required');\n\n const experimentIds = new Set<string>();\n for (const [index, experiment] of input.experiments.entries()) {\n const issue = validateExperiment(experiment, experimentIds);\n if (issue) return invalid(`experiments[${index}]: ${issue}`);\n }\n\n return { ok: true, manifest: input as unknown as DeliveryManifest };\n}\n\nfunction validateExperiment(value: unknown, experimentIds: Set<string>): string | null {\n if (!isRecord(value)) return 'must be an object';\n for (const key of ['id', 'name', 'salt'] as const) {\n if (!isNonEmptyString(value[key])) return `${key} is required`;\n }\n if (experimentIds.has(value.id as string)) return `duplicate experiment id ${value.id as string}`;\n experimentIds.add(value.id as string);\n if (value.state !== 'running' && value.state !== 'paused') return 'invalid state';\n if (!isRate(value.sampleRate)) return 'sampleRate must be between 0 and 1';\n if (typeof value.applyVariants !== 'boolean') return 'applyVariants must be boolean';\n if (!Array.isArray(value.variants) || value.variants.length < 2) {\n return 'at least two variants are required';\n }\n\n const variantKeys = new Set<string>();\n let declaredWeight = 0;\n let weightedVariants = 0;\n for (const [index, variant] of value.variants.entries()) {\n if (!isRecord(variant) || !isNonEmptyString(variant.key)) {\n return `variants[${index}].key is required`;\n }\n if (!isNonEmptyString(variant.name)) return `variants[${index}].name is required`;\n if (variantKeys.has(variant.key)) return `duplicate variant key ${variant.key}`;\n variantKeys.add(variant.key);\n if (variant.weight !== undefined) {\n if (\n !Number.isInteger(variant.weight) ||\n Number(variant.weight) < 0 ||\n Number(variant.weight) > 10000\n ) {\n return `variants[${index}].weight must be an integer between 0 and 10000`;\n }\n weightedVariants += 1;\n declaredWeight += Number(variant.weight);\n }\n if (variant.recipeSampleRate !== undefined && !isRate(variant.recipeSampleRate)) {\n return `variants[${index}].recipeSampleRate must be between 0 and 1`;\n }\n }\n if (weightedVariants > 0 && weightedVariants !== value.variants.length) {\n return 'variant weights must be declared for every variant or none';\n }\n if (weightedVariants > 0 && declaredWeight !== 10000) {\n return 'variant weights must sum to 10000';\n }\n for (const key of ['defaultVariantKey', 'divertTo'] as const) {\n if (\n value[key] !== undefined &&\n (!isNonEmptyString(value[key]) || !variantKeys.has(value[key]))\n ) {\n return `${key} must reference an existing variant`;\n }\n }\n return null;\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(sortDeep(value));\n}\n\nfunction sortDeep(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortDeep);\n if (isRecord(value)) {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) sorted[key] = sortDeep(value[key]);\n return sorted;\n }\n return value;\n}\n\nfunction invalid(details: string) {\n return { ok: false as const, reason: 'schema-invalid' as const, details };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isRate(value: unknown): boolean {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;\n}\n\nfunction isHex(value: unknown, length: number): value is string {\n return typeof value === 'string' && new RegExp(`^[0-9a-f]{${length}}$`).test(value);\n}\n\nfunction isIsoDate(value: unknown): value is string {\n return typeof value === 'string' && Number.isFinite(Date.parse(value));\n}\n\nfunction isSignature(value: unknown): value is ManifestSignature {\n return (\n isRecord(value) &&\n isNonEmptyString(value.keyId) &&\n value.alg === 'ed25519' &&\n isHex(value.sig, 128) &&\n isHex(value.signedHash, 64)\n );\n}\n","/**\n * Ed25519 keypair generation for `fx dev` and tests. Production keys are\n * generated offline and stored in a secrets manager.\n */\nimport { hexEncode } from './hash.js';\nimport { getSubtle } from './subtle.js';\n\nexport interface Ed25519Keypair {\n publicKeyHex: string;\n privateKeyHex: string;\n keyId: string;\n}\n\nexport async function generateKeypair(keyId: string): Promise<Ed25519Keypair> {\n const subtle = await getSubtle();\n const kp = (await subtle.generateKey({ name: 'Ed25519' }, true, [\n 'sign',\n 'verify',\n ])) as CryptoKeyPair;\n const rawPub = new Uint8Array(await subtle.exportKey('raw', kp.publicKey));\n const pkcs8Priv = new Uint8Array(await subtle.exportKey('pkcs8', kp.privateKey));\n // PKCS#8 ed25519 = 16-byte prefix + 32-byte seed. Strip the prefix.\n const seed = pkcs8Priv.slice(pkcs8Priv.length - 32);\n return {\n publicKeyHex: hexEncode(rawPub),\n privateKeyHex: hexEncode(seed),\n keyId,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabricorg/experiments-manifest-signing",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Ed25519 sign + verify utilities for Fabric Experiments manifests. Works in Node, Edge, and browsers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,28 +10,20 @@
|
|
|
10
10
|
"module": "./dist/index.js",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
-
"import": {
|
|
14
|
-
|
|
15
|
-
"default": "./dist/index.js"
|
|
16
|
-
},
|
|
17
|
-
"require": {
|
|
18
|
-
"types": "./dist/index.d.cts",
|
|
19
|
-
"default": "./dist/index.cjs"
|
|
20
|
-
}
|
|
13
|
+
"import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
|
|
14
|
+
"require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
|
|
21
15
|
}
|
|
22
16
|
},
|
|
23
|
-
"files": [
|
|
24
|
-
"dist"
|
|
25
|
-
],
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"tsup": "^8.3.5",
|
|
28
|
-
"typescript": "^5.8.3",
|
|
29
|
-
"vitest": "^2.1.8"
|
|
30
|
-
},
|
|
17
|
+
"files": ["dist"],
|
|
31
18
|
"scripts": {
|
|
32
19
|
"build": "tsup",
|
|
33
20
|
"type-check": "tsc --noEmit",
|
|
34
21
|
"test": "vitest run",
|
|
35
22
|
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"tsup": "^8.3.5",
|
|
26
|
+
"typescript": "^5.8.3",
|
|
27
|
+
"vitest": "^3.2.7"
|
|
36
28
|
}
|
|
37
|
-
}
|
|
29
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Fabric
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|