@byok-sdk/core 0.30.2 → 0.32.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.
@@ -1,1585 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  import { ByokCoreError } from '@byok-sdk/core';
3
3
 
4
- // src/sealed-payload/sealed-content.ts
5
- var CONTENT_HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
6
- var DEVICE_PROOF_SCHEMA_ID = "byok-device-proof-v1";
7
- var DEVICE_PROOF_VERSION = 1;
8
- var DEVICE_PROOF_ALGORITHMS = ["ed25519"];
9
- var BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
10
- var HTTP_METHOD_PATTERN = /^[A-Z]+$/;
11
- function canonicalizationFailure(path, reason) {
12
- return new ByokCoreError(
13
- "proof_canonicalization_failed",
14
- `Cannot canonicalize ${path}: ${reason}`
15
- );
16
- }
17
- function isPlainObject(value) {
18
- const prototype = Object.getPrototypeOf(value);
19
- return prototype === Object.prototype || prototype === null;
20
- }
21
- function canonicalizeValue(value, path, seen) {
22
- if (value === null) return "null";
23
- switch (typeof value) {
24
- case "boolean":
25
- return value ? "true" : "false";
26
- case "string":
27
- return JSON.stringify(value);
28
- case "number": {
29
- if (!Number.isSafeInteger(value)) {
30
- throw canonicalizationFailure(
31
- path,
32
- `only safe integers are canonicalizable, received ${String(value)}`
33
- );
34
- }
35
- return String(value === 0 ? 0 : value);
36
- }
37
- case "undefined":
38
- throw canonicalizationFailure(path, "undefined has no canonical form");
39
- case "bigint":
40
- throw canonicalizationFailure(path, "bigint is not a JSON type");
41
- case "function":
42
- case "symbol":
43
- throw canonicalizationFailure(path, `${typeof value} is not a JSON type`);
44
- case "object":
45
- break;
46
- default:
47
- throw canonicalizationFailure(path, `unsupported type ${typeof value}`);
48
- }
49
- const objectValue = value;
50
- if (seen.has(objectValue)) {
51
- throw canonicalizationFailure(path, "circular reference");
52
- }
53
- seen.add(objectValue);
54
- try {
55
- if (Array.isArray(objectValue)) {
56
- const parts2 = objectValue.map(
57
- (entry, index) => canonicalizeValue(entry, `${path}[${index}]`, seen)
58
- );
59
- return `[${parts2.join(",")}]`;
60
- }
61
- if (!isPlainObject(objectValue)) {
62
- throw canonicalizationFailure(
63
- path,
64
- "only plain objects and arrays are canonicalizable"
65
- );
66
- }
67
- const keys = Object.keys(objectValue).sort();
68
- const record = objectValue;
69
- const parts = keys.map((key) => {
70
- const encodedKey = JSON.stringify(key);
71
- const encodedValue = canonicalizeValue(record[key], `${path}.${key}`, seen);
72
- return `${encodedKey}:${encodedValue}`;
73
- });
74
- return `{${parts.join(",")}}`;
75
- } finally {
76
- seen.delete(objectValue);
77
- }
78
- }
79
- function canonicalizeJson(value) {
80
- return canonicalizeValue(value, "<root>", /* @__PURE__ */ new Set());
81
- }
82
- function canonicalizeJsonBytes(value) {
83
- return new TextEncoder().encode(canonicalizeJson(value));
84
- }
85
- var DeviceProofProtectedClaimsSchema = z.strictObject({
86
- version: z.literal(DEVICE_PROOF_VERSION),
87
- tenantId: z.string().min(1),
88
- productId: z.string().min(1),
89
- deviceId: z.string().min(1),
90
- /** Identity of the signing key, resolved against the device row at verify time. */
91
- keyId: z.string().min(1),
92
- /** Rotation generation. An older epoch is rejected by the verifier, not here. */
93
- keyEpoch: z.number().int().nonnegative(),
94
- /** Idempotency key: exact replay returns the original result, reuse with a different body is a conflict. */
95
- requestId: z.string().min(1),
96
- /** Logical operation, e.g. `truth.write`. */
97
- operation: z.string().min(1),
98
- /** Tenant-scoped resource the operation targets. */
99
- resource: z.string().min(1),
100
- method: z.string().regex(HTTP_METHOD_PATTERN).optional(),
101
- path: z.string().startsWith("/").optional(),
102
- operationId: z.string().min(1).optional(),
103
- bodySha256: z.string().regex(CONTENT_HASH_PATTERN),
104
- /** Bound alongside the hash so a truncation attack cannot hide behind a hash collision claim. */
105
- bodySize: z.number().int().nonnegative(),
106
- issuedAt: z.iso.datetime(),
107
- expiresAt: z.iso.datetime().optional(),
108
- nonce: z.string().min(1).optional()
109
- }).superRefine((claims, ctx) => {
110
- const hasRequestLine = claims.method !== void 0 && claims.path !== void 0;
111
- const hasOperationId = claims.operationId !== void 0;
112
- if (hasRequestLine === hasOperationId) {
113
- ctx.addIssue({
114
- code: "custom",
115
- message: "Claims must carry exactly one request binding: both `method` and `path`, or `operationId`."
116
- });
117
- }
118
- if (!hasRequestLine && (claims.method !== void 0 || claims.path !== void 0)) {
119
- ctx.addIssue({
120
- code: "custom",
121
- message: "`method` and `path` must be supplied together."
122
- });
123
- }
124
- });
125
- z.strictObject({
126
- schema: z.literal(DEVICE_PROOF_SCHEMA_ID),
127
- algorithm: z.enum(DEVICE_PROOF_ALGORITHMS),
128
- protected: DeviceProofProtectedClaimsSchema,
129
- /** base64url, unpadded. */
130
- signature: z.string().regex(BASE64URL_PATTERN)
131
- });
132
- var CANONICAL_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
133
- function isCanonicalTimestamp(value) {
134
- if (typeof value !== "string") return false;
135
- if (!CANONICAL_TIMESTAMP_PATTERN.test(value)) return false;
136
- const parsed = Date.parse(value);
137
- if (!Number.isFinite(parsed)) return false;
138
- return new Date(parsed).toISOString() === value;
139
- }
140
-
141
- // src/total-parse.ts
142
- function safeParseTotal(schema, value) {
143
- try {
144
- return schema.safeParse(value);
145
- } catch {
146
- return { success: false };
147
- }
148
- }
149
- function webCryptoSubtle() {
150
- const subtle = globalThis.crypto?.subtle;
151
- if (subtle === void 0) {
152
- throw new ByokCoreError("webcrypto_unavailable", "WebCrypto subtle is unavailable in this runtime");
153
- }
154
- return subtle;
155
- }
156
- var KEM_ID = 16;
157
- var KDF_ID = 1;
158
- var AEAD_ID = 1;
159
- var HPKE_P256_PUBLIC_KEY_BYTES = 65;
160
- var N_SECRET = 32;
161
- var N_K = 16;
162
- var N_N = 12;
163
- var N_H = 32;
164
- var HPKE_AEAD_TAG_BYTES = 16;
165
- var MODE_BASE = 0;
166
- var HPKE_V1 = utf8("HPKE-v1");
167
- var KEM_SUITE_ID = concat(utf8("KEM"), i2osp(KEM_ID, 2));
168
- var HPKE_SUITE_ID = concat(utf8("HPKE"), i2osp(KEM_ID, 2), i2osp(KDF_ID, 2), i2osp(AEAD_ID, 2));
169
- var EMPTY = new Uint8Array(0);
170
- var ZERO_SALT = new Uint8Array(N_H);
171
- function utf8(value) {
172
- return new TextEncoder().encode(value);
173
- }
174
- function concat(...parts) {
175
- const length = parts.reduce((total, part) => total + part.byteLength, 0);
176
- const out = new Uint8Array(length);
177
- let offset = 0;
178
- for (const part of parts) {
179
- out.set(part, offset);
180
- offset += part.byteLength;
181
- }
182
- return out;
183
- }
184
- function i2osp(value, length) {
185
- if (!Number.isSafeInteger(value) || value < 0) {
186
- throw new ByokCoreError("sealed_secret_invalid", "HPKE integer encoding requires a non-negative safe integer");
187
- }
188
- const out = new Uint8Array(length);
189
- let remaining = value;
190
- for (let index = length - 1; index >= 0; index -= 1) {
191
- out[index] = remaining % 256;
192
- remaining = Math.floor(remaining / 256);
193
- }
194
- if (remaining !== 0) {
195
- throw new ByokCoreError("sealed_secret_invalid", "HPKE integer does not fit the requested width");
196
- }
197
- return out;
198
- }
199
- function copy(bytes) {
200
- return new Uint8Array(bytes);
201
- }
202
- async function hmacSha256(key, data) {
203
- const subtle = webCryptoSubtle();
204
- const hmacKey = await subtle.importKey("raw", copy(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
205
- return new Uint8Array(await subtle.sign("HMAC", hmacKey, copy(data)));
206
- }
207
- async function hkdfExpand(prk, info, length) {
208
- if (length > 255 * N_H) {
209
- throw new ByokCoreError("sealed_secret_invalid", "HKDF output length exceeds its limit");
210
- }
211
- const out = new Uint8Array(length);
212
- let previous = EMPTY;
213
- let offset = 0;
214
- for (let counter = 1; offset < length; counter += 1) {
215
- previous = await hmacSha256(prk, concat(previous, info, Uint8Array.of(counter)));
216
- const take = Math.min(previous.byteLength, length - offset);
217
- out.set(previous.subarray(0, take), offset);
218
- offset += take;
219
- }
220
- return out;
221
- }
222
- async function labeledExtract(suiteId, salt, label, ikm) {
223
- return hmacSha256(salt.byteLength === 0 ? ZERO_SALT : salt, concat(HPKE_V1, suiteId, utf8(label), ikm));
224
- }
225
- async function labeledExpand(suiteId, prk, label, info, length) {
226
- return hkdfExpand(prk, concat(i2osp(length, 2), HPKE_V1, suiteId, utf8(label), info), length);
227
- }
228
- async function importP256PublicKey(raw) {
229
- if (raw.byteLength !== HPKE_P256_PUBLIC_KEY_BYTES || raw[0] !== 4) {
230
- throw new ByokCoreError("sealed_secret_invalid", "P-256 public key must be a 65-byte uncompressed point");
231
- }
232
- try {
233
- return await webCryptoSubtle().importKey("raw", copy(raw), { name: "ECDH", namedCurve: "P-256" }, true, []);
234
- } catch (cause) {
235
- throw new ByokCoreError("sealed_secret_invalid", "P-256 public key is not a valid curve point", { cause });
236
- }
237
- }
238
- async function ecdhP256(privateKey, publicKey) {
239
- const bits = await webCryptoSubtle().deriveBits({ name: "ECDH", public: publicKey }, privateKey, N_SECRET * 8);
240
- return new Uint8Array(bits);
241
- }
242
- async function extractAndExpand(dh, kemContext) {
243
- const eaePrk = await labeledExtract(KEM_SUITE_ID, EMPTY, "eae_prk", dh);
244
- return labeledExpand(KEM_SUITE_ID, eaePrk, "shared_secret", kemContext, N_SECRET);
245
- }
246
- async function keyScheduleBase(sharedSecret, info) {
247
- const pskIdHash = await labeledExtract(HPKE_SUITE_ID, EMPTY, "psk_id_hash", EMPTY);
248
- const infoHash = await labeledExtract(HPKE_SUITE_ID, EMPTY, "info_hash", info);
249
- const context = concat(Uint8Array.of(MODE_BASE), pskIdHash, infoHash);
250
- const secret = await labeledExtract(HPKE_SUITE_ID, sharedSecret, "secret", EMPTY);
251
- return {
252
- sharedSecret,
253
- key: await labeledExpand(HPKE_SUITE_ID, secret, "key", context, N_K),
254
- baseNonce: await labeledExpand(HPKE_SUITE_ID, secret, "base_nonce", context, N_N),
255
- exporterSecret: await labeledExpand(HPKE_SUITE_ID, secret, "exp", context, N_H)
256
- };
257
- }
258
- async function setupBaseSender(recipientPublicKey, info, ephemeral) {
259
- const pkR = await importP256PublicKey(recipientPublicKey);
260
- let pair = ephemeral;
261
- if (pair === void 0) {
262
- const subtle = webCryptoSubtle();
263
- const generated = await subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
264
- pair = {
265
- privateKey: generated.privateKey,
266
- publicKey: new Uint8Array(await subtle.exportKey("raw", generated.publicKey))
267
- };
268
- }
269
- const enc = copy(pair.publicKey);
270
- const dh = await ecdhP256(pair.privateKey, pkR);
271
- const sharedSecret = await extractAndExpand(dh, concat(enc, recipientPublicKey));
272
- return { enc, schedule: await keyScheduleBase(sharedSecret, info) };
273
- }
274
- async function setupBaseRecipient(enc, recipientPrivateKey, recipientPublicKey, info) {
275
- if (recipientPublicKey.byteLength !== HPKE_P256_PUBLIC_KEY_BYTES || recipientPublicKey[0] !== 4) {
276
- throw new ByokCoreError("sealed_secret_invalid", "P-256 recipient public key must be a 65-byte uncompressed point");
277
- }
278
- const pkE = await importP256PublicKey(enc);
279
- let dh;
280
- try {
281
- dh = await ecdhP256(recipientPrivateKey, pkE);
282
- } catch (cause) {
283
- throw new ByokCoreError("sealed_secret_open_failed", "HPKE decapsulation failed", { cause });
284
- }
285
- const sharedSecret = await extractAndExpand(dh, concat(enc, recipientPublicKey));
286
- return keyScheduleBase(sharedSecret, info);
287
- }
288
- function hpkeNonce(baseNonce, sequence) {
289
- const seq = i2osp(sequence, N_N);
290
- const out = new Uint8Array(N_N);
291
- for (let index = 0; index < N_N; index += 1) out[index] = baseNonce[index] ^ seq[index];
292
- return out;
293
- }
294
- async function aesGcmKey(key, usage) {
295
- return webCryptoSubtle().importKey("raw", copy(key), { name: "AES-GCM" }, false, [usage]);
296
- }
297
- async function hpkeAeadSeal(schedule, sequence, aad, plaintext) {
298
- const ciphertext = await webCryptoSubtle().encrypt(
299
- { name: "AES-GCM", iv: hpkeNonce(schedule.baseNonce, sequence), additionalData: copy(aad), tagLength: 128 },
300
- await aesGcmKey(schedule.key, "encrypt"),
301
- copy(plaintext)
302
- );
303
- return new Uint8Array(ciphertext);
304
- }
305
- async function hpkeAeadOpen(schedule, sequence, aad, ciphertext) {
306
- if (ciphertext.byteLength < HPKE_AEAD_TAG_BYTES) {
307
- throw new ByokCoreError("sealed_secret_invalid", "HPKE ciphertext is shorter than its authentication tag");
308
- }
309
- try {
310
- const plaintext = await webCryptoSubtle().decrypt(
311
- { name: "AES-GCM", iv: hpkeNonce(schedule.baseNonce, sequence), additionalData: copy(aad), tagLength: 128 },
312
- await aesGcmKey(schedule.key, "decrypt"),
313
- copy(ciphertext)
314
- );
315
- return new Uint8Array(plaintext);
316
- } catch (cause) {
317
- throw new ByokCoreError("sealed_secret_open_failed", "HPKE ciphertext failed authentication", { cause });
318
- }
319
- }
320
- async function hpkeSealBase(input) {
321
- const { enc, schedule } = await setupBaseSender(input.recipientPublicKey, input.info);
322
- return { enc, ciphertext: await hpkeAeadSeal(schedule, 0, input.aad, input.plaintext) };
323
- }
324
- async function hpkeOpenBase(input) {
325
- const schedule = await setupBaseRecipient(input.enc, input.recipientPrivateKey, input.recipientPublicKey, input.info);
326
- return hpkeAeadOpen(schedule, 0, input.aad, input.ciphertext);
327
- }
328
-
329
- // src/sealed-provider-secret.ts
330
- var PROVIDER_SECRET_HPKE_SUITE = "hpke-base.dhkem-p256-hkdf-sha256.hkdf-sha256.aes-128-gcm";
331
- var PROVIDER_PROVISIONING_REQUEST_VERSION = 1;
332
- var PROVIDER_PROVISIONING_OPERATIONS = ["configure", "update_model", "replace_secret", "delete"];
333
- var PROVIDER_PROVISIONING_SECRET_OPERATIONS = ["configure", "replace_secret"];
334
- var PROVIDER_SECRET_MAX_PADDED_BYTES = 4096;
335
- var SEALING_KEY_ID_LENGTH = 22;
336
- var B64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
337
- var B64URL_PATTERN = /^[A-Za-z0-9_-]*$/;
338
- function encodeBase64Url(bytes) {
339
- let out = "";
340
- let index = 0;
341
- for (; index + 3 <= bytes.length; index += 3) {
342
- const value = bytes[index] << 16 | bytes[index + 1] << 8 | bytes[index + 2];
343
- out += B64URL_ALPHABET[value >> 18 & 63] + B64URL_ALPHABET[value >> 12 & 63] + B64URL_ALPHABET[value >> 6 & 63] + B64URL_ALPHABET[value & 63];
344
- }
345
- const rest = bytes.length - index;
346
- if (rest === 1) {
347
- const value = bytes[index] << 16;
348
- out += B64URL_ALPHABET[value >> 18 & 63] + B64URL_ALPHABET[value >> 12 & 63];
349
- } else if (rest === 2) {
350
- const value = bytes[index] << 16 | bytes[index + 1] << 8;
351
- out += B64URL_ALPHABET[value >> 18 & 63] + B64URL_ALPHABET[value >> 12 & 63] + B64URL_ALPHABET[value >> 6 & 63];
352
- }
353
- return out;
354
- }
355
- function decodeBase64Url(value, field) {
356
- if (!B64URL_PATTERN.test(value) || value.length % 4 === 1) {
357
- throw new ByokCoreError("sealed_secret_invalid", `${field} is not canonical base64url`);
358
- }
359
- const out = new Uint8Array(Math.floor(value.length * 3 / 4));
360
- let buffer = 0;
361
- let bits = 0;
362
- let offset = 0;
363
- for (const char of value) {
364
- buffer = buffer << 6 | B64URL_ALPHABET.indexOf(char);
365
- bits += 6;
366
- if (bits >= 8) {
367
- bits -= 8;
368
- out[offset] = buffer >> bits & 255;
369
- offset += 1;
370
- }
371
- }
372
- if (encodeBase64Url(out) !== value) {
373
- throw new ByokCoreError("sealed_secret_invalid", `${field} is not canonical base64url`);
374
- }
375
- return out;
376
- }
377
- function base64UrlField(bytes) {
378
- return z.string().superRefine((value, ctx) => {
379
- try {
380
- const decoded = decodeBase64Url(value, "field");
381
- if (bytes.exact !== void 0 && decoded.byteLength !== bytes.exact) {
382
- ctx.addIssue({ code: "custom", message: `must decode to exactly ${bytes.exact} bytes` });
383
- }
384
- if (bytes.max !== void 0 && decoded.byteLength > bytes.max) {
385
- ctx.addIssue({ code: "custom", message: `must decode to at most ${bytes.max} bytes` });
386
- }
387
- } catch {
388
- ctx.addIssue({ code: "custom", message: "must be canonical unpadded base64url" });
389
- }
390
- });
391
- }
392
- var SealingPublicJwkSchema = z.strictObject({
393
- kty: z.literal("EC"),
394
- crv: z.literal("P-256"),
395
- x: base64UrlField({ exact: 32 }),
396
- y: base64UrlField({ exact: 32 })
397
- });
398
- var SealingKeyIdSchema = z.string().regex(new RegExp(`^[A-Za-z0-9_-]{${SEALING_KEY_ID_LENGTH}}$`));
399
- function parseSealingPublicJwk(input) {
400
- const result = safeParseTotal(SealingPublicJwkSchema, input);
401
- if (!result.success) {
402
- throw new ByokCoreError("sealed_secret_invalid", "Sealing public key must be a strict P-256 EC public JWK");
403
- }
404
- return result.data;
405
- }
406
- function sealingPublicKeyBytes(publicJwk) {
407
- const jwk = parseSealingPublicJwk(publicJwk);
408
- const raw = new Uint8Array(HPKE_P256_PUBLIC_KEY_BYTES);
409
- raw[0] = 4;
410
- raw.set(decodeBase64Url(jwk.x, "x"), 1);
411
- raw.set(decodeBase64Url(jwk.y, "y"), 33);
412
- return raw;
413
- }
414
- z.strictObject({
415
- version: z.literal(1),
416
- keyId: SealingKeyIdSchema,
417
- epoch: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
418
- publicJwk: SealingPublicJwkSchema
419
- });
420
- var OpaqueIdSchema = z.string().min(1).max(200).superRefine((value, ctx) => {
421
- if (value.trim() !== value || /[\u0000-\u001f\u007f]/u.test(value)) {
422
- ctx.addIssue({ code: "custom", message: "must not carry surrounding whitespace or control characters" });
423
- }
424
- });
425
- var CanonicalTimestampSchema = z.string().refine(isCanonicalTimestamp, "must be a canonical ISO-8601 UTC instant");
426
- var ProfileRefSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/u);
427
- var ProviderKindSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/u);
428
- var ModelIdSchema = z.string().min(1).max(160).refine((value) => value.trim() === value && !/[\u0000-\u001f\u007f]/u.test(value), "model id is invalid");
429
- var CapabilitySchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
430
- var PiModelJsonSchema = z.custom((value) => {
431
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
432
- try {
433
- canonicalizeJson(value);
434
- return true;
435
- } catch {
436
- return false;
437
- }
438
- }, "piModel must be a canonical JSON object");
439
- var ModelConfigFields = {
440
- agentId: OpaqueIdSchema,
441
- providerKind: ProviderKindSchema,
442
- modelId: ModelIdSchema,
443
- piModel: PiModelJsonSchema,
444
- capabilities: z.array(CapabilitySchema).max(8).refine((values) => new Set(values).size === values.length, "capabilities must be unique")
445
- };
446
- var ProviderProvisioningConfigV1Schema = z.discriminatedUnion("operation", [
447
- z.strictObject({ operation: z.literal("configure"), ...ModelConfigFields }),
448
- z.strictObject({ operation: z.literal("update_model"), ...ModelConfigFields }),
449
- z.strictObject({ operation: z.literal("replace_secret"), agentId: OpaqueIdSchema, providerKind: ProviderKindSchema }),
450
- z.strictObject({ operation: z.literal("delete"), agentId: OpaqueIdSchema })
451
- ]);
452
- var ProviderProvisioningExpectedProfileSchema = z.union([
453
- z.null(),
454
- z.strictObject({
455
- profileRef: ProfileRefSchema,
456
- profileRevision: z.string().regex(/^(?:0|[1-9][0-9]{0,19})$/u),
457
- profileHash: z.string().regex(CONTENT_HASH_PATTERN)
458
- })
459
- ]);
460
- var ProviderProvisioningIdentityRevisionSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/u);
461
- var ProviderProvisioningHeaderV1Schema = z.strictObject({
462
- tenantId: OpaqueIdSchema,
463
- deviceId: OpaqueIdSchema,
464
- agentId: OpaqueIdSchema,
465
- requestId: OpaqueIdSchema,
466
- operation: z.enum(PROVIDER_PROVISIONING_OPERATIONS),
467
- /** Non-secret, strictly increasing per bot (A3); a device rejects anything not above its high-watermark. */
468
- operationGeneration: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
469
- expectedProfile: ProviderProvisioningExpectedProfileSchema,
470
- /** The device enrollment revision the Host authorized against (F2). */
471
- expectedEnrollmentRevision: ProviderProvisioningIdentityRevisionSchema,
472
- /** The agent placement revision the Host authorized against (F2). */
473
- expectedPlacementRevision: ProviderProvisioningIdentityRevisionSchema,
474
- configDigest: z.string().regex(CONTENT_HASH_PATTERN),
475
- issuedAt: CanonicalTimestampSchema,
476
- expiresAt: CanonicalTimestampSchema
477
- });
478
- var MAX_CIPHERTEXT_BYTES = PROVIDER_SECRET_MAX_PADDED_BYTES + HPKE_AEAD_TAG_BYTES;
479
- var SealedProviderSecretV1Schema = z.strictObject({
480
- suite: z.literal(PROVIDER_SECRET_HPKE_SUITE),
481
- keyId: SealingKeyIdSchema,
482
- enc: base64UrlField({ exact: HPKE_P256_PUBLIC_KEY_BYTES }),
483
- ciphertext: base64UrlField({ max: MAX_CIPHERTEXT_BYTES })
484
- });
485
- function isSecretOperation(operation) {
486
- return PROVIDER_PROVISIONING_SECRET_OPERATIONS.includes(operation);
487
- }
488
- z.strictObject({
489
- version: z.literal(PROVIDER_PROVISIONING_REQUEST_VERSION),
490
- header: ProviderProvisioningHeaderV1Schema,
491
- config: ProviderProvisioningConfigV1Schema,
492
- sealed: SealedProviderSecretV1Schema.optional()
493
- }).superRefine((request, ctx) => {
494
- if (request.header.operation !== request.config.operation) {
495
- ctx.addIssue({ code: "custom", path: ["config", "operation"], message: "config operation must equal header operation" });
496
- }
497
- if (request.header.agentId !== request.config.agentId) {
498
- ctx.addIssue({ code: "custom", path: ["config", "agentId"], message: "config agentId must equal header agentId" });
499
- }
500
- if (isSecretOperation(request.header.operation) !== (request.sealed !== void 0)) {
501
- ctx.addIssue({ code: "custom", path: ["sealed"], message: "a sealed secret is required exactly for configure and replace_secret" });
502
- }
503
- });
504
-
505
- // src/sealed-payload/primitives.ts
506
- var P256PublicJwkSchema = SealingPublicJwkSchema;
507
- var ECDSA_P256_SIGNATURE_BYTES = 64;
508
- function invalidInput(message) {
509
- return new ByokCoreError("sealed_secret_invalid", message);
510
- }
511
- function utf82(value) {
512
- return new TextEncoder().encode(value);
513
- }
514
- function copy2(bytes) {
515
- return new Uint8Array(bytes);
516
- }
517
- function concat2(...parts) {
518
- const out = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
519
- let offset = 0;
520
- for (const part of parts) {
521
- out.set(part, offset);
522
- offset += part.byteLength;
523
- }
524
- return out;
525
- }
526
- function hex(bytes) {
527
- let out = "";
528
- for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
529
- return out;
530
- }
531
- function randomBytes(length) {
532
- const source = globalThis.crypto;
533
- if (source?.getRandomValues === void 0) {
534
- throw new ByokCoreError("webcrypto_unavailable", "WebCrypto getRandomValues is unavailable in this runtime");
535
- }
536
- return source.getRandomValues(new Uint8Array(length));
537
- }
538
- async function sha256(bytes) {
539
- return new Uint8Array(await webCryptoSubtle().digest("SHA-256", copy2(bytes)));
540
- }
541
- async function hmacSha2562(key, data) {
542
- const subtle = webCryptoSubtle();
543
- const hmacKey = await subtle.importKey("raw", copy2(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
544
- return new Uint8Array(await subtle.sign("HMAC", hmacKey, copy2(data)));
545
- }
546
- async function hkdfSha256(ikm, salt, info, length) {
547
- const subtle = webCryptoSubtle();
548
- const key = await subtle.importKey("raw", copy2(ikm), { name: "HKDF" }, false, ["deriveBits"]);
549
- const bits = await subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt: copy2(salt), info: utf82(info) }, key, length * 8);
550
- return new Uint8Array(bits);
551
- }
552
- function base64UrlBytes(bytes) {
553
- return z.string().refine((value) => {
554
- try {
555
- return decodeBase64Url(value, "field").byteLength === bytes;
556
- } catch {
557
- return false;
558
- }
559
- }, `must be canonical unpadded base64url of ${bytes} bytes`);
560
- }
561
- var ECDSA_P256 = { name: "ECDSA", namedCurve: "P-256" };
562
- var ECDSA_SHA256 = { name: "ECDSA", hash: "SHA-256" };
563
- function ecdsaSubtle() {
564
- return webCryptoSubtle();
565
- }
566
- async function p256PublicKeyBytes(publicJwk) {
567
- const raw = sealingPublicKeyBytes(publicJwk);
568
- await importEcdsaPublicKey(publicJwk);
569
- return raw;
570
- }
571
- async function importEcdsaPublicKey(publicJwk) {
572
- const jwk = P256PublicJwkSchema.parse(publicJwk);
573
- try {
574
- return await ecdsaSubtle().importKey("jwk", { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y }, ECDSA_P256, true, ["verify"]);
575
- } catch (cause) {
576
- throw new ByokCoreError("sealed_secret_invalid", "P-256 public key is not a valid curve point", { cause });
577
- }
578
- }
579
- async function ecdsaSign(publicJwk, privateKey, data) {
580
- let signature;
581
- try {
582
- signature = encodeBase64Url(new Uint8Array(await ecdsaSubtle().sign(ECDSA_SHA256, privateKey, copy2(data))));
583
- } catch (cause) {
584
- throw new ByokCoreError("sealed_secret_invalid", "Signing key is not an ECDSA P-256 private key", { cause });
585
- }
586
- if (!await ecdsaVerify(publicJwk, signature, data)) {
587
- throw invalidInput("Signing private key does not pair with the signing public key");
588
- }
589
- return signature;
590
- }
591
- async function ecdsaVerify(publicJwk, signature, data) {
592
- let signatureBytes;
593
- try {
594
- signatureBytes = decodeBase64Url(signature, "sig");
595
- } catch {
596
- return false;
597
- }
598
- if (signatureBytes.byteLength !== ECDSA_P256_SIGNATURE_BYTES) return false;
599
- const key = await importEcdsaPublicKey(publicJwk);
600
- return ecdsaSubtle().verify(ECDSA_SHA256, key, signatureBytes, copy2(data));
601
- }
602
-
603
- // src/sealed-payload/member-chain.ts
604
- var MEMBER_ID_DOMAIN = "byok-member-id-v1\n";
605
- var MEMBER_ID_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
606
- var MEMBER_CHAIN_VERSION = 1;
607
- var MEMBER_CHAIN_DOMAIN_PREFIX = "byok-member-chain-v1\n";
608
- var MEMBER_CHAIN_OPS = ["add", "remove", "rotate", "scope-policy", "shred"];
609
- var CanonicalJsonObjectSchema = z.unknown().transform((value, ctx) => {
610
- try {
611
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
612
- return JSON.parse(canonicalizeJson(value));
613
- }
614
- } catch {
615
- }
616
- ctx.addIssue({ code: "custom", message: "must be a canonical JSON object" });
617
- return z.NEVER;
618
- });
619
- var unsignedEntryShape = {
620
- v: z.literal(MEMBER_CHAIN_VERSION),
621
- tenantId: z.string().min(1),
622
- seq: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
623
- prev: z.union([z.null(), z.string().regex(CONTENT_HASH_PATTERN)]),
624
- op: z.enum(MEMBER_CHAIN_OPS),
625
- subject: CanonicalJsonObjectSchema,
626
- signer: z.string().regex(MEMBER_ID_PATTERN)
627
- };
628
- function firstEntryHasNoPrev(entry, ctx) {
629
- if (entry.seq === 1 !== (entry.prev === null)) {
630
- ctx.addIssue({ code: "custom", path: ["prev"], message: "the first entry, and only the first, has no prev" });
631
- }
632
- }
633
- var UnsignedMemberChainEntryV1Schema = z.strictObject(unsignedEntryShape).superRefine(firstEntryHasNoPrev);
634
- var MemberChainEntryV1Schema = z.strictObject({ ...unsignedEntryShape, sig: base64UrlBytes(64) }).superRefine(firstEntryHasNoPrev);
635
- async function deriveMemberId(signingPublicJwk) {
636
- const raw = await p256PublicKeyBytes(signingPublicJwk);
637
- return encodeBase64Url(await sha256(concat2(utf82(MEMBER_ID_DOMAIN), raw)));
638
- }
639
- function parseUnsignedEntry(entry) {
640
- const parsed = safeParseTotal(UnsignedMemberChainEntryV1Schema, entry);
641
- if (!parsed.success) throw invalidInput("Member chain entry is invalid");
642
- return parsed.data;
643
- }
644
- function signingInput(entry) {
645
- return concat2(
646
- utf82(MEMBER_CHAIN_DOMAIN_PREFIX),
647
- canonicalizeJsonBytes({
648
- v: entry.v,
649
- tenantId: entry.tenantId,
650
- seq: entry.seq,
651
- prev: entry.prev,
652
- op: entry.op,
653
- subject: entry.subject,
654
- signer: entry.signer
655
- })
656
- );
657
- }
658
- function memberChainSigningInput(entry) {
659
- return signingInput(parseUnsignedEntry(entry));
660
- }
661
- async function memberChainEntryHash(entry) {
662
- return `sha256:${hex(await sha256(memberChainSigningInput(entry)))}`;
663
- }
664
- async function signMemberChainEntry(entry, signer) {
665
- const unsigned = parseUnsignedEntry(entry);
666
- if (await deriveMemberId(signer.publicJwk) !== unsigned.signer) {
667
- throw invalidInput("Member chain entry signer does not match the signing key");
668
- }
669
- return { ...unsigned, sig: await ecdsaSign(signer.publicJwk, signer.privateKey, signingInput(unsigned)) };
670
- }
671
- async function verifyMemberChainEntry(entry, signerPublicJwk) {
672
- const parsed = safeParseTotal(MemberChainEntryV1Schema, entry);
673
- if (!parsed.success || await deriveMemberId(signerPublicJwk) !== parsed.data.signer) {
674
- return { ok: false, reason: "member_chain_entry_invalid" };
675
- }
676
- const input = signingInput(parsed.data);
677
- if (!await ecdsaVerify(signerPublicJwk, parsed.data.sig, input)) {
678
- return { ok: false, reason: "member_chain_signature_invalid" };
679
- }
680
- return { ok: true, entry: parsed.data, hash: `sha256:${hex(await sha256(input))}` };
681
- }
682
- var MEMBER_KEY_BUNDLE_VERSION = 1;
683
- var MEMBER_KEM_DOMAIN_PREFIX = "byok-member-kem-v1\n";
684
- var KemEpochSchema = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
685
- var KemSigSchema = base64UrlBytes(64);
686
- var MemberKeyBundleV1Schema = z.strictObject({
687
- v: z.literal(MEMBER_KEY_BUNDLE_VERSION),
688
- memberId: z.string().regex(MEMBER_ID_PATTERN),
689
- signingPublicJwk: P256PublicJwkSchema,
690
- kemPublicJwk: P256PublicJwkSchema,
691
- kemEpoch: KemEpochSchema,
692
- kemSig: KemSigSchema,
693
- deviceId: z.string().min(1).optional()
694
- });
695
- var CreateInputSchema = z.object({
696
- signingPublicJwk: P256PublicJwkSchema,
697
- signingPrivateKey: z.custom(),
698
- kemPublicJwk: P256PublicJwkSchema,
699
- kemEpoch: KemEpochSchema,
700
- deviceId: z.string().min(1).optional()
701
- });
702
- var KemSignerSchema = z.object({ memberId: z.string().regex(MEMBER_ID_PATTERN), signingPublicJwk: P256PublicJwkSchema });
703
- var KemStatementSchema = z.object({ kemPublicJwk: P256PublicJwkSchema, kemEpoch: KemEpochSchema, kemSig: KemSigSchema });
704
- function memberKemSigningInput(memberId, kemPublicJwk, kemEpoch) {
705
- return concat2(
706
- utf82(MEMBER_KEM_DOMAIN_PREFIX),
707
- canonicalizeJsonBytes({ memberId, kemPublicKey: { x: kemPublicJwk.x, y: kemPublicJwk.y }, kemEpoch })
708
- );
709
- }
710
- async function createMemberKeyBundle(input) {
711
- const parsed = safeParseTotal(CreateInputSchema, input);
712
- if (!parsed.success) throw invalidInput("Member key bundle input is invalid");
713
- const { signingPublicJwk, signingPrivateKey, kemPublicJwk, kemEpoch, deviceId } = parsed.data;
714
- const memberId = await deriveMemberId(signingPublicJwk);
715
- if (!await isValidMemberKemKey(kemPublicJwk, signingPublicJwk)) throw invalidInput("Member KEM key is invalid");
716
- const kemSig = await ecdsaSign(signingPublicJwk, signingPrivateKey, memberKemSigningInput(memberId, kemPublicJwk, kemEpoch));
717
- return {
718
- v: MEMBER_KEY_BUNDLE_VERSION,
719
- memberId,
720
- signingPublicJwk,
721
- kemPublicJwk,
722
- kemEpoch,
723
- kemSig,
724
- ...deviceId === void 0 ? {} : { deviceId }
725
- };
726
- }
727
- async function isValidMemberKemKey(kemPublicJwk, signingPublicJwk) {
728
- if (kemPublicJwk.x === signingPublicJwk.x && kemPublicJwk.y === signingPublicJwk.y) return false;
729
- try {
730
- await p256PublicKeyBytes(kemPublicJwk);
731
- return true;
732
- } catch {
733
- return false;
734
- }
735
- }
736
- async function verifyMemberKem(member, kem) {
737
- const signer = safeParseTotal(KemSignerSchema, member);
738
- const statement = safeParseTotal(KemStatementSchema, kem);
739
- if (!signer.success || !statement.success) return false;
740
- const { memberId, signingPublicJwk } = signer.data;
741
- const { kemPublicJwk, kemEpoch, kemSig } = statement.data;
742
- if (!await isValidMemberKemKey(kemPublicJwk, signingPublicJwk)) return false;
743
- try {
744
- return await ecdsaVerify(signingPublicJwk, kemSig, memberKemSigningInput(memberId, kemPublicJwk, kemEpoch));
745
- } catch {
746
- return false;
747
- }
748
- }
749
- async function parseMemberKeyBundle(value) {
750
- const parsed = safeParseTotal(MemberKeyBundleV1Schema, value);
751
- if (!parsed.success) return void 0;
752
- const bundle = parsed.data;
753
- try {
754
- if (await deriveMemberId(bundle.signingPublicJwk) !== bundle.memberId) return void 0;
755
- } catch {
756
- return void 0;
757
- }
758
- if (!await verifyMemberKem(bundle, bundle)) return void 0;
759
- return bundle;
760
- }
761
-
762
- // src/sealed-payload/scope-key.ts
763
- var SCOPE_KEY_BYTES = 32;
764
- var SCOPE_KEY_ID_PATTERN = /^[A-Za-z0-9_-]{22}$/u;
765
- var KEY_ID_MESSAGE = "byok-scope-key-id-v1";
766
- var CONTENT_KEY_INFO = "byok-sdk.sealed.v1.enc";
767
- var MAC_KEY_INFO = "byok-sdk.sealed.v1.mac";
768
- var SEALED_SALT_BYTES = 32;
769
- var SCOPE_KEY_PACKAGE_VERSION = 1;
770
- var SCOPE_KEY_HPKE_INFO = "byok-sdk.scope-key.v1";
771
- var SCOPE_KEY_PACKAGE_DOMAIN_PREFIX = "byok-scope-key-package-v1\n";
772
- var SCOPE_KEY_PACKAGE_V2 = 2;
773
- var SCOPE_KEY_HPKE_INFO_V2 = "byok-sdk.scope-key.v2";
774
- var SCOPE_KEY_PACKAGE_V2_DOMAIN_PREFIX = "byok-scope-key-package-v2\n";
775
- var P256_POINT_BYTES = 65;
776
- var AEAD_TAG_BYTES = 16;
777
- function assertScopeKey(scopeKey) {
778
- if (scopeKey.byteLength !== SCOPE_KEY_BYTES) throw invalidInput(`Scope key must be ${SCOPE_KEY_BYTES} bytes`);
779
- }
780
- function generateScopeKey() {
781
- return randomBytes(SCOPE_KEY_BYTES);
782
- }
783
- async function deriveScopeKeyId(scopeKey) {
784
- assertScopeKey(scopeKey);
785
- const mac = await hmacSha2562(scopeKey, utf82(KEY_ID_MESSAGE));
786
- return encodeBase64Url(mac.subarray(0, 16));
787
- }
788
- async function deriveSealedContentKey(scopeKey, salt) {
789
- assertScopeKey(scopeKey);
790
- if (salt.byteLength !== SEALED_SALT_BYTES) throw invalidInput(`Sealed content salt must be ${SEALED_SALT_BYTES} bytes`);
791
- return hkdfSha256(scopeKey, salt, CONTENT_KEY_INFO, 32);
792
- }
793
- async function deriveSealedMacKey(scopeKey) {
794
- assertScopeKey(scopeKey);
795
- return hkdfSha256(scopeKey, new Uint8Array(32), MAC_KEY_INFO, 32);
796
- }
797
- var ScopeKeyPackageV1Schema = z.strictObject({
798
- v: z.literal(SCOPE_KEY_PACKAGE_VERSION),
799
- tenantId: z.string().min(1),
800
- scopeId: z.string().min(1),
801
- epoch: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
802
- keyId: z.string().regex(SCOPE_KEY_ID_PATTERN),
803
- recipient: z.string().regex(MEMBER_ID_PATTERN),
804
- sender: z.string().regex(MEMBER_ID_PATTERN),
805
- enc: base64UrlBytes(P256_POINT_BYTES),
806
- ciphertext: base64UrlBytes(SCOPE_KEY_BYTES + AEAD_TAG_BYTES),
807
- sig: base64UrlBytes(64)
808
- });
809
- var ScopeKeyPackageV2Schema = z.strictObject({
810
- ...ScopeKeyPackageV1Schema.shape,
811
- v: z.literal(SCOPE_KEY_PACKAGE_V2),
812
- recipientKemEpoch: KemEpochSchema
813
- });
814
- var ScopeKeyPackageSchema = z.union([ScopeKeyPackageV1Schema, ScopeKeyPackageV2Schema]);
815
- function packageAad({ enc: _enc, ciphertext: _ciphertext, sig: _sig, ...identity }) {
816
- return canonicalizeJsonBytes(identity);
817
- }
818
- function packageSigningInput({ sig: _sig, ...pkg }) {
819
- return concat2(utf82(pkg.v === SCOPE_KEY_PACKAGE_V2 ? SCOPE_KEY_PACKAGE_V2_DOMAIN_PREFIX : SCOPE_KEY_PACKAGE_DOMAIN_PREFIX), canonicalizeJsonBytes(pkg));
820
- }
821
- var PackageIdentitySchema = ScopeKeyPackageV2Schema.omit({ enc: true, ciphertext: true, sig: true });
822
- async function sealScopeKeyPackage(input) {
823
- const parsed = safeParseTotal(PackageIdentitySchema, {
824
- v: SCOPE_KEY_PACKAGE_V2,
825
- tenantId: input.tenantId,
826
- scopeId: input.scopeId,
827
- epoch: input.epoch,
828
- keyId: await deriveScopeKeyId(input.scopeKey),
829
- recipient: input.recipient.memberId,
830
- recipientKemEpoch: input.recipient.kemEpoch,
831
- sender: await deriveMemberId(input.sender.signingPublicJwk)
832
- });
833
- if (!parsed.success) throw invalidInput("Scope key package input is invalid");
834
- const identity = parsed.data;
835
- const { enc, ciphertext } = await hpkeSealBase({
836
- recipientPublicKey: await p256PublicKeyBytes(input.recipient.kemPublicJwk),
837
- info: utf82(SCOPE_KEY_HPKE_INFO_V2),
838
- aad: packageAad(identity),
839
- plaintext: input.scopeKey
840
- });
841
- const unsigned = { ...identity, enc: encodeBase64Url(enc), ciphertext: encodeBase64Url(ciphertext) };
842
- return { ...unsigned, sig: await ecdsaSign(input.sender.signingPublicJwk, input.sender.signingPrivateKey, packageSigningInput(unsigned)) };
843
- }
844
- async function openScopeKeyPackage(input) {
845
- const parsed = safeParseTotal(ScopeKeyPackageSchema, input.package);
846
- if (!parsed.success) return { ok: false, reason: "key_package_invalid" };
847
- const pkg = parsed.data;
848
- if (pkg.recipient !== input.recipient.memberId || pkg.v === SCOPE_KEY_PACKAGE_V2 && pkg.recipientKemEpoch !== input.recipient.kemEpoch || pkg.tenantId !== input.expected.tenantId || pkg.scopeId !== input.expected.scopeId || pkg.epoch !== input.expected.epoch || pkg.sender !== await deriveMemberId(input.senderSigningPublicJwk)) {
849
- return { ok: false, reason: "key_package_invalid" };
850
- }
851
- if (!await ecdsaVerify(input.senderSigningPublicJwk, pkg.sig, packageSigningInput(pkg))) {
852
- return { ok: false, reason: "key_package_signature_invalid" };
853
- }
854
- let scopeKey;
855
- try {
856
- scopeKey = await hpkeOpenBase({
857
- recipientPrivateKey: input.recipient.kemPrivateKey,
858
- recipientPublicKey: await p256PublicKeyBytes(input.recipient.kemPublicJwk),
859
- enc: decodeBase64Url(pkg.enc, "enc"),
860
- info: utf82(pkg.v === SCOPE_KEY_PACKAGE_V2 ? SCOPE_KEY_HPKE_INFO_V2 : SCOPE_KEY_HPKE_INFO),
861
- aad: packageAad(pkg),
862
- ciphertext: decodeBase64Url(pkg.ciphertext, "ciphertext")
863
- });
864
- } catch (error) {
865
- if (error instanceof ByokCoreError) return { ok: false, reason: "key_package_open_failed" };
866
- throw error;
867
- }
868
- if (scopeKey.byteLength !== SCOPE_KEY_BYTES || await deriveScopeKeyId(scopeKey) !== pkg.keyId) {
869
- scopeKey.fill(0);
870
- return { ok: false, reason: "key_package_open_failed" };
871
- }
872
- return { ok: true, scopeKey, keyId: pkg.keyId };
873
- }
874
-
875
- // src/sealed-payload/sealed-json.ts
876
- function canonicalValue(value, seen) {
877
- if (value === null) return "null";
878
- switch (typeof value) {
879
- case "boolean":
880
- return value ? "true" : "false";
881
- case "string":
882
- return JSON.stringify(value);
883
- case "number":
884
- if (!Number.isFinite(value)) throw invalidInput("Sealed plaintext holds a number with no JSON form");
885
- return String(value);
886
- case "object":
887
- break;
888
- default:
889
- throw invalidInput(`Sealed plaintext holds a ${typeof value}, which is not a JSON type`);
890
- }
891
- if (seen.has(value)) throw invalidInput("Sealed plaintext holds a circular reference");
892
- seen.add(value);
893
- try {
894
- if (Array.isArray(value)) return `[${Array.from(value, (entry) => canonicalValue(entry, seen)).join(",")}]`;
895
- const prototype = Object.getPrototypeOf(value);
896
- if (prototype !== Object.prototype && prototype !== null) throw invalidInput("Sealed plaintext holds an object that is not plain");
897
- const record = value;
898
- const members = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalValue(record[key], seen)}`);
899
- return `{${members.join(",")}}`;
900
- } finally {
901
- seen.delete(value);
902
- }
903
- }
904
- function canonicalizeSealedJson(value) {
905
- return canonicalValue(value, /* @__PURE__ */ new Set());
906
- }
907
-
908
- // src/sealed-payload/sealed-content.ts
909
- var SEALED_CONTENT_VERSION = 1;
910
- var SEALED_CONTENT_SUITE = "byok-sealed-v1.hkdf-sha256.aes-256-gcm";
911
- var SEALED_CATEGORIES = ["task-input", "agent-message", "task-output", "memory-projection"];
912
- var SEALED_DIRECTIONS = ["server-to-daemon", "daemon-to-server"];
913
- var SEALED_NONCE_BYTES = 12;
914
- var AEAD_TAG_BYTES2 = 16;
915
- var FRAME_HEADER_BYTES = 4;
916
- var ALIGNED_FRAME_LIMIT = 16 * 1024;
917
- var FRAME_ALIGNMENT = 256;
918
- var MAX_CONTENT_LENGTH = 4294967295;
919
- var CONTENT_MAC_PREFIX = "byok-content-mac-v1\n";
920
- var CONTENT_MAC_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/u;
921
- var FAMILY_PATTERN = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/u;
922
- var IDENTITY_FIELD_PATTERN = /^[a-z][A-Za-z0-9]*$/u;
923
- var ROUTINE_TEMPLATE_FAMILY = "routine.template";
924
- function floorLog2(value) {
925
- let exponent = 0;
926
- for (let remaining = value; remaining >= 2; remaining = Math.floor(remaining / 2)) exponent += 1;
927
- return exponent;
928
- }
929
- function sealedPaddedLength(frameLength) {
930
- if (!Number.isSafeInteger(frameLength) || frameLength < FRAME_HEADER_BYTES) {
931
- throw invalidInput("Sealed frame length must be a safe integer of at least 4");
932
- }
933
- if (frameLength <= ALIGNED_FRAME_LIMIT) return Math.ceil(frameLength / FRAME_ALIGNMENT) * FRAME_ALIGNMENT;
934
- const exponent = floorLog2(frameLength);
935
- const step = 2 ** (exponent - (floorLog2(exponent) + 1));
936
- return Math.ceil(frameLength / step) * step;
937
- }
938
- function isLegalPaddedLength(paddedLength) {
939
- return Number.isSafeInteger(paddedLength) && paddedLength >= FRAME_ALIGNMENT && sealedPaddedLength(paddedLength) === paddedLength;
940
- }
941
- function encodeSealedFrame(content) {
942
- if (content.byteLength > MAX_CONTENT_LENGTH) throw invalidInput("Sealed content exceeds the 32-bit frame length");
943
- const frame = new Uint8Array(sealedPaddedLength(FRAME_HEADER_BYTES + content.byteLength));
944
- new DataView(frame.buffer).setUint32(0, content.byteLength, false);
945
- frame.set(content, FRAME_HEADER_BYTES);
946
- return frame;
947
- }
948
- function decodeSealedFrame(frame, paddedLength, maxContentLength) {
949
- if (frame.byteLength !== paddedLength || paddedLength < FRAME_HEADER_BYTES) return void 0;
950
- const contentLength = new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(0, false);
951
- if (contentLength > paddedLength - FRAME_HEADER_BYTES) return void 0;
952
- if (sealedPaddedLength(FRAME_HEADER_BYTES + contentLength) !== paddedLength) return void 0;
953
- if (contentLength > maxContentLength) return void 0;
954
- for (let index = FRAME_HEADER_BYTES + contentLength; index < paddedLength; index += 1) {
955
- if (frame[index] !== 0) return void 0;
956
- }
957
- return frame.subarray(FRAME_HEADER_BYTES, FRAME_HEADER_BYTES + contentLength);
958
- }
959
- var NonEmptyStringSchema = z.string().min(1);
960
- var PositiveIntegerSchema = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
961
- var SealedContentHeaderV1Schema = z.strictObject({
962
- v: z.literal(SEALED_CONTENT_VERSION),
963
- suite: z.literal(SEALED_CONTENT_SUITE),
964
- category: z.enum(SEALED_CATEGORIES),
965
- scope: z.strictObject({
966
- id: NonEmptyStringSchema,
967
- epoch: PositiveIntegerSchema,
968
- keyId: z.string().regex(SCOPE_KEY_ID_PATTERN)
969
- }),
970
- chainHead: z.strictObject({
971
- seq: PositiveIntegerSchema,
972
- hash: z.string().regex(CONTENT_HASH_PATTERN)
973
- }),
974
- sender: z.string().regex(MEMBER_ID_PATTERN),
975
- salt: base64UrlBytes(SEALED_SALT_BYTES),
976
- nonce: base64UrlBytes(SEALED_NONCE_BYTES),
977
- paddedLength: PositiveIntegerSchema.refine(isLegalPaddedLength, "must be a legal padded frame length"),
978
- contentMac: z.string().regex(CONTENT_MAC_PATTERN)
979
- });
980
- function isCanonicalCategoryList(categories) {
981
- return categories.every((category, index) => index === 0 || SEALED_CATEGORIES.indexOf(categories[index - 1]) < SEALED_CATEGORIES.indexOf(category));
982
- }
983
- var SealedBindingSchema = z.strictObject({
984
- tenantId: NonEmptyStringSchema,
985
- family: z.string().regex(FAMILY_PATTERN),
986
- direction: z.enum(SEALED_DIRECTIONS),
987
- identity: z.record(
988
- z.string().regex(IDENTITY_FIELD_PATTERN),
989
- z.union([NonEmptyStringSchema, z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)])
990
- ).refine((identity) => Object.keys(identity).length > 0, "identity must name at least one field"),
991
- deviceId: z.union([z.null(), NonEmptyStringSchema]),
992
- agentRef: z.union([z.null(), z.strictObject({ agentId: NonEmptyStringSchema, profileRevision: NonEmptyStringSchema })]),
993
- categories: z.array(z.enum(SEALED_CATEGORIES)).min(1).refine(isCanonicalCategoryList, "categories must be unique and in canonical order"),
994
- notAfter: z.union([z.null(), z.string().refine(isCanonicalTimestamp, "must be a canonical UTC instant")])
995
- }).superRefine((binding, ctx) => {
996
- const template = binding.family === ROUTINE_TEMPLATE_FAMILY;
997
- if (binding.deviceId === null !== template || binding.notAfter === null === template) {
998
- ctx.addIssue({
999
- code: "custom",
1000
- path: ["deviceId"],
1001
- message: "a routine template binds an expiry and no device; every other family binds a device and no expiry"
1002
- });
1003
- }
1004
- });
1005
- function parseBinding(binding) {
1006
- const parsed = safeParseTotal(SealedBindingSchema, binding);
1007
- if (!parsed.success) throw invalidInput("Sealed content binding is invalid");
1008
- return parsed.data;
1009
- }
1010
- function assertMaxContentLength(maxContentLength) {
1011
- if (!Number.isSafeInteger(maxContentLength) || maxContentLength < 0 || maxContentLength > MAX_CONTENT_LENGTH) {
1012
- throw invalidInput("Sealed content limit must be a non-negative 32-bit integer");
1013
- }
1014
- }
1015
- function bindingAad(header, binding) {
1016
- return {
1017
- v: header.v,
1018
- suite: header.suite,
1019
- category: header.category,
1020
- tenantId: binding.tenantId,
1021
- scopeId: header.scope.id,
1022
- epoch: header.scope.epoch,
1023
- keyId: header.scope.keyId,
1024
- chainHead: { seq: header.chainHead.seq, hash: header.chainHead.hash },
1025
- sender: header.sender,
1026
- family: binding.family,
1027
- direction: binding.direction,
1028
- identity: binding.identity,
1029
- deviceId: binding.deviceId,
1030
- agentRef: binding.agentRef === null ? null : { agentId: binding.agentRef.agentId, profileRevision: binding.agentRef.profileRevision },
1031
- categories: [...binding.categories],
1032
- notAfter: binding.notAfter
1033
- };
1034
- }
1035
- function aadBytes(header, binding) {
1036
- return canonicalizeJsonBytes({ ...bindingAad(header, binding), paddedLength: header.paddedLength, contentMac: header.contentMac });
1037
- }
1038
- function isNonEmptyPlainObject(value) {
1039
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1040
- const prototype = Object.getPrototypeOf(value);
1041
- return (prototype === Object.prototype || prototype === null) && Object.keys(value).length > 0;
1042
- }
1043
- function canonicalPlaintext(plaintext) {
1044
- try {
1045
- if (isNonEmptyPlainObject(plaintext)) return canonicalizeSealedJson(plaintext);
1046
- } catch (cause) {
1047
- throw new ByokCoreError("sealed_secret_invalid", "Sealed plaintext is not canonical JSON", { cause });
1048
- }
1049
- throw invalidInput("Sealed plaintext must be a non-empty plain JSON object");
1050
- }
1051
- function parseCanonicalPlaintext(content) {
1052
- let text;
1053
- let value;
1054
- try {
1055
- text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(content);
1056
- value = JSON.parse(text);
1057
- if (!isNonEmptyPlainObject(value) || canonicalizeSealedJson(value) !== text) return void 0;
1058
- } catch {
1059
- return void 0;
1060
- }
1061
- return { value, text };
1062
- }
1063
- async function contentMacOf(scopeKey, category, binding, plaintextText) {
1064
- const macKey = await deriveSealedMacKey(scopeKey);
1065
- try {
1066
- const input = `${CONTENT_MAC_PREFIX}${category}
4
+ var CONTENT_HASH_PATTERN=/^sha256:[0-9a-f]{64}$/;var DEVICE_PROOF_SCHEMA_ID="byok-device-proof-v1";var DEVICE_PROOF_VERSION=1;var DEVICE_PROOF_ALGORITHMS=["ed25519"];var BASE64URL_PATTERN=/^[A-Za-z0-9_-]+$/;var HTTP_METHOD_PATTERN=/^[A-Z]+$/;function canonicalizationFailure(path,reason){return new ByokCoreError("proof_canonicalization_failed",`Cannot canonicalize ${path}: ${reason}`)}function isPlainObject(value){const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null}function canonicalizeValue(value,path,seen){if(value===null)return "null";switch(typeof value){case "boolean":return value?"true":"false";case "string":return JSON.stringify(value);case "number":{if(!Number.isSafeInteger(value)){throw canonicalizationFailure(path,`only safe integers are canonicalizable, received ${String(value)}`)}return String(value===0?0:value)}case "undefined":throw canonicalizationFailure(path,"undefined has no canonical form");case "bigint":throw canonicalizationFailure(path,"bigint is not a JSON type");case "function":case "symbol":throw canonicalizationFailure(path,`${typeof value} is not a JSON type`);case "object":break;default:throw canonicalizationFailure(path,`unsupported type ${typeof value}`)}const objectValue=value;if(seen.has(objectValue)){throw canonicalizationFailure(path,"circular reference")}seen.add(objectValue);try{if(Array.isArray(objectValue)){const parts2=objectValue.map((entry,index)=>canonicalizeValue(entry,`${path}[${index}]`,seen));return `[${parts2.join(",")}]`}if(!isPlainObject(objectValue)){throw canonicalizationFailure(path,"only plain objects and arrays are canonicalizable")}const keys=Object.keys(objectValue).sort();const record=objectValue;const parts=keys.map(key=>{const encodedKey=JSON.stringify(key);const encodedValue=canonicalizeValue(record[key],`${path}.${key}`,seen);return `${encodedKey}:${encodedValue}`});return `{${parts.join(",")}}`}finally{seen.delete(objectValue);}}function canonicalizeJson(value){return canonicalizeValue(value,"<root>",new Set)}function canonicalizeJsonBytes(value){return new TextEncoder().encode(canonicalizeJson(value))}var DeviceProofProtectedClaimsSchema=z.strictObject({version:z.literal(DEVICE_PROOF_VERSION),tenantId:z.string().min(1),productId:z.string().min(1),deviceId:z.string().min(1),keyId:z.string().min(1),keyEpoch:z.number().int().nonnegative(),requestId:z.string().min(1),operation:z.string().min(1),resource:z.string().min(1),method:z.string().regex(HTTP_METHOD_PATTERN).optional(),path:z.string().startsWith("/").optional(),operationId:z.string().min(1).optional(),bodySha256:z.string().regex(CONTENT_HASH_PATTERN),bodySize:z.number().int().nonnegative(),issuedAt:z.iso.datetime(),expiresAt:z.iso.datetime().optional(),nonce:z.string().min(1).optional()}).superRefine((claims,ctx)=>{const hasRequestLine=claims.method!==void 0&&claims.path!==void 0;const hasOperationId=claims.operationId!==void 0;if(hasRequestLine===hasOperationId){ctx.addIssue({code:"custom",message:"Claims must carry exactly one request binding: both `method` and `path`, or `operationId`."});}if(!hasRequestLine&&(claims.method!==void 0||claims.path!==void 0)){ctx.addIssue({code:"custom",message:"`method` and `path` must be supplied together."});}});z.strictObject({schema:z.literal(DEVICE_PROOF_SCHEMA_ID),algorithm:z.enum(DEVICE_PROOF_ALGORITHMS),protected:DeviceProofProtectedClaimsSchema,signature:z.string().regex(BASE64URL_PATTERN)});var CANONICAL_TIMESTAMP_PATTERN=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;function isCanonicalTimestamp(value){if(typeof value!=="string")return false;if(!CANONICAL_TIMESTAMP_PATTERN.test(value))return false;const parsed=Date.parse(value);if(!Number.isFinite(parsed))return false;return new Date(parsed).toISOString()===value}function safeParseTotal(schema,value){try{return schema.safeParse(value)}catch{return {success:false}}}function webCryptoSubtle(){const subtle=globalThis.crypto?.subtle;if(subtle===void 0){throw new ByokCoreError("webcrypto_unavailable","WebCrypto subtle is unavailable in this runtime")}return subtle}var KEM_ID=16;var KDF_ID=1;var AEAD_ID=1;var HPKE_P256_PUBLIC_KEY_BYTES=65;var N_SECRET=32;var N_K=16;var N_N=12;var N_H=32;var HPKE_AEAD_TAG_BYTES=16;var MODE_BASE=0;var HPKE_V1=utf8("HPKE-v1");var KEM_SUITE_ID=concat(utf8("KEM"),i2osp(KEM_ID,2));var HPKE_SUITE_ID=concat(utf8("HPKE"),i2osp(KEM_ID,2),i2osp(KDF_ID,2),i2osp(AEAD_ID,2));var EMPTY=new Uint8Array(0);var ZERO_SALT=new Uint8Array(N_H);function utf8(value){return new TextEncoder().encode(value)}function concat(...parts){const length=parts.reduce((total,part)=>total+part.byteLength,0);const out=new Uint8Array(length);let offset=0;for(const part of parts){out.set(part,offset);offset+=part.byteLength;}return out}function i2osp(value,length){if(!Number.isSafeInteger(value)||value<0){throw new ByokCoreError("sealed_secret_invalid","HPKE integer encoding requires a non-negative safe integer")}const out=new Uint8Array(length);let remaining=value;for(let index=length-1;index>=0;index-=1){out[index]=remaining%256;remaining=Math.floor(remaining/256);}if(remaining!==0){throw new ByokCoreError("sealed_secret_invalid","HPKE integer does not fit the requested width")}return out}function copy(bytes){return new Uint8Array(bytes)}async function hmacSha256(key,data){const subtle=webCryptoSubtle();const hmacKey=await subtle.importKey("raw",copy(key),{name:"HMAC",hash:"SHA-256"},false,["sign"]);return new Uint8Array(await subtle.sign("HMAC",hmacKey,copy(data)))}async function hkdfExpand(prk,info,length){if(length>255*N_H){throw new ByokCoreError("sealed_secret_invalid","HKDF output length exceeds its limit")}const out=new Uint8Array(length);let previous=EMPTY;let offset=0;for(let counter=1;offset<length;counter+=1){previous=await hmacSha256(prk,concat(previous,info,Uint8Array.of(counter)));const take=Math.min(previous.byteLength,length-offset);out.set(previous.subarray(0,take),offset);offset+=take;}return out}async function labeledExtract(suiteId,salt,label,ikm){return hmacSha256(salt.byteLength===0?ZERO_SALT:salt,concat(HPKE_V1,suiteId,utf8(label),ikm))}async function labeledExpand(suiteId,prk,label,info,length){return hkdfExpand(prk,concat(i2osp(length,2),HPKE_V1,suiteId,utf8(label),info),length)}async function importP256PublicKey(raw){if(raw.byteLength!==HPKE_P256_PUBLIC_KEY_BYTES||raw[0]!==4){throw new ByokCoreError("sealed_secret_invalid","P-256 public key must be a 65-byte uncompressed point")}try{return await webCryptoSubtle().importKey("raw",copy(raw),{name:"ECDH",namedCurve:"P-256"},true,[])}catch(cause){throw new ByokCoreError("sealed_secret_invalid","P-256 public key is not a valid curve point",{cause})}}async function ecdhP256(privateKey,publicKey){const bits=await webCryptoSubtle().deriveBits({name:"ECDH",public:publicKey},privateKey,N_SECRET*8);return new Uint8Array(bits)}async function extractAndExpand(dh,kemContext){const eaePrk=await labeledExtract(KEM_SUITE_ID,EMPTY,"eae_prk",dh);return labeledExpand(KEM_SUITE_ID,eaePrk,"shared_secret",kemContext,N_SECRET)}async function keyScheduleBase(sharedSecret,info){const pskIdHash=await labeledExtract(HPKE_SUITE_ID,EMPTY,"psk_id_hash",EMPTY);const infoHash=await labeledExtract(HPKE_SUITE_ID,EMPTY,"info_hash",info);const context=concat(Uint8Array.of(MODE_BASE),pskIdHash,infoHash);const secret=await labeledExtract(HPKE_SUITE_ID,sharedSecret,"secret",EMPTY);return {sharedSecret,key:await labeledExpand(HPKE_SUITE_ID,secret,"key",context,N_K),baseNonce:await labeledExpand(HPKE_SUITE_ID,secret,"base_nonce",context,N_N),exporterSecret:await labeledExpand(HPKE_SUITE_ID,secret,"exp",context,N_H)}}async function setupBaseSender(recipientPublicKey,info,ephemeral){const pkR=await importP256PublicKey(recipientPublicKey);let pair=ephemeral;if(pair===void 0){const subtle=webCryptoSubtle();const generated=await subtle.generateKey({name:"ECDH",namedCurve:"P-256"},true,["deriveBits"]);pair={privateKey:generated.privateKey,publicKey:new Uint8Array(await subtle.exportKey("raw",generated.publicKey))};}const enc=copy(pair.publicKey);const dh=await ecdhP256(pair.privateKey,pkR);const sharedSecret=await extractAndExpand(dh,concat(enc,recipientPublicKey));return {enc,schedule:await keyScheduleBase(sharedSecret,info)}}async function setupBaseRecipient(enc,recipientPrivateKey,recipientPublicKey,info){if(recipientPublicKey.byteLength!==HPKE_P256_PUBLIC_KEY_BYTES||recipientPublicKey[0]!==4){throw new ByokCoreError("sealed_secret_invalid","P-256 recipient public key must be a 65-byte uncompressed point")}const pkE=await importP256PublicKey(enc);let dh;try{dh=await ecdhP256(recipientPrivateKey,pkE);}catch(cause){throw new ByokCoreError("sealed_secret_open_failed","HPKE decapsulation failed",{cause})}const sharedSecret=await extractAndExpand(dh,concat(enc,recipientPublicKey));return keyScheduleBase(sharedSecret,info)}function hpkeNonce(baseNonce,sequence){const seq=i2osp(sequence,N_N);const out=new Uint8Array(N_N);for(let index=0;index<N_N;index+=1)out[index]=baseNonce[index]^seq[index];return out}async function aesGcmKey(key,usage){return webCryptoSubtle().importKey("raw",copy(key),{name:"AES-GCM"},false,[usage])}async function hpkeAeadSeal(schedule,sequence,aad,plaintext){const ciphertext=await webCryptoSubtle().encrypt({name:"AES-GCM",iv:hpkeNonce(schedule.baseNonce,sequence),additionalData:copy(aad),tagLength:128},await aesGcmKey(schedule.key,"encrypt"),copy(plaintext));return new Uint8Array(ciphertext)}async function hpkeAeadOpen(schedule,sequence,aad,ciphertext){if(ciphertext.byteLength<HPKE_AEAD_TAG_BYTES){throw new ByokCoreError("sealed_secret_invalid","HPKE ciphertext is shorter than its authentication tag")}try{const plaintext=await webCryptoSubtle().decrypt({name:"AES-GCM",iv:hpkeNonce(schedule.baseNonce,sequence),additionalData:copy(aad),tagLength:128},await aesGcmKey(schedule.key,"decrypt"),copy(ciphertext));return new Uint8Array(plaintext)}catch(cause){throw new ByokCoreError("sealed_secret_open_failed","HPKE ciphertext failed authentication",{cause})}}async function hpkeSealBase(input){const{enc,schedule}=await setupBaseSender(input.recipientPublicKey,input.info);return {enc,ciphertext:await hpkeAeadSeal(schedule,0,input.aad,input.plaintext)}}async function hpkeOpenBase(input){const schedule=await setupBaseRecipient(input.enc,input.recipientPrivateKey,input.recipientPublicKey,input.info);return hpkeAeadOpen(schedule,0,input.aad,input.ciphertext)}var PROVIDER_SECRET_HPKE_SUITE="hpke-base.dhkem-p256-hkdf-sha256.hkdf-sha256.aes-128-gcm";var PROVIDER_PROVISIONING_REQUEST_VERSION=1;var PROVIDER_PROVISIONING_OPERATIONS=["configure","update_model","replace_secret","delete"];var PROVIDER_PROVISIONING_SECRET_OPERATIONS=["configure","replace_secret"];var PROVIDER_SECRET_MAX_PADDED_BYTES=4096;var SEALING_KEY_ID_LENGTH=22;var B64URL_ALPHABET="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";var B64URL_PATTERN=/^[A-Za-z0-9_-]*$/;function encodeBase64Url(bytes){let out="";let index=0;for(;index+3<=bytes.length;index+=3){const value=bytes[index]<<16|bytes[index+1]<<8|bytes[index+2];out+=B64URL_ALPHABET[value>>18&63]+B64URL_ALPHABET[value>>12&63]+B64URL_ALPHABET[value>>6&63]+B64URL_ALPHABET[value&63];}const rest=bytes.length-index;if(rest===1){const value=bytes[index]<<16;out+=B64URL_ALPHABET[value>>18&63]+B64URL_ALPHABET[value>>12&63];}else if(rest===2){const value=bytes[index]<<16|bytes[index+1]<<8;out+=B64URL_ALPHABET[value>>18&63]+B64URL_ALPHABET[value>>12&63]+B64URL_ALPHABET[value>>6&63];}return out}function decodeBase64Url(value,field){if(!B64URL_PATTERN.test(value)||value.length%4===1){throw new ByokCoreError("sealed_secret_invalid",`${field} is not canonical base64url`)}const out=new Uint8Array(Math.floor(value.length*3/4));let buffer=0;let bits=0;let offset=0;for(const char of value){buffer=buffer<<6|B64URL_ALPHABET.indexOf(char);bits+=6;if(bits>=8){bits-=8;out[offset]=buffer>>bits&255;offset+=1;}}if(encodeBase64Url(out)!==value){throw new ByokCoreError("sealed_secret_invalid",`${field} is not canonical base64url`)}return out}function base64UrlField(bytes){return z.string().superRefine((value,ctx)=>{try{const decoded=decodeBase64Url(value,"field");if(bytes.exact!==void 0&&decoded.byteLength!==bytes.exact){ctx.addIssue({code:"custom",message:`must decode to exactly ${bytes.exact} bytes`});}if(bytes.max!==void 0&&decoded.byteLength>bytes.max){ctx.addIssue({code:"custom",message:`must decode to at most ${bytes.max} bytes`});}}catch{ctx.addIssue({code:"custom",message:"must be canonical unpadded base64url"});}})}var SealingPublicJwkSchema=z.strictObject({kty:z.literal("EC"),crv:z.literal("P-256"),x:base64UrlField({exact:32}),y:base64UrlField({exact:32})});var SealingKeyIdSchema=z.string().regex(new RegExp(`^[A-Za-z0-9_-]{${SEALING_KEY_ID_LENGTH}}$`));function parseSealingPublicJwk(input){const result=safeParseTotal(SealingPublicJwkSchema,input);if(!result.success){throw new ByokCoreError("sealed_secret_invalid","Sealing public key must be a strict P-256 EC public JWK")}return result.data}function sealingPublicKeyBytes(publicJwk){const jwk=parseSealingPublicJwk(publicJwk);const raw=new Uint8Array(HPKE_P256_PUBLIC_KEY_BYTES);raw[0]=4;raw.set(decodeBase64Url(jwk.x,"x"),1);raw.set(decodeBase64Url(jwk.y,"y"),33);return raw}z.strictObject({version:z.literal(1),keyId:SealingKeyIdSchema,epoch:z.number().int().positive().max(Number.MAX_SAFE_INTEGER),publicJwk:SealingPublicJwkSchema});var OpaqueIdSchema=z.string().min(1).max(200).superRefine((value,ctx)=>{if(value.trim()!==value||/[\u0000-\u001f\u007f]/u.test(value)){ctx.addIssue({code:"custom",message:"must not carry surrounding whitespace or control characters"});}});var CanonicalTimestampSchema=z.string().refine(isCanonicalTimestamp,"must be a canonical ISO-8601 UTC instant");var ProfileRefSchema=z.string().min(1).max(64).regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/u);var ProviderKindSchema=z.string().min(1).max(64).regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/u);var ModelIdSchema=z.string().min(1).max(160).refine(value=>value.trim()===value&&!/[\u0000-\u001f\u007f]/u.test(value),"model id is invalid");var CapabilitySchema=z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);var PiModelJsonSchema=z.custom(value=>{if(value===null||typeof value!=="object"||Array.isArray(value))return false;try{canonicalizeJson(value);return true}catch{return false}},"piModel must be a canonical JSON object");var ModelConfigFields={agentId:OpaqueIdSchema,providerKind:ProviderKindSchema,modelId:ModelIdSchema,piModel:PiModelJsonSchema,capabilities:z.array(CapabilitySchema).max(8).refine(values=>new Set(values).size===values.length,"capabilities must be unique")};var ProviderProvisioningConfigV1Schema=z.discriminatedUnion("operation",[z.strictObject({operation:z.literal("configure"),...ModelConfigFields}),z.strictObject({operation:z.literal("update_model"),...ModelConfigFields}),z.strictObject({operation:z.literal("replace_secret"),agentId:OpaqueIdSchema,providerKind:ProviderKindSchema}),z.strictObject({operation:z.literal("delete"),agentId:OpaqueIdSchema})]);var ProviderProvisioningExpectedProfileSchema=z.union([z.null(),z.strictObject({profileRef:ProfileRefSchema,profileRevision:z.string().regex(/^(?:0|[1-9][0-9]{0,19})$/u),profileHash:z.string().regex(CONTENT_HASH_PATTERN)})]);var ProviderProvisioningIdentityRevisionSchema=z.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/u);var ProviderProvisioningHeaderV1Schema=z.strictObject({tenantId:OpaqueIdSchema,deviceId:OpaqueIdSchema,agentId:OpaqueIdSchema,requestId:OpaqueIdSchema,operation:z.enum(PROVIDER_PROVISIONING_OPERATIONS),operationGeneration:z.number().int().positive().max(Number.MAX_SAFE_INTEGER),expectedProfile:ProviderProvisioningExpectedProfileSchema,expectedEnrollmentRevision:ProviderProvisioningIdentityRevisionSchema,expectedPlacementRevision:ProviderProvisioningIdentityRevisionSchema,configDigest:z.string().regex(CONTENT_HASH_PATTERN),issuedAt:CanonicalTimestampSchema,expiresAt:CanonicalTimestampSchema});var MAX_CIPHERTEXT_BYTES=PROVIDER_SECRET_MAX_PADDED_BYTES+HPKE_AEAD_TAG_BYTES;var SealedProviderSecretV1Schema=z.strictObject({suite:z.literal(PROVIDER_SECRET_HPKE_SUITE),keyId:SealingKeyIdSchema,enc:base64UrlField({exact:HPKE_P256_PUBLIC_KEY_BYTES}),ciphertext:base64UrlField({max:MAX_CIPHERTEXT_BYTES})});function isSecretOperation(operation){return PROVIDER_PROVISIONING_SECRET_OPERATIONS.includes(operation)}z.strictObject({version:z.literal(PROVIDER_PROVISIONING_REQUEST_VERSION),header:ProviderProvisioningHeaderV1Schema,config:ProviderProvisioningConfigV1Schema,sealed:SealedProviderSecretV1Schema.optional()}).superRefine((request,ctx)=>{if(request.header.operation!==request.config.operation){ctx.addIssue({code:"custom",path:["config","operation"],message:"config operation must equal header operation"});}if(request.header.agentId!==request.config.agentId){ctx.addIssue({code:"custom",path:["config","agentId"],message:"config agentId must equal header agentId"});}if(isSecretOperation(request.header.operation)!==(request.sealed!==void 0)){ctx.addIssue({code:"custom",path:["sealed"],message:"a sealed secret is required exactly for configure and replace_secret"});}});var P256PublicJwkSchema=SealingPublicJwkSchema;var ECDSA_P256_SIGNATURE_BYTES=64;function invalidInput(message){return new ByokCoreError("sealed_secret_invalid",message)}function utf82(value){return new TextEncoder().encode(value)}function copy2(bytes){return new Uint8Array(bytes)}function concat2(...parts){const out=new Uint8Array(parts.reduce((total,part)=>total+part.byteLength,0));let offset=0;for(const part of parts){out.set(part,offset);offset+=part.byteLength;}return out}function hex(bytes){let out="";for(const byte of bytes)out+=byte.toString(16).padStart(2,"0");return out}function randomBytes(length){const source=globalThis.crypto;if(source?.getRandomValues===void 0){throw new ByokCoreError("webcrypto_unavailable","WebCrypto getRandomValues is unavailable in this runtime")}return source.getRandomValues(new Uint8Array(length))}async function sha256(bytes){return new Uint8Array(await webCryptoSubtle().digest("SHA-256",copy2(bytes)))}async function hmacSha2562(key,data){const subtle=webCryptoSubtle();const hmacKey=await subtle.importKey("raw",copy2(key),{name:"HMAC",hash:"SHA-256"},false,["sign"]);return new Uint8Array(await subtle.sign("HMAC",hmacKey,copy2(data)))}async function hkdfSha256(ikm,salt,info,length){const subtle=webCryptoSubtle();const key=await subtle.importKey("raw",copy2(ikm),{name:"HKDF"},false,["deriveBits"]);const bits=await subtle.deriveBits({name:"HKDF",hash:"SHA-256",salt:copy2(salt),info:utf82(info)},key,length*8);return new Uint8Array(bits)}function base64UrlBytes(bytes){return z.string().refine(value=>{try{return decodeBase64Url(value,"field").byteLength===bytes}catch{return false}},`must be canonical unpadded base64url of ${bytes} bytes`)}var ECDSA_P256={name:"ECDSA",namedCurve:"P-256"};var ECDSA_SHA256={name:"ECDSA",hash:"SHA-256"};function ecdsaSubtle(){return webCryptoSubtle()}async function p256PublicKeyBytes(publicJwk){const raw=sealingPublicKeyBytes(publicJwk);await importEcdsaPublicKey(publicJwk);return raw}async function importEcdsaPublicKey(publicJwk){const jwk=P256PublicJwkSchema.parse(publicJwk);try{return await ecdsaSubtle().importKey("jwk",{kty:jwk.kty,crv:jwk.crv,x:jwk.x,y:jwk.y},ECDSA_P256,true,["verify"])}catch(cause){throw new ByokCoreError("sealed_secret_invalid","P-256 public key is not a valid curve point",{cause})}}async function ecdsaSign(publicJwk,privateKey,data){let signature;try{signature=encodeBase64Url(new Uint8Array(await ecdsaSubtle().sign(ECDSA_SHA256,privateKey,copy2(data))));}catch(cause){throw new ByokCoreError("sealed_secret_invalid","Signing key is not an ECDSA P-256 private key",{cause})}if(!await ecdsaVerify(publicJwk,signature,data)){throw invalidInput("Signing private key does not pair with the signing public key")}return signature}async function ecdsaVerify(publicJwk,signature,data){let signatureBytes;try{signatureBytes=decodeBase64Url(signature,"sig");}catch{return false}if(signatureBytes.byteLength!==ECDSA_P256_SIGNATURE_BYTES)return false;const key=await importEcdsaPublicKey(publicJwk);return ecdsaSubtle().verify(ECDSA_SHA256,key,signatureBytes,copy2(data))}var MEMBER_ID_DOMAIN="byok-member-id-v1\n";var MEMBER_ID_PATTERN=/^[A-Za-z0-9_-]{43}$/u;var MEMBER_CHAIN_VERSION=1;var MEMBER_CHAIN_DOMAIN_PREFIX="byok-member-chain-v1\n";var MEMBER_CHAIN_OPS=["add","remove","rotate","scope-policy","shred"];var CanonicalJsonObjectSchema=z.unknown().transform((value,ctx)=>{try{if(value!==null&&typeof value==="object"&&!Array.isArray(value)){return JSON.parse(canonicalizeJson(value))}}catch{}ctx.addIssue({code:"custom",message:"must be a canonical JSON object"});return z.NEVER});var unsignedEntryShape={v:z.literal(MEMBER_CHAIN_VERSION),tenantId:z.string().min(1),seq:z.number().int().positive().max(Number.MAX_SAFE_INTEGER),prev:z.union([z.null(),z.string().regex(CONTENT_HASH_PATTERN)]),op:z.enum(MEMBER_CHAIN_OPS),subject:CanonicalJsonObjectSchema,signer:z.string().regex(MEMBER_ID_PATTERN)};function firstEntryHasNoPrev(entry,ctx){if(entry.seq===1!==(entry.prev===null)){ctx.addIssue({code:"custom",path:["prev"],message:"the first entry, and only the first, has no prev"});}}var UnsignedMemberChainEntryV1Schema=z.strictObject(unsignedEntryShape).superRefine(firstEntryHasNoPrev);var MemberChainEntryV1Schema=z.strictObject({...unsignedEntryShape,sig:base64UrlBytes(64)}).superRefine(firstEntryHasNoPrev);async function deriveMemberId(signingPublicJwk){const raw=await p256PublicKeyBytes(signingPublicJwk);return encodeBase64Url(await sha256(concat2(utf82(MEMBER_ID_DOMAIN),raw)))}function parseUnsignedEntry(entry){const parsed=safeParseTotal(UnsignedMemberChainEntryV1Schema,entry);if(!parsed.success)throw invalidInput("Member chain entry is invalid");return parsed.data}function signingInput(entry){return concat2(utf82(MEMBER_CHAIN_DOMAIN_PREFIX),canonicalizeJsonBytes({v:entry.v,tenantId:entry.tenantId,seq:entry.seq,prev:entry.prev,op:entry.op,subject:entry.subject,signer:entry.signer}))}function memberChainSigningInput(entry){return signingInput(parseUnsignedEntry(entry))}async function memberChainEntryHash(entry){return `sha256:${hex(await sha256(memberChainSigningInput(entry)))}`}async function signMemberChainEntry(entry,signer){const unsigned=parseUnsignedEntry(entry);if(await deriveMemberId(signer.publicJwk)!==unsigned.signer){throw invalidInput("Member chain entry signer does not match the signing key")}return {...unsigned,sig:await ecdsaSign(signer.publicJwk,signer.privateKey,signingInput(unsigned))}}async function verifyMemberChainEntry(entry,signerPublicJwk){const parsed=safeParseTotal(MemberChainEntryV1Schema,entry);if(!parsed.success||await deriveMemberId(signerPublicJwk)!==parsed.data.signer){return {ok:false,reason:"member_chain_entry_invalid"}}const input=signingInput(parsed.data);if(!await ecdsaVerify(signerPublicJwk,parsed.data.sig,input)){return {ok:false,reason:"member_chain_signature_invalid"}}return {ok:true,entry:parsed.data,hash:`sha256:${hex(await sha256(input))}`}}var MEMBER_KEY_BUNDLE_VERSION=1;var MEMBER_KEM_DOMAIN_PREFIX="byok-member-kem-v1\n";var KemEpochSchema=z.number().int().positive().max(Number.MAX_SAFE_INTEGER);var KemSigSchema=base64UrlBytes(64);var MemberKeyBundleV1Schema=z.strictObject({v:z.literal(MEMBER_KEY_BUNDLE_VERSION),memberId:z.string().regex(MEMBER_ID_PATTERN),signingPublicJwk:P256PublicJwkSchema,kemPublicJwk:P256PublicJwkSchema,kemEpoch:KemEpochSchema,kemSig:KemSigSchema,deviceId:z.string().min(1).optional()});var CreateInputSchema=z.object({signingPublicJwk:P256PublicJwkSchema,signingPrivateKey:z.custom(),kemPublicJwk:P256PublicJwkSchema,kemEpoch:KemEpochSchema,deviceId:z.string().min(1).optional()});var KemSignerSchema=z.object({memberId:z.string().regex(MEMBER_ID_PATTERN),signingPublicJwk:P256PublicJwkSchema});var KemStatementSchema=z.object({kemPublicJwk:P256PublicJwkSchema,kemEpoch:KemEpochSchema,kemSig:KemSigSchema});function memberKemSigningInput(memberId,kemPublicJwk,kemEpoch){return concat2(utf82(MEMBER_KEM_DOMAIN_PREFIX),canonicalizeJsonBytes({memberId,kemPublicKey:{x:kemPublicJwk.x,y:kemPublicJwk.y},kemEpoch}))}async function createMemberKeyBundle(input){const parsed=safeParseTotal(CreateInputSchema,input);if(!parsed.success)throw invalidInput("Member key bundle input is invalid");const{signingPublicJwk,signingPrivateKey,kemPublicJwk,kemEpoch,deviceId}=parsed.data;const memberId=await deriveMemberId(signingPublicJwk);if(!await isValidMemberKemKey(kemPublicJwk,signingPublicJwk))throw invalidInput("Member KEM key is invalid");const kemSig=await ecdsaSign(signingPublicJwk,signingPrivateKey,memberKemSigningInput(memberId,kemPublicJwk,kemEpoch));return {v:MEMBER_KEY_BUNDLE_VERSION,memberId,signingPublicJwk,kemPublicJwk,kemEpoch,kemSig,...deviceId===void 0?{}:{deviceId}}}async function isValidMemberKemKey(kemPublicJwk,signingPublicJwk){if(kemPublicJwk.x===signingPublicJwk.x&&kemPublicJwk.y===signingPublicJwk.y)return false;try{await p256PublicKeyBytes(kemPublicJwk);return true}catch{return false}}async function verifyMemberKem(member,kem){const signer=safeParseTotal(KemSignerSchema,member);const statement=safeParseTotal(KemStatementSchema,kem);if(!signer.success||!statement.success)return false;const{memberId,signingPublicJwk}=signer.data;const{kemPublicJwk,kemEpoch,kemSig}=statement.data;if(!await isValidMemberKemKey(kemPublicJwk,signingPublicJwk))return false;try{return await ecdsaVerify(signingPublicJwk,kemSig,memberKemSigningInput(memberId,kemPublicJwk,kemEpoch))}catch{return false}}async function parseMemberKeyBundle(value){const parsed=safeParseTotal(MemberKeyBundleV1Schema,value);if(!parsed.success)return void 0;const bundle=parsed.data;try{if(await deriveMemberId(bundle.signingPublicJwk)!==bundle.memberId)return void 0}catch{return void 0}if(!await verifyMemberKem(bundle,bundle))return void 0;return bundle}var SCOPE_KEY_BYTES=32;var SCOPE_KEY_ID_PATTERN=/^[A-Za-z0-9_-]{22}$/u;var KEY_ID_MESSAGE="byok-scope-key-id-v1";var CONTENT_KEY_INFO="byok-sdk.sealed.v1.enc";var MAC_KEY_INFO="byok-sdk.sealed.v1.mac";var SEALED_SALT_BYTES=32;var SCOPE_KEY_PACKAGE_VERSION=1;var SCOPE_KEY_HPKE_INFO="byok-sdk.scope-key.v1";var SCOPE_KEY_PACKAGE_DOMAIN_PREFIX="byok-scope-key-package-v1\n";var SCOPE_KEY_PACKAGE_V2=2;var SCOPE_KEY_HPKE_INFO_V2="byok-sdk.scope-key.v2";var SCOPE_KEY_PACKAGE_V2_DOMAIN_PREFIX="byok-scope-key-package-v2\n";var P256_POINT_BYTES=65;var AEAD_TAG_BYTES=16;function assertScopeKey(scopeKey){if(scopeKey.byteLength!==SCOPE_KEY_BYTES)throw invalidInput(`Scope key must be ${SCOPE_KEY_BYTES} bytes`)}function generateScopeKey(){return randomBytes(SCOPE_KEY_BYTES)}async function deriveScopeKeyId(scopeKey){assertScopeKey(scopeKey);const mac=await hmacSha2562(scopeKey,utf82(KEY_ID_MESSAGE));return encodeBase64Url(mac.subarray(0,16))}async function deriveSealedContentKey(scopeKey,salt){assertScopeKey(scopeKey);if(salt.byteLength!==SEALED_SALT_BYTES)throw invalidInput(`Sealed content salt must be ${SEALED_SALT_BYTES} bytes`);return hkdfSha256(scopeKey,salt,CONTENT_KEY_INFO,32)}async function deriveSealedMacKey(scopeKey){assertScopeKey(scopeKey);return hkdfSha256(scopeKey,new Uint8Array(32),MAC_KEY_INFO,32)}var ScopeKeyPackageV1Schema=z.strictObject({v:z.literal(SCOPE_KEY_PACKAGE_VERSION),tenantId:z.string().min(1),scopeId:z.string().min(1),epoch:z.number().int().positive().max(Number.MAX_SAFE_INTEGER),keyId:z.string().regex(SCOPE_KEY_ID_PATTERN),recipient:z.string().regex(MEMBER_ID_PATTERN),sender:z.string().regex(MEMBER_ID_PATTERN),enc:base64UrlBytes(P256_POINT_BYTES),ciphertext:base64UrlBytes(SCOPE_KEY_BYTES+AEAD_TAG_BYTES),sig:base64UrlBytes(64)});var ScopeKeyPackageV2Schema=z.strictObject({...ScopeKeyPackageV1Schema.shape,v:z.literal(SCOPE_KEY_PACKAGE_V2),recipientKemEpoch:KemEpochSchema});var ScopeKeyPackageSchema=z.union([ScopeKeyPackageV1Schema,ScopeKeyPackageV2Schema]);function packageAad({enc:_enc,ciphertext:_ciphertext,sig:_sig,...identity}){return canonicalizeJsonBytes(identity)}function packageSigningInput({sig:_sig,...pkg}){return concat2(utf82(pkg.v===SCOPE_KEY_PACKAGE_V2?SCOPE_KEY_PACKAGE_V2_DOMAIN_PREFIX:SCOPE_KEY_PACKAGE_DOMAIN_PREFIX),canonicalizeJsonBytes(pkg))}var PackageIdentitySchema=ScopeKeyPackageV2Schema.omit({enc:true,ciphertext:true,sig:true});async function sealScopeKeyPackage(input){const parsed=safeParseTotal(PackageIdentitySchema,{v:SCOPE_KEY_PACKAGE_V2,tenantId:input.tenantId,scopeId:input.scopeId,epoch:input.epoch,keyId:await deriveScopeKeyId(input.scopeKey),recipient:input.recipient.memberId,recipientKemEpoch:input.recipient.kemEpoch,sender:await deriveMemberId(input.sender.signingPublicJwk)});if(!parsed.success)throw invalidInput("Scope key package input is invalid");const identity=parsed.data;const{enc,ciphertext}=await hpkeSealBase({recipientPublicKey:await p256PublicKeyBytes(input.recipient.kemPublicJwk),info:utf82(SCOPE_KEY_HPKE_INFO_V2),aad:packageAad(identity),plaintext:input.scopeKey});const unsigned={...identity,enc:encodeBase64Url(enc),ciphertext:encodeBase64Url(ciphertext)};return {...unsigned,sig:await ecdsaSign(input.sender.signingPublicJwk,input.sender.signingPrivateKey,packageSigningInput(unsigned))}}async function openScopeKeyPackage(input){const parsed=safeParseTotal(ScopeKeyPackageSchema,input.package);if(!parsed.success)return {ok:false,reason:"key_package_invalid"};const pkg=parsed.data;if(pkg.recipient!==input.recipient.memberId||pkg.v===SCOPE_KEY_PACKAGE_V2&&pkg.recipientKemEpoch!==input.recipient.kemEpoch||pkg.tenantId!==input.expected.tenantId||pkg.scopeId!==input.expected.scopeId||pkg.epoch!==input.expected.epoch||pkg.sender!==await deriveMemberId(input.senderSigningPublicJwk)){return {ok:false,reason:"key_package_invalid"}}if(!await ecdsaVerify(input.senderSigningPublicJwk,pkg.sig,packageSigningInput(pkg))){return {ok:false,reason:"key_package_signature_invalid"}}let scopeKey;try{scopeKey=await hpkeOpenBase({recipientPrivateKey:input.recipient.kemPrivateKey,recipientPublicKey:await p256PublicKeyBytes(input.recipient.kemPublicJwk),enc:decodeBase64Url(pkg.enc,"enc"),info:utf82(pkg.v===SCOPE_KEY_PACKAGE_V2?SCOPE_KEY_HPKE_INFO_V2:SCOPE_KEY_HPKE_INFO),aad:packageAad(pkg),ciphertext:decodeBase64Url(pkg.ciphertext,"ciphertext")});}catch(error){if(error instanceof ByokCoreError)return {ok:false,reason:"key_package_open_failed"};throw error}if(scopeKey.byteLength!==SCOPE_KEY_BYTES||await deriveScopeKeyId(scopeKey)!==pkg.keyId){scopeKey.fill(0);return {ok:false,reason:"key_package_open_failed"}}return {ok:true,scopeKey,keyId:pkg.keyId}}function canonicalValue(value,seen){if(value===null)return "null";switch(typeof value){case "boolean":return value?"true":"false";case "string":return JSON.stringify(value);case "number":if(!Number.isFinite(value))throw invalidInput("Sealed plaintext holds a number with no JSON form");return String(value);case "object":break;default:throw invalidInput(`Sealed plaintext holds a ${typeof value}, which is not a JSON type`)}if(seen.has(value))throw invalidInput("Sealed plaintext holds a circular reference");seen.add(value);try{if(Array.isArray(value))return `[${Array.from(value,entry=>canonicalValue(entry,seen)).join(",")}]`;const prototype=Object.getPrototypeOf(value);if(prototype!==Object.prototype&&prototype!==null)throw invalidInput("Sealed plaintext holds an object that is not plain");const record=value;const members=Object.keys(record).sort().map(key=>`${JSON.stringify(key)}:${canonicalValue(record[key],seen)}`);return `{${members.join(",")}}`}finally{seen.delete(value);}}function canonicalizeSealedJson(value){return canonicalValue(value,new Set)}var SEALED_CONTENT_VERSION=1;var SEALED_CONTENT_SUITE="byok-sealed-v1.hkdf-sha256.aes-256-gcm";var SEALED_CATEGORIES=["task-input","agent-message","task-output","memory-projection"];var SEALED_DIRECTIONS=["server-to-daemon","daemon-to-server"];var SEALED_NONCE_BYTES=12;var AEAD_TAG_BYTES2=16;var FRAME_HEADER_BYTES=4;var ALIGNED_FRAME_LIMIT=16*1024;var FRAME_ALIGNMENT=256;var MAX_CONTENT_LENGTH=4294967295;var CONTENT_MAC_PREFIX="byok-content-mac-v1\n";var CONTENT_MAC_PATTERN=/^hmac-sha256:[0-9a-f]{64}$/u;var FAMILY_PATTERN=/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/u;var IDENTITY_FIELD_PATTERN=/^[a-z][A-Za-z0-9]*$/u;var ROUTINE_TEMPLATE_FAMILY="routine.template";function floorLog2(value){let exponent=0;for(let remaining=value;remaining>=2;remaining=Math.floor(remaining/2))exponent+=1;return exponent}function sealedPaddedLength(frameLength){if(!Number.isSafeInteger(frameLength)||frameLength<FRAME_HEADER_BYTES){throw invalidInput("Sealed frame length must be a safe integer of at least 4")}if(frameLength<=ALIGNED_FRAME_LIMIT)return Math.ceil(frameLength/FRAME_ALIGNMENT)*FRAME_ALIGNMENT;const exponent=floorLog2(frameLength);const step=2**(exponent-(floorLog2(exponent)+1));return Math.ceil(frameLength/step)*step}function isLegalPaddedLength(paddedLength){return Number.isSafeInteger(paddedLength)&&paddedLength>=FRAME_ALIGNMENT&&sealedPaddedLength(paddedLength)===paddedLength}function encodeSealedFrame(content){if(content.byteLength>MAX_CONTENT_LENGTH)throw invalidInput("Sealed content exceeds the 32-bit frame length");const frame=new Uint8Array(sealedPaddedLength(FRAME_HEADER_BYTES+content.byteLength));new DataView(frame.buffer).setUint32(0,content.byteLength,false);frame.set(content,FRAME_HEADER_BYTES);return frame}function decodeSealedFrame(frame,paddedLength,maxContentLength){if(frame.byteLength!==paddedLength||paddedLength<FRAME_HEADER_BYTES)return void 0;const contentLength=new DataView(frame.buffer,frame.byteOffset,frame.byteLength).getUint32(0,false);if(contentLength>paddedLength-FRAME_HEADER_BYTES)return void 0;if(sealedPaddedLength(FRAME_HEADER_BYTES+contentLength)!==paddedLength)return void 0;if(contentLength>maxContentLength)return void 0;for(let index=FRAME_HEADER_BYTES+contentLength;index<paddedLength;index+=1){if(frame[index]!==0)return void 0}return frame.subarray(FRAME_HEADER_BYTES,FRAME_HEADER_BYTES+contentLength)}var NonEmptyStringSchema=z.string().min(1);var PositiveIntegerSchema=z.number().int().positive().max(Number.MAX_SAFE_INTEGER);var SealedContentHeaderV1Schema=z.strictObject({v:z.literal(SEALED_CONTENT_VERSION),suite:z.literal(SEALED_CONTENT_SUITE),category:z.enum(SEALED_CATEGORIES),scope:z.strictObject({id:NonEmptyStringSchema,epoch:PositiveIntegerSchema,keyId:z.string().regex(SCOPE_KEY_ID_PATTERN)}),chainHead:z.strictObject({seq:PositiveIntegerSchema,hash:z.string().regex(CONTENT_HASH_PATTERN)}),sender:z.string().regex(MEMBER_ID_PATTERN),salt:base64UrlBytes(SEALED_SALT_BYTES),nonce:base64UrlBytes(SEALED_NONCE_BYTES),paddedLength:PositiveIntegerSchema.refine(isLegalPaddedLength,"must be a legal padded frame length"),contentMac:z.string().regex(CONTENT_MAC_PATTERN)});function isCanonicalCategoryList(categories){return categories.every((category,index)=>index===0||SEALED_CATEGORIES.indexOf(categories[index-1])<SEALED_CATEGORIES.indexOf(category))}var SealedBindingSchema=z.strictObject({tenantId:NonEmptyStringSchema,family:z.string().regex(FAMILY_PATTERN),direction:z.enum(SEALED_DIRECTIONS),identity:z.record(z.string().regex(IDENTITY_FIELD_PATTERN),z.union([NonEmptyStringSchema,z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)])).refine(identity=>Object.keys(identity).length>0,"identity must name at least one field"),deviceId:z.union([z.null(),NonEmptyStringSchema]),agentRef:z.union([z.null(),z.strictObject({agentId:NonEmptyStringSchema,profileRevision:NonEmptyStringSchema})]),categories:z.array(z.enum(SEALED_CATEGORIES)).min(1).refine(isCanonicalCategoryList,"categories must be unique and in canonical order"),notAfter:z.union([z.null(),z.string().refine(isCanonicalTimestamp,"must be a canonical UTC instant")])}).superRefine((binding,ctx)=>{const template=binding.family===ROUTINE_TEMPLATE_FAMILY;if(binding.deviceId===null!==template||binding.notAfter===null===template){ctx.addIssue({code:"custom",path:["deviceId"],message:"a routine template binds an expiry and no device; every other family binds a device and no expiry"});}});function parseBinding(binding){const parsed=safeParseTotal(SealedBindingSchema,binding);if(!parsed.success)throw invalidInput("Sealed content binding is invalid");return parsed.data}function assertMaxContentLength(maxContentLength){if(!Number.isSafeInteger(maxContentLength)||maxContentLength<0||maxContentLength>MAX_CONTENT_LENGTH){throw invalidInput("Sealed content limit must be a non-negative 32-bit integer")}}function bindingAad(header,binding){return {v:header.v,suite:header.suite,category:header.category,tenantId:binding.tenantId,scopeId:header.scope.id,epoch:header.scope.epoch,keyId:header.scope.keyId,chainHead:{seq:header.chainHead.seq,hash:header.chainHead.hash},sender:header.sender,family:binding.family,direction:binding.direction,identity:binding.identity,deviceId:binding.deviceId,agentRef:binding.agentRef===null?null:{agentId:binding.agentRef.agentId,profileRevision:binding.agentRef.profileRevision},categories:[...binding.categories],notAfter:binding.notAfter}}function aadBytes(header,binding){return canonicalizeJsonBytes({...bindingAad(header,binding),paddedLength:header.paddedLength,contentMac:header.contentMac})}function isNonEmptyPlainObject(value){if(value===null||typeof value!=="object"||Array.isArray(value))return false;const prototype=Object.getPrototypeOf(value);return (prototype===Object.prototype||prototype===null)&&Object.keys(value).length>0}function canonicalPlaintext(plaintext){try{if(isNonEmptyPlainObject(plaintext))return canonicalizeSealedJson(plaintext)}catch(cause){throw new ByokCoreError("sealed_secret_invalid","Sealed plaintext is not canonical JSON",{cause})}throw invalidInput("Sealed plaintext must be a non-empty plain JSON object")}function parseCanonicalPlaintext(content){let text;let value;try{text=new TextDecoder("utf-8",{fatal:true,ignoreBOM:true}).decode(content);value=JSON.parse(text);if(!isNonEmptyPlainObject(value)||canonicalizeSealedJson(value)!==text)return void 0}catch{return void 0}return {value,text}}async function contentMacOf(scopeKey,category,binding,plaintextText){const macKey=await deriveSealedMacKey(scopeKey);try{const input=`${CONTENT_MAC_PREFIX}${category}
1067
5
  ${binding.family}
1068
6
  ${canonicalizeJson(binding.identity)}
1069
- ${plaintextText}`;
1070
- return `hmac-sha256:${hex(await hmacSha2562(macKey, utf82(input)))}`;
1071
- } finally {
1072
- macKey.fill(0);
1073
- }
1074
- }
1075
- async function aesGcm(mode, key, nonce, aad, data) {
1076
- const subtle = webCryptoSubtle();
1077
- const cryptoKey = await subtle.importKey("raw", copy2(key), { name: "AES-GCM" }, false, [mode]);
1078
- const params = { name: "AES-GCM", iv: copy2(nonce), additionalData: copy2(aad), tagLength: 128 };
1079
- const out = mode === "encrypt" ? await subtle.encrypt(params, cryptoKey, copy2(data)) : await subtle.decrypt(params, cryptoKey, copy2(data));
1080
- return new Uint8Array(out);
1081
- }
1082
- async function sealContent(input) {
1083
- return sealContentWithRandomness(input, { salt: randomBytes(SEALED_SALT_BYTES), nonce: randomBytes(SEALED_NONCE_BYTES) });
1084
- }
1085
- async function sealContentWithRandomness(input, randomness) {
1086
- const binding = parseBinding(input.binding);
1087
- assertMaxContentLength(input.maxContentLength);
1088
- if (!binding.categories.includes(input.category)) {
1089
- throw invalidInput("Sealed content category is not one of the task sealing categories");
1090
- }
1091
- const plaintextText = canonicalPlaintext(input.plaintext);
1092
- const content = utf82(plaintextText);
1093
- if (content.byteLength > input.maxContentLength) throw invalidInput("Sealed content exceeds its plaintext limit");
1094
- const frame = encodeSealedFrame(content);
1095
- const { salt, nonce } = randomness;
1096
- const parsed = safeParseTotal(SealedContentHeaderV1Schema, {
1097
- v: SEALED_CONTENT_VERSION,
1098
- suite: SEALED_CONTENT_SUITE,
1099
- category: input.category,
1100
- scope: { id: input.scope.id, epoch: input.scope.epoch, keyId: await deriveScopeKeyId(input.scopeKey) },
1101
- chainHead: { seq: input.chainHead.seq, hash: input.chainHead.hash },
1102
- sender: input.sender,
1103
- salt: encodeBase64Url(salt),
1104
- nonce: encodeBase64Url(nonce),
1105
- paddedLength: frame.byteLength,
1106
- contentMac: await contentMacOf(input.scopeKey, input.category, binding, plaintextText)
1107
- });
1108
- if (!parsed.success) throw invalidInput("Sealed content header input is invalid");
1109
- const contentKey = await deriveSealedContentKey(input.scopeKey, salt);
1110
- try {
1111
- const ciphertext = await aesGcm("encrypt", contentKey, nonce, aadBytes(parsed.data, binding), frame);
1112
- return { header: parsed.data, ciphertext };
1113
- } finally {
1114
- contentKey.fill(0);
1115
- frame.fill(0);
1116
- content.fill(0);
1117
- }
1118
- }
1119
- var SealedScopePolicySchema = z.strictObject({ scopeId: NonEmptyStringSchema, epoch: PositiveIntegerSchema });
1120
- function failure(reason) {
1121
- return { ok: false, reason };
1122
- }
1123
- async function openSealedContent(input) {
1124
- assertMaxContentLength(input.maxContentLength);
1125
- const parsedBinding = safeParseTotal(SealedBindingSchema, input.binding);
1126
- const parsedSealing = safeParseTotal(SealedScopePolicySchema, input.sealing);
1127
- if (!parsedBinding.success || !parsedSealing.success) return failure("sealing_policy_mismatch");
1128
- const binding = parsedBinding.data;
1129
- const sealing = parsedSealing.data;
1130
- const parsed = safeParseTotal(SealedContentHeaderV1Schema, input.header);
1131
- if (!parsed.success) return failure("sealed_open_failed");
1132
- const header = parsed.data;
1133
- if (!binding.categories.includes(header.category) || header.scope.id !== sealing.scopeId || header.scope.epoch < sealing.epoch) {
1134
- return failure("sealing_policy_mismatch");
1135
- }
1136
- if (await deriveScopeKeyId(input.scopeKey) !== header.scope.keyId) return failure("sealed_key_unavailable");
1137
- if (input.ciphertext.byteLength !== header.paddedLength + AEAD_TAG_BYTES2) return failure("sealed_open_failed");
1138
- const contentKey = await deriveSealedContentKey(input.scopeKey, decodeBase64Url(header.salt, "salt"));
1139
- let frame;
1140
- try {
1141
- frame = await aesGcm("decrypt", contentKey, decodeBase64Url(header.nonce, "nonce"), aadBytes(header, binding), input.ciphertext);
1142
- } catch {
1143
- return failure("sealed_open_failed");
1144
- } finally {
1145
- contentKey.fill(0);
1146
- }
1147
- try {
1148
- const content = decodeSealedFrame(frame, header.paddedLength, input.maxContentLength);
1149
- const plaintext = content === void 0 ? void 0 : parseCanonicalPlaintext(content);
1150
- if (plaintext === void 0) return failure("sealed_open_failed");
1151
- if (await contentMacOf(input.scopeKey, header.category, binding, plaintext.text) !== header.contentMac) {
1152
- return failure("sealed_content_mac_mismatch");
1153
- }
1154
- return { ok: true, plaintext: plaintext.value, header };
1155
- } finally {
1156
- frame.fill(0);
1157
- }
1158
- }
1159
- var MEMBER_KEY_RECORD_VERSION = 1;
1160
- var PrivateJwkSchema = z.strictObject({
1161
- kty: z.literal("EC"),
1162
- crv: z.literal("P-256"),
1163
- x: base64UrlBytes(32),
1164
- y: base64UrlBytes(32),
1165
- d: base64UrlBytes(32)
1166
- });
1167
- var KemEpochSchema2 = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
1168
- var MemberKeyRecordV1Schema = z.strictObject({
1169
- v: z.literal(MEMBER_KEY_RECORD_VERSION),
1170
- tenantId: z.string().min(1),
1171
- deviceId: z.string().min(1).optional(),
1172
- signingJwk: PrivateJwkSchema,
1173
- kemEpoch: KemEpochSchema2,
1174
- kemJwk: PrivateJwkSchema,
1175
- nextKem: z.strictObject({ epoch: KemEpochSchema2, jwk: PrivateJwkSchema }).optional()
1176
- }).refine((record) => record.nextKem === void 0 || record.nextKem.epoch === record.kemEpoch + 1);
1177
- var BindingSchema = z.object({ tenantId: z.string().min(1), deviceId: z.string().min(1).optional() });
1178
- function keySubtle() {
1179
- return webCryptoSubtle();
1180
- }
1181
- var ECDSA_P2562 = { name: "ECDSA", namedCurve: "P-256" };
1182
- var ECDSA_SHA2562 = { name: "ECDSA", hash: "SHA-256" };
1183
- var ECDH_P256 = { name: "ECDH", namedCurve: "P-256" };
1184
- async function generatePrivateJwk(algorithm, usages) {
1185
- const pair = await keySubtle().generateKey(algorithm, true, usages);
1186
- const { kty, crv, x, y, d } = await keySubtle().exportKey("jwk", pair.privateKey);
1187
- return PrivateJwkSchema.parse({ kty, crv, x, y, d });
1188
- }
1189
- async function generateMemberKeyRecord(binding) {
1190
- const parsed = safeParseTotal(BindingSchema, binding);
1191
- if (!parsed.success) throw invalidInput("Member key binding is invalid");
1192
- const { tenantId, deviceId } = parsed.data;
1193
- return canonicalizeJson({
1194
- v: MEMBER_KEY_RECORD_VERSION,
1195
- tenantId,
1196
- ...deviceId === void 0 ? {} : { deviceId },
1197
- signingJwk: await generatePrivateJwk(ECDSA_P2562, ["sign", "verify"]),
1198
- kemEpoch: 1,
1199
- kemJwk: await generatePrivateJwk(ECDH_P256, ["deriveBits"])
1200
- });
1201
- }
1202
- function publicJwkOf(jwk) {
1203
- return P256PublicJwkSchema.parse({ kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y });
1204
- }
1205
- function bytesEqual(left, right) {
1206
- const a = new Uint8Array(left);
1207
- const b = new Uint8Array(right);
1208
- return a.length === b.length && a.every((byte, index) => byte === b[index]);
1209
- }
1210
- async function importSigningKey(jwk) {
1211
- const subtle = keySubtle();
1212
- const publicJwk = publicJwkOf(jwk);
1213
- const privateKey = await subtle.importKey("jwk", { ...jwk, ext: false }, ECDSA_P2562, false, ["sign"]);
1214
- const publicKey = await subtle.importKey("jwk", publicJwk, ECDSA_P2562, true, ["verify"]);
1215
- const probe = randomBytes(32);
1216
- if (!await subtle.verify(ECDSA_SHA2562, publicKey, await subtle.sign(ECDSA_SHA2562, privateKey, probe), probe)) {
1217
- throw new TypeError("signing scalar does not match its public point");
1218
- }
1219
- return { publicJwk, privateKey };
1220
- }
1221
- async function importKemKey(jwk) {
1222
- const subtle = keySubtle();
1223
- const publicJwk = publicJwkOf(jwk);
1224
- const privateKey = await subtle.importKey("jwk", { ...jwk, ext: false }, ECDH_P256, false, ["deriveBits"]);
1225
- const publicKey = await subtle.importKey("jwk", publicJwk, ECDH_P256, true, []);
1226
- const probe = await subtle.generateKey(ECDH_P256, false, ["deriveBits"]);
1227
- const viaMember = await subtle.deriveBits({ name: "ECDH", public: probe.publicKey }, privateKey, 256);
1228
- const viaProbe = await subtle.deriveBits({ name: "ECDH", public: publicKey }, probe.privateKey, 256);
1229
- if (!bytesEqual(viaMember, viaProbe)) throw new TypeError("KEM scalar does not match its public point");
1230
- return { publicJwk, privateKey };
1231
- }
1232
- async function openMemberKeyRecord(text) {
1233
- const record = parseRecord(text);
1234
- if (record === void 0) return void 0;
1235
- try {
1236
- const signing = await importSigningKey(record.signingJwk);
1237
- const kem = { epoch: record.kemEpoch, ...await importKemKey(record.kemJwk) };
1238
- const binding = record.deviceId === void 0 ? { tenantId: record.tenantId } : { tenantId: record.tenantId, deviceId: record.deviceId };
1239
- if (record.nextKem === void 0) return { binding, signing, kem };
1240
- return { binding, signing, kem, nextKem: { epoch: record.nextKem.epoch, ...await importKemKey(record.nextKem.jwk) } };
1241
- } catch {
1242
- return void 0;
1243
- }
1244
- }
1245
- async function beginMemberKemRotation(text) {
1246
- const record = parseRecord(text);
1247
- if (record === void 0) throw invalidInput("Member key record is invalid");
1248
- if (record.nextKem !== void 0) return text;
1249
- return canonicalizeJson({ ...record, nextKem: { epoch: record.kemEpoch + 1, jwk: await generatePrivateJwk(ECDH_P256, ["deriveBits"]) } });
1250
- }
1251
- function finishMemberKemRotation(text, epoch) {
1252
- const record = parseRecord(text);
1253
- if (record === void 0) throw invalidInput("Member key record is invalid");
1254
- if (record.nextKem === void 0) return record.kemEpoch === epoch ? text : void 0;
1255
- if (record.nextKem.epoch !== epoch) return void 0;
1256
- const { nextKem, ...rest } = record;
1257
- return canonicalizeJson({ ...rest, kemEpoch: nextKem.epoch, kemJwk: nextKem.jwk });
1258
- }
1259
- function parseRecord(text) {
1260
- if (typeof text !== "string") return void 0;
1261
- try {
1262
- const parsed = safeParseTotal(MemberKeyRecordV1Schema, JSON.parse(text));
1263
- return parsed.success && canonicalizeJson(parsed.data) === text ? parsed.data : void 0;
1264
- } catch {
1265
- return void 0;
1266
- }
1267
- }
1268
- var MEMBER_FINGERPRINT_DOMAIN = "byok-member-fingerprint-v1\n";
1269
- var FINGERPRINT_GROUPS = 5;
1270
- var GROUP_BYTES = 5;
1271
- var GROUP_MODULUS = 100000n;
1272
- var FingerprintInputSchema = z.object({ tenantId: z.string().min(1), signingPublicJwk: P256PublicJwkSchema });
1273
- async function memberFingerprint(input) {
1274
- return (await fingerprintGroups(input)).groups.join(" ");
1275
- }
1276
- async function memberApprovalCode(tenantId, first, second) {
1277
- const members = await Promise.all([first, second].map((signingPublicJwk) => fingerprintGroups({ tenantId, signingPublicJwk })));
1278
- if (members[0].memberId === members[1].memberId) throw invalidInput("An approval code needs two different members");
1279
- members.sort((a, b) => a.memberId < b.memberId ? -1 : 1);
1280
- return members.flatMap((member) => member.groups).join(" ");
1281
- }
1282
- async function fingerprintGroups(input) {
1283
- const parsed = safeParseTotal(FingerprintInputSchema, input);
1284
- if (!parsed.success) throw invalidInput("Member fingerprint needs a tenant id and a P-256 signing key");
1285
- const { tenantId, signingPublicJwk } = parsed.data;
1286
- const memberId = await deriveMemberId(signingPublicJwk);
1287
- const digest = await sha256(
1288
- concat2(
1289
- utf82(MEMBER_FINGERPRINT_DOMAIN),
1290
- canonicalizeJsonBytes({ v: 1, tenantId, signingKey: { x: signingPublicJwk.x, y: signingPublicJwk.y } })
1291
- )
1292
- );
1293
- const groups = [];
1294
- for (let group = 0; group < FINGERPRINT_GROUPS; group += 1) {
1295
- let value = 0n;
1296
- for (const byte of digest.subarray(group * GROUP_BYTES, (group + 1) * GROUP_BYTES)) value = value << 8n | BigInt(byte);
1297
- groups.push((value % GROUP_MODULUS).toString().padStart(5, "0"));
1298
- }
1299
- return { memberId, groups };
1300
- }
1301
- var PAIRING_VERIFICATION_SECRET_BYTES = 16;
1302
- var PAIRING_VERIFICATION_TEXT_LENGTH = 26;
1303
- var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
1304
- var PV_TEXT_PATTERN = /^[A-Za-z2-7]{26}$/u;
1305
- function generatePairingVerificationSecret() {
1306
- return randomBytes(PAIRING_VERIFICATION_SECRET_BYTES);
1307
- }
1308
- function encodePairingVerificationSecret(pv) {
1309
- if (pv.byteLength !== PAIRING_VERIFICATION_SECRET_BYTES) {
1310
- throw invalidInput(`Pairing verification secret must be ${PAIRING_VERIFICATION_SECRET_BYTES} bytes`);
1311
- }
1312
- let out = "";
1313
- let buffer = 0;
1314
- let bits = 0;
1315
- for (const byte of pv) {
1316
- buffer = buffer << 8 | byte;
1317
- bits += 8;
1318
- while (bits >= 5) {
1319
- bits -= 5;
1320
- out += BASE32_ALPHABET[buffer >>> bits & 31];
1321
- }
1322
- buffer &= (1 << bits) - 1;
1323
- }
1324
- return out + BASE32_ALPHABET[buffer << 5 - bits & 31];
1325
- }
1326
- function decodePairingVerificationSecret(text) {
1327
- if (!PV_TEXT_PATTERN.test(text)) {
1328
- throw invalidInput(`Pairing verification secret must be ${PAIRING_VERIFICATION_TEXT_LENGTH} base32 characters`);
1329
- }
1330
- const upper = text.toUpperCase();
1331
- const out = new Uint8Array(PAIRING_VERIFICATION_SECRET_BYTES);
1332
- let buffer = 0;
1333
- let bits = 0;
1334
- let index = 0;
1335
- for (const char of upper) {
1336
- buffer = buffer << 5 | BASE32_ALPHABET.indexOf(char);
1337
- bits += 5;
1338
- if (bits >= 8) {
1339
- bits -= 8;
1340
- out[index++] = buffer >>> bits & 255;
1341
- }
1342
- buffer &= (1 << bits) - 1;
1343
- }
1344
- if (buffer !== 0) throw invalidInput("Pairing verification secret has nonzero trailing bits");
1345
- return out;
1346
- }
1347
- function formatPairingInput(pairingCode, pv) {
1348
- if (pairingCode.length === 0) throw invalidInput("Pairing code must not be empty");
1349
- return `${pairingCode}.${encodePairingVerificationSecret(pv)}`;
1350
- }
1351
- function parsePairingInput(text) {
1352
- const dot = text.lastIndexOf(".");
1353
- if (dot <= 0) throw invalidInput("Pairing input must be <pairing code>.<verification secret>");
1354
- return { pairingCode: text.slice(0, dot), pv: decodePairingVerificationSecret(text.slice(dot + 1)) };
1355
- }
1356
- var DEVICE_PAIRING_MAC_DOMAIN = "byok-pairing-device-mac-v1\n";
1357
- var CLIENT_PAIRING_MAC_DOMAIN = "byok-pairing-client-mac-v1\n";
1358
- var PairingMacSchema = base64UrlBytes(32);
1359
- var ChainHeadSchema = z.strictObject({
1360
- seq: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
1361
- hash: z.string().regex(CONTENT_HASH_PATTERN)
1362
- });
1363
- var DeviceMacInputSchema = z.object({ tenantId: z.string().min(1), deviceId: z.string().min(1), member: MemberKeyBundleV1Schema });
1364
- var ClientMacInputSchema = z.object({ member: MemberKeyBundleV1Schema, chainHead: ChainHeadSchema });
1365
- function devicePairingMacInput(input) {
1366
- const parsed = safeParseTotal(DeviceMacInputSchema, input);
1367
- if (!parsed.success || parsed.data.member.deviceId !== parsed.data.deviceId) {
1368
- throw invalidInput("Device pairing MAC input must name the device member's own device id");
1369
- }
1370
- const { tenantId, deviceId, member } = parsed.data;
1371
- return concat2(utf82(DEVICE_PAIRING_MAC_DOMAIN), canonicalizeJsonBytes({ tenantId, deviceId, member }));
1372
- }
1373
- function clientPairingMacInput(input) {
1374
- const parsed = safeParseTotal(ClientMacInputSchema, input);
1375
- if (!parsed.success || parsed.data.member.deviceId !== void 0) {
1376
- throw invalidInput("Client pairing MAC input must hold a client member and a chain head");
1377
- }
1378
- const { member, chainHead } = parsed.data;
1379
- return concat2(utf82(CLIENT_PAIRING_MAC_DOMAIN), canonicalizeJsonBytes({ member, chainHead: { seq: chainHead.seq, hash: chainHead.hash } }));
1380
- }
1381
- function checkPv(pv) {
1382
- if (pv.byteLength !== PAIRING_VERIFICATION_SECRET_BYTES) {
1383
- throw invalidInput(`Pairing verification secret must be ${PAIRING_VERIFICATION_SECRET_BYTES} bytes`);
1384
- }
1385
- }
1386
- async function computeDevicePairingMac(pv, input) {
1387
- checkPv(pv);
1388
- return encodeBase64Url(await hmacSha2562(pv, devicePairingMacInput(input)));
1389
- }
1390
- async function computeClientPairingMac(pv, input) {
1391
- checkPv(pv);
1392
- return encodeBase64Url(await hmacSha2562(pv, clientPairingMacInput(input)));
1393
- }
1394
- async function verifyDevicePairingMac(pv, input, mac) {
1395
- checkPv(pv);
1396
- return hmacVerify(pv, () => devicePairingMacInput(input), mac);
1397
- }
1398
- async function verifyClientPairingMac(pv, input, mac) {
1399
- checkPv(pv);
1400
- return hmacVerify(pv, () => clientPairingMacInput(input), mac);
1401
- }
1402
- async function hmacVerify(key, buildData, mac) {
1403
- const parsed = safeParseTotal(PairingMacSchema, mac);
1404
- if (!parsed.success) return false;
1405
- let data;
1406
- try {
1407
- data = buildData();
1408
- } catch {
1409
- return false;
1410
- }
1411
- const subtle = webCryptoSubtle();
1412
- const hmacKey = await subtle.importKey("raw", copy2(key), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
1413
- return subtle.verify("HMAC", hmacKey, decodeBase64Url(parsed.data, "mac"), data);
1414
- }
1415
- var MemberIdSchema = z.string().regex(MEMBER_ID_PATTERN);
1416
- var ScopeIdSchema = z.string().min(1);
1417
- var AddSubjectSchema = z.strictObject({ member: z.unknown() });
1418
- var RemoveSubjectSchema = z.strictObject({ memberId: MemberIdSchema });
1419
- var RotateSubjectSchema = z.strictObject({
1420
- memberId: MemberIdSchema,
1421
- kemPublicJwk: P256PublicJwkSchema,
1422
- kemEpoch: KemEpochSchema,
1423
- kemSig: KemSigSchema
1424
- });
1425
- var ScopePolicySubjectSchema = z.strictObject({
1426
- scopeId: ScopeIdSchema,
1427
- categories: z.array(z.enum(SEALED_CATEGORIES)).min(1),
1428
- members: z.array(MemberIdSchema).min(1)
1429
- });
1430
- var ShredSubjectSchema = z.strictObject({ scopeId: ScopeIdSchema });
1431
- var SignerSchema = z.object({ signer: MemberIdSchema });
1432
- var GenesisSchema = z.object({ op: z.literal("add"), subject: AddSubjectSchema });
1433
- var HeadSchema = z.object({ seq: z.number(), hash: z.string() });
1434
- var EntriesSchema = z.array(z.unknown());
1435
- var AnchorInputSchema = z.object({
1436
- tenantId: z.string(),
1437
- entries: EntriesSchema,
1438
- self: z.unknown(),
1439
- approverSigningPublicJwk: P256PublicJwkSchema,
1440
- authenticatedHead: z.unknown().optional()
1441
- });
1442
- function emptyMemberChainState(tenantId) {
1443
- return { tenantId, head: null, members: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Set(), scopes: /* @__PURE__ */ new Map(), shredded: /* @__PURE__ */ new Set() };
1444
- }
1445
- function memberChainScopeRecipients(state, scopeId) {
1446
- const scope = state.scopes.get(scopeId);
1447
- if (scope === void 0) return [];
1448
- return scope.members.flatMap((memberId) => {
1449
- const member = state.members.get(memberId);
1450
- return member === void 0 ? [] : [member];
1451
- });
1452
- }
1453
- async function applyMemberChainEntries(state, entries) {
1454
- const list = safeParseTotal(EntriesSchema, entries);
1455
- if (!list.success) return { ok: false, reason: "member_chain_entry_invalid", index: 0 };
1456
- const result = await walk(state, list.data, () => false);
1457
- return result.ok ? { ok: true, state: result.state } : result;
1458
- }
1459
- async function genesisSigner(state, raw, signer) {
1460
- if (state.head !== null || state.members.size > 0) return void 0;
1461
- const entry = safeParseTotal(GenesisSchema, raw);
1462
- const member = entry.success ? await parseMemberKeyBundle(entry.data.subject.member) : void 0;
1463
- return member !== void 0 && member.memberId === signer ? member : void 0;
1464
- }
1465
- async function walk(state, entries, mark) {
1466
- const next = {
1467
- tenantId: state.tenantId,
1468
- head: state.head,
1469
- members: new Map(state.members),
1470
- removed: new Set(state.removed),
1471
- scopes: new Map(state.scopes),
1472
- shredded: new Set(state.shredded)
1473
- };
1474
- let marked;
1475
- for (const [index, raw] of entries.entries()) {
1476
- const signer = safeParseTotal(SignerSchema, raw);
1477
- if (!signer.success) return { ok: false, reason: "member_chain_entry_invalid", index };
1478
- const signerBundle = next.members.get(signer.data.signer) ?? await genesisSigner(next, raw, signer.data.signer);
1479
- if (signerBundle === void 0) return { ok: false, reason: "member_chain_signer_invalid", index };
1480
- const verified = await verifyMemberChainEntry(raw, signerBundle.signingPublicJwk);
1481
- if (!verified.ok) return { ok: false, reason: verified.reason, index };
1482
- const entry = verified.entry;
1483
- const expected = next.head === null ? { seq: 1, prev: null } : { seq: next.head.seq + 1, prev: next.head.hash };
1484
- if (entry.tenantId !== next.tenantId || entry.seq !== expected.seq || entry.prev !== expected.prev) {
1485
- return { ok: false, reason: "member_chain_link_invalid", index };
1486
- }
1487
- if (marked === void 0 && mark(entry, signerBundle.signingPublicJwk)) marked = { index, seq: entry.seq, hash: verified.hash };
1488
- const applied = await applySubject(next, entry);
1489
- if (applied !== true) return { ok: false, reason: applied === false ? "member_chain_subject_invalid" : applied, index };
1490
- next.head = { seq: entry.seq, hash: verified.hash };
1491
- }
1492
- return { ok: true, state: next, marked };
1493
- }
1494
- async function applySubject(state, entry) {
1495
- switch (entry.op) {
1496
- case "add": {
1497
- const subject = safeParseTotal(AddSubjectSchema, entry.subject);
1498
- const member = subject.success ? await parseMemberKeyBundle(subject.data.member) : void 0;
1499
- if (member === void 0 || state.members.has(member.memberId) || state.removed.has(member.memberId)) return false;
1500
- state.members.set(member.memberId, member);
1501
- return true;
1502
- }
1503
- case "remove": {
1504
- const subject = safeParseTotal(RemoveSubjectSchema, entry.subject);
1505
- if (!subject.success || !state.members.has(subject.data.memberId) || state.members.size === 1) return false;
1506
- state.members.delete(subject.data.memberId);
1507
- state.removed.add(subject.data.memberId);
1508
- return true;
1509
- }
1510
- case "rotate": {
1511
- const subject = safeParseTotal(RotateSubjectSchema, entry.subject);
1512
- if (!subject.success || subject.data.memberId !== entry.signer) return false;
1513
- const member = state.members.get(subject.data.memberId);
1514
- if (member === void 0 || subject.data.kemEpoch !== member.kemEpoch + 1) return false;
1515
- const { kemPublicJwk } = subject.data;
1516
- if (kemPublicJwk.x === member.kemPublicJwk.x && kemPublicJwk.y === member.kemPublicJwk.y) return false;
1517
- if (!await verifyMemberKem(member, subject.data)) return false;
1518
- state.members.set(member.memberId, {
1519
- ...member,
1520
- kemPublicJwk: subject.data.kemPublicJwk,
1521
- kemEpoch: subject.data.kemEpoch,
1522
- kemSig: subject.data.kemSig
1523
- });
1524
- return true;
1525
- }
1526
- case "scope-policy": {
1527
- const subject = safeParseTotal(ScopePolicySubjectSchema, entry.subject);
1528
- if (!subject.success || state.shredded.has(subject.data.scopeId)) return false;
1529
- const { categories, members } = subject.data;
1530
- if (!strictlyIncreasing(categories.map((category) => SEALED_CATEGORIES.indexOf(category)))) return false;
1531
- if (!strictlyIncreasing(members) || !members.every((memberId) => state.members.has(memberId))) return false;
1532
- if (!(state.scopes.get(subject.data.scopeId)?.members ?? members).includes(entry.signer)) return "member_chain_signer_invalid";
1533
- state.scopes.set(subject.data.scopeId, { categories, members, policySeq: entry.seq });
1534
- return true;
1535
- }
1536
- case "shred": {
1537
- const subject = safeParseTotal(ShredSubjectSchema, entry.subject);
1538
- if (!subject.success) return false;
1539
- const scope = state.scopes.get(subject.data.scopeId);
1540
- if (scope === void 0) return false;
1541
- if (!scope.members.includes(entry.signer)) return "member_chain_signer_invalid";
1542
- state.scopes.delete(subject.data.scopeId);
1543
- state.shredded.add(subject.data.scopeId);
1544
- return true;
1545
- }
1546
- }
1547
- }
1548
- function strictlyIncreasing(values) {
1549
- return values.every((value, index) => index === 0 || values[index - 1] < value);
1550
- }
1551
- async function verifyMemberChainFromAnchor(input) {
1552
- const parsed = safeParseTotal(AnchorInputSchema, input);
1553
- if (!parsed.success) return { ok: false, reason: "member_chain_anchor_missing" };
1554
- const { tenantId, entries, self, approverSigningPublicJwk: approver, authenticatedHead } = parsed.data;
1555
- let approverId;
1556
- let selfText;
1557
- try {
1558
- approverId = await deriveMemberId(approver);
1559
- selfText = canonicalizeJson(self);
1560
- } catch {
1561
- return { ok: false, reason: "member_chain_anchor_missing" };
1562
- }
1563
- const isAnchor = (entry, signerKey) => {
1564
- if (entry.op !== "add" || entry.signer !== approverId || signerKey.x !== approver.x || signerKey.y !== approver.y) return false;
1565
- try {
1566
- return canonicalizeJson(entry.subject.member) === selfText;
1567
- } catch {
1568
- return false;
1569
- }
1570
- };
1571
- const outcome = await walk(emptyMemberChainState(tenantId), entries, isAnchor);
1572
- if (!outcome.ok) return outcome;
1573
- const anchor = outcome.marked;
1574
- if (anchor === void 0) return { ok: false, reason: "member_chain_anchor_missing" };
1575
- if (authenticatedHead !== void 0) {
1576
- const head = safeParseTotal(HeadSchema, authenticatedHead);
1577
- if (!head.success || head.data.seq !== anchor.seq || head.data.hash !== anchor.hash) {
1578
- return { ok: false, reason: "member_chain_head_mismatch", index: anchor.index };
1579
- }
1580
- }
1581
- return { ok: true, state: outcome.state, anchorSeq: anchor.seq };
1582
- }
7
+ ${plaintextText}`;return `hmac-sha256:${hex(await hmacSha2562(macKey,utf82(input)))}`}finally{macKey.fill(0);}}async function aesGcm(mode,key,nonce,aad,data){const subtle=webCryptoSubtle();const cryptoKey=await subtle.importKey("raw",copy2(key),{name:"AES-GCM"},false,[mode]);const params={name:"AES-GCM",iv:copy2(nonce),additionalData:copy2(aad),tagLength:128};const out=mode==="encrypt"?await subtle.encrypt(params,cryptoKey,copy2(data)):await subtle.decrypt(params,cryptoKey,copy2(data));return new Uint8Array(out)}async function sealContent(input){return sealContentWithRandomness(input,{salt:randomBytes(SEALED_SALT_BYTES),nonce:randomBytes(SEALED_NONCE_BYTES)})}async function sealContentWithRandomness(input,randomness){const binding=parseBinding(input.binding);assertMaxContentLength(input.maxContentLength);if(!binding.categories.includes(input.category)){throw invalidInput("Sealed content category is not one of the task sealing categories")}const plaintextText=canonicalPlaintext(input.plaintext);const content=utf82(plaintextText);if(content.byteLength>input.maxContentLength)throw invalidInput("Sealed content exceeds its plaintext limit");const frame=encodeSealedFrame(content);const{salt,nonce}=randomness;const parsed=safeParseTotal(SealedContentHeaderV1Schema,{v:SEALED_CONTENT_VERSION,suite:SEALED_CONTENT_SUITE,category:input.category,scope:{id:input.scope.id,epoch:input.scope.epoch,keyId:await deriveScopeKeyId(input.scopeKey)},chainHead:{seq:input.chainHead.seq,hash:input.chainHead.hash},sender:input.sender,salt:encodeBase64Url(salt),nonce:encodeBase64Url(nonce),paddedLength:frame.byteLength,contentMac:await contentMacOf(input.scopeKey,input.category,binding,plaintextText)});if(!parsed.success)throw invalidInput("Sealed content header input is invalid");const contentKey=await deriveSealedContentKey(input.scopeKey,salt);try{const ciphertext=await aesGcm("encrypt",contentKey,nonce,aadBytes(parsed.data,binding),frame);return {header:parsed.data,ciphertext}}finally{contentKey.fill(0);frame.fill(0);content.fill(0);}}var SealedScopePolicySchema=z.strictObject({scopeId:NonEmptyStringSchema,epoch:PositiveIntegerSchema});function failure(reason){return {ok:false,reason}}async function openSealedContent(input){assertMaxContentLength(input.maxContentLength);const parsedBinding=safeParseTotal(SealedBindingSchema,input.binding);const parsedSealing=safeParseTotal(SealedScopePolicySchema,input.sealing);if(!parsedBinding.success||!parsedSealing.success)return failure("sealing_policy_mismatch");const binding=parsedBinding.data;const sealing=parsedSealing.data;const parsed=safeParseTotal(SealedContentHeaderV1Schema,input.header);if(!parsed.success)return failure("sealed_open_failed");const header=parsed.data;if(!binding.categories.includes(header.category)||header.scope.id!==sealing.scopeId||header.scope.epoch<sealing.epoch){return failure("sealing_policy_mismatch")}if(await deriveScopeKeyId(input.scopeKey)!==header.scope.keyId)return failure("sealed_key_unavailable");if(input.ciphertext.byteLength!==header.paddedLength+AEAD_TAG_BYTES2)return failure("sealed_open_failed");const contentKey=await deriveSealedContentKey(input.scopeKey,decodeBase64Url(header.salt,"salt"));let frame;try{frame=await aesGcm("decrypt",contentKey,decodeBase64Url(header.nonce,"nonce"),aadBytes(header,binding),input.ciphertext);}catch{return failure("sealed_open_failed")}finally{contentKey.fill(0);}try{const content=decodeSealedFrame(frame,header.paddedLength,input.maxContentLength);const plaintext=content===void 0?void 0:parseCanonicalPlaintext(content);if(plaintext===void 0)return failure("sealed_open_failed");if(await contentMacOf(input.scopeKey,header.category,binding,plaintext.text)!==header.contentMac){return failure("sealed_content_mac_mismatch")}return {ok:true,plaintext:plaintext.value,header}}finally{frame.fill(0);}}var MEMBER_KEY_RECORD_VERSION=1;var PrivateJwkSchema=z.strictObject({kty:z.literal("EC"),crv:z.literal("P-256"),x:base64UrlBytes(32),y:base64UrlBytes(32),d:base64UrlBytes(32)});var KemEpochSchema2=z.number().int().positive().max(Number.MAX_SAFE_INTEGER);var MemberKeyRecordV1Schema=z.strictObject({v:z.literal(MEMBER_KEY_RECORD_VERSION),tenantId:z.string().min(1),deviceId:z.string().min(1).optional(),signingJwk:PrivateJwkSchema,kemEpoch:KemEpochSchema2,kemJwk:PrivateJwkSchema,nextKem:z.strictObject({epoch:KemEpochSchema2,jwk:PrivateJwkSchema}).optional()}).refine(record=>record.nextKem===void 0||record.nextKem.epoch===record.kemEpoch+1);var BindingSchema=z.object({tenantId:z.string().min(1),deviceId:z.string().min(1).optional()});function keySubtle(){return webCryptoSubtle()}var ECDSA_P2562={name:"ECDSA",namedCurve:"P-256"};var ECDSA_SHA2562={name:"ECDSA",hash:"SHA-256"};var ECDH_P256={name:"ECDH",namedCurve:"P-256"};async function generatePrivateJwk(algorithm,usages){const pair=await keySubtle().generateKey(algorithm,true,usages);const{kty,crv,x,y,d}=await keySubtle().exportKey("jwk",pair.privateKey);return PrivateJwkSchema.parse({kty,crv,x,y,d})}async function generateMemberKeyRecord(binding){const parsed=safeParseTotal(BindingSchema,binding);if(!parsed.success)throw invalidInput("Member key binding is invalid");const{tenantId,deviceId}=parsed.data;return canonicalizeJson({v:MEMBER_KEY_RECORD_VERSION,tenantId,...deviceId===void 0?{}:{deviceId},signingJwk:await generatePrivateJwk(ECDSA_P2562,["sign","verify"]),kemEpoch:1,kemJwk:await generatePrivateJwk(ECDH_P256,["deriveBits"])})}function publicJwkOf(jwk){return P256PublicJwkSchema.parse({kty:jwk.kty,crv:jwk.crv,x:jwk.x,y:jwk.y})}function bytesEqual(left,right){const a=new Uint8Array(left);const b=new Uint8Array(right);return a.length===b.length&&a.every((byte,index)=>byte===b[index])}async function importSigningKey(jwk){const subtle=keySubtle();const publicJwk=publicJwkOf(jwk);const privateKey=await subtle.importKey("jwk",{...jwk,ext:false},ECDSA_P2562,false,["sign"]);const publicKey=await subtle.importKey("jwk",publicJwk,ECDSA_P2562,true,["verify"]);const probe=randomBytes(32);if(!await subtle.verify(ECDSA_SHA2562,publicKey,await subtle.sign(ECDSA_SHA2562,privateKey,probe),probe)){throw new TypeError("signing scalar does not match its public point")}return {publicJwk,privateKey}}async function importKemKey(jwk){const subtle=keySubtle();const publicJwk=publicJwkOf(jwk);const privateKey=await subtle.importKey("jwk",{...jwk,ext:false},ECDH_P256,false,["deriveBits"]);const publicKey=await subtle.importKey("jwk",publicJwk,ECDH_P256,true,[]);const probe=await subtle.generateKey(ECDH_P256,false,["deriveBits"]);const viaMember=await subtle.deriveBits({name:"ECDH",public:probe.publicKey},privateKey,256);const viaProbe=await subtle.deriveBits({name:"ECDH",public:publicKey},probe.privateKey,256);if(!bytesEqual(viaMember,viaProbe))throw new TypeError("KEM scalar does not match its public point");return {publicJwk,privateKey}}async function openMemberKeyRecord(text){const record=parseRecord(text);if(record===void 0)return void 0;try{const signing=await importSigningKey(record.signingJwk);const kem={epoch:record.kemEpoch,...await importKemKey(record.kemJwk)};const binding=record.deviceId===void 0?{tenantId:record.tenantId}:{tenantId:record.tenantId,deviceId:record.deviceId};if(record.nextKem===void 0)return {binding,signing,kem};return {binding,signing,kem,nextKem:{epoch:record.nextKem.epoch,...await importKemKey(record.nextKem.jwk)}}}catch{return void 0}}async function beginMemberKemRotation(text){const record=parseRecord(text);if(record===void 0)throw invalidInput("Member key record is invalid");if(record.nextKem!==void 0)return text;return canonicalizeJson({...record,nextKem:{epoch:record.kemEpoch+1,jwk:await generatePrivateJwk(ECDH_P256,["deriveBits"])}})}function finishMemberKemRotation(text,epoch){const record=parseRecord(text);if(record===void 0)throw invalidInput("Member key record is invalid");if(record.nextKem===void 0)return record.kemEpoch===epoch?text:void 0;if(record.nextKem.epoch!==epoch)return void 0;const{nextKem,...rest}=record;return canonicalizeJson({...rest,kemEpoch:nextKem.epoch,kemJwk:nextKem.jwk})}function parseRecord(text){if(typeof text!=="string")return void 0;try{const parsed=safeParseTotal(MemberKeyRecordV1Schema,JSON.parse(text));return parsed.success&&canonicalizeJson(parsed.data)===text?parsed.data:void 0}catch{return void 0}}var MEMBER_FINGERPRINT_DOMAIN="byok-member-fingerprint-v1\n";var FINGERPRINT_GROUPS=5;var GROUP_BYTES=5;var GROUP_MODULUS=100000n;var FingerprintInputSchema=z.object({tenantId:z.string().min(1),signingPublicJwk:P256PublicJwkSchema});async function memberFingerprint(input){return (await fingerprintGroups(input)).groups.join(" ")}async function memberApprovalCode(tenantId,first,second){const members=await Promise.all([first,second].map(signingPublicJwk=>fingerprintGroups({tenantId,signingPublicJwk})));if(members[0].memberId===members[1].memberId)throw invalidInput("An approval code needs two different members");members.sort((a,b)=>a.memberId<b.memberId?-1:1);return members.flatMap(member=>member.groups).join(" ")}async function fingerprintGroups(input){const parsed=safeParseTotal(FingerprintInputSchema,input);if(!parsed.success)throw invalidInput("Member fingerprint needs a tenant id and a P-256 signing key");const{tenantId,signingPublicJwk}=parsed.data;const memberId=await deriveMemberId(signingPublicJwk);const digest=await sha256(concat2(utf82(MEMBER_FINGERPRINT_DOMAIN),canonicalizeJsonBytes({v:1,tenantId,signingKey:{x:signingPublicJwk.x,y:signingPublicJwk.y}})));const groups=[];for(let group=0;group<FINGERPRINT_GROUPS;group+=1){let value=0n;for(const byte of digest.subarray(group*GROUP_BYTES,(group+1)*GROUP_BYTES))value=value<<8n|BigInt(byte);groups.push((value%GROUP_MODULUS).toString().padStart(5,"0"));}return {memberId,groups}}var PAIRING_VERIFICATION_SECRET_BYTES=16;var PAIRING_VERIFICATION_TEXT_LENGTH=26;var BASE32_ALPHABET="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";var PV_TEXT_PATTERN=/^[A-Za-z2-7]{26}$/u;function generatePairingVerificationSecret(){return randomBytes(PAIRING_VERIFICATION_SECRET_BYTES)}function encodePairingVerificationSecret(pv){if(pv.byteLength!==PAIRING_VERIFICATION_SECRET_BYTES){throw invalidInput(`Pairing verification secret must be ${PAIRING_VERIFICATION_SECRET_BYTES} bytes`)}let out="";let buffer=0;let bits=0;for(const byte of pv){buffer=buffer<<8|byte;bits+=8;while(bits>=5){bits-=5;out+=BASE32_ALPHABET[buffer>>>bits&31];}buffer&=(1<<bits)-1;}return out+BASE32_ALPHABET[buffer<<5-bits&31]}function decodePairingVerificationSecret(text){if(!PV_TEXT_PATTERN.test(text)){throw invalidInput(`Pairing verification secret must be ${PAIRING_VERIFICATION_TEXT_LENGTH} base32 characters`)}const upper=text.toUpperCase();const out=new Uint8Array(PAIRING_VERIFICATION_SECRET_BYTES);let buffer=0;let bits=0;let index=0;for(const char of upper){buffer=buffer<<5|BASE32_ALPHABET.indexOf(char);bits+=5;if(bits>=8){bits-=8;out[index++]=buffer>>>bits&255;}buffer&=(1<<bits)-1;}if(buffer!==0)throw invalidInput("Pairing verification secret has nonzero trailing bits");return out}function formatPairingInput(pairingCode,pv){if(pairingCode.length===0)throw invalidInput("Pairing code must not be empty");return `${pairingCode}.${encodePairingVerificationSecret(pv)}`}function parsePairingInput(text){const dot=text.lastIndexOf(".");if(dot<=0)throw invalidInput("Pairing input must be <pairing code>.<verification secret>");return {pairingCode:text.slice(0,dot),pv:decodePairingVerificationSecret(text.slice(dot+1))}}var DEVICE_PAIRING_MAC_DOMAIN="byok-pairing-device-mac-v1\n";var CLIENT_PAIRING_MAC_DOMAIN="byok-pairing-client-mac-v1\n";var PairingMacSchema=base64UrlBytes(32);var ChainHeadSchema=z.strictObject({seq:z.number().int().positive().max(Number.MAX_SAFE_INTEGER),hash:z.string().regex(CONTENT_HASH_PATTERN)});var DeviceMacInputSchema=z.object({tenantId:z.string().min(1),deviceId:z.string().min(1),member:MemberKeyBundleV1Schema});var ClientMacInputSchema=z.object({member:MemberKeyBundleV1Schema,chainHead:ChainHeadSchema});function devicePairingMacInput(input){const parsed=safeParseTotal(DeviceMacInputSchema,input);if(!parsed.success||parsed.data.member.deviceId!==parsed.data.deviceId){throw invalidInput("Device pairing MAC input must name the device member's own device id")}const{tenantId,deviceId,member}=parsed.data;return concat2(utf82(DEVICE_PAIRING_MAC_DOMAIN),canonicalizeJsonBytes({tenantId,deviceId,member}))}function clientPairingMacInput(input){const parsed=safeParseTotal(ClientMacInputSchema,input);if(!parsed.success||parsed.data.member.deviceId!==void 0){throw invalidInput("Client pairing MAC input must hold a client member and a chain head")}const{member,chainHead}=parsed.data;return concat2(utf82(CLIENT_PAIRING_MAC_DOMAIN),canonicalizeJsonBytes({member,chainHead:{seq:chainHead.seq,hash:chainHead.hash}}))}function checkPv(pv){if(pv.byteLength!==PAIRING_VERIFICATION_SECRET_BYTES){throw invalidInput(`Pairing verification secret must be ${PAIRING_VERIFICATION_SECRET_BYTES} bytes`)}}async function computeDevicePairingMac(pv,input){checkPv(pv);return encodeBase64Url(await hmacSha2562(pv,devicePairingMacInput(input)))}async function computeClientPairingMac(pv,input){checkPv(pv);return encodeBase64Url(await hmacSha2562(pv,clientPairingMacInput(input)))}async function verifyDevicePairingMac(pv,input,mac){checkPv(pv);return hmacVerify(pv,()=>devicePairingMacInput(input),mac)}async function verifyClientPairingMac(pv,input,mac){checkPv(pv);return hmacVerify(pv,()=>clientPairingMacInput(input),mac)}async function hmacVerify(key,buildData,mac){const parsed=safeParseTotal(PairingMacSchema,mac);if(!parsed.success)return false;let data;try{data=buildData();}catch{return false}const subtle=webCryptoSubtle();const hmacKey=await subtle.importKey("raw",copy2(key),{name:"HMAC",hash:"SHA-256"},false,["verify"]);return subtle.verify("HMAC",hmacKey,decodeBase64Url(parsed.data,"mac"),data)}var MemberIdSchema=z.string().regex(MEMBER_ID_PATTERN);var ScopeIdSchema=z.string().min(1);var AddSubjectSchema=z.strictObject({member:z.unknown()});var RemoveSubjectSchema=z.strictObject({memberId:MemberIdSchema});var RotateSubjectSchema=z.strictObject({memberId:MemberIdSchema,kemPublicJwk:P256PublicJwkSchema,kemEpoch:KemEpochSchema,kemSig:KemSigSchema});var ScopePolicySubjectSchema=z.strictObject({scopeId:ScopeIdSchema,categories:z.array(z.enum(SEALED_CATEGORIES)).min(1),members:z.array(MemberIdSchema).min(1)});var ShredSubjectSchema=z.strictObject({scopeId:ScopeIdSchema});var SignerSchema=z.object({signer:MemberIdSchema});var GenesisSchema=z.object({op:z.literal("add"),subject:AddSubjectSchema});var HeadSchema=z.object({seq:z.number(),hash:z.string()});var EntriesSchema=z.array(z.unknown());var AnchorInputSchema=z.object({tenantId:z.string(),entries:EntriesSchema,self:z.unknown(),approverSigningPublicJwk:P256PublicJwkSchema,authenticatedHead:z.unknown().optional()});function emptyMemberChainState(tenantId){return {tenantId,head:null,members:new Map,removed:new Set,scopes:new Map,shredded:new Set}}function memberChainScopeRecipients(state,scopeId){const scope=state.scopes.get(scopeId);if(scope===void 0)return [];return scope.members.flatMap(memberId=>{const member=state.members.get(memberId);return member===void 0?[]:[member]})}async function applyMemberChainEntries(state,entries){const list=safeParseTotal(EntriesSchema,entries);if(!list.success)return {ok:false,reason:"member_chain_entry_invalid",index:0};const result=await walk(state,list.data,()=>false);return result.ok?{ok:true,state:result.state}:result}async function genesisSigner(state,raw,signer){if(state.head!==null||state.members.size>0)return void 0;const entry=safeParseTotal(GenesisSchema,raw);const member=entry.success?await parseMemberKeyBundle(entry.data.subject.member):void 0;return member!==void 0&&member.memberId===signer?member:void 0}async function walk(state,entries,mark){const next={tenantId:state.tenantId,head:state.head,members:new Map(state.members),removed:new Set(state.removed),scopes:new Map(state.scopes),shredded:new Set(state.shredded)};let marked;for(const[index,raw]of entries.entries()){const signer=safeParseTotal(SignerSchema,raw);if(!signer.success)return {ok:false,reason:"member_chain_entry_invalid",index};const signerBundle=next.members.get(signer.data.signer)??await genesisSigner(next,raw,signer.data.signer);if(signerBundle===void 0)return {ok:false,reason:"member_chain_signer_invalid",index};const verified=await verifyMemberChainEntry(raw,signerBundle.signingPublicJwk);if(!verified.ok)return {ok:false,reason:verified.reason,index};const entry=verified.entry;const expected=next.head===null?{seq:1,prev:null}:{seq:next.head.seq+1,prev:next.head.hash};if(entry.tenantId!==next.tenantId||entry.seq!==expected.seq||entry.prev!==expected.prev){return {ok:false,reason:"member_chain_link_invalid",index}}if(marked===void 0&&mark(entry,signerBundle.signingPublicJwk))marked={index,seq:entry.seq,hash:verified.hash};const applied=await applySubject(next,entry);if(applied!==true)return {ok:false,reason:applied===false?"member_chain_subject_invalid":applied,index};next.head={seq:entry.seq,hash:verified.hash};}return {ok:true,state:next,marked}}async function applySubject(state,entry){switch(entry.op){case "add":{const subject=safeParseTotal(AddSubjectSchema,entry.subject);const member=subject.success?await parseMemberKeyBundle(subject.data.member):void 0;if(member===void 0||state.members.has(member.memberId)||state.removed.has(member.memberId))return false;state.members.set(member.memberId,member);return true}case "remove":{const subject=safeParseTotal(RemoveSubjectSchema,entry.subject);if(!subject.success||!state.members.has(subject.data.memberId)||state.members.size===1)return false;state.members.delete(subject.data.memberId);state.removed.add(subject.data.memberId);return true}case "rotate":{const subject=safeParseTotal(RotateSubjectSchema,entry.subject);if(!subject.success||subject.data.memberId!==entry.signer)return false;const member=state.members.get(subject.data.memberId);if(member===void 0||subject.data.kemEpoch!==member.kemEpoch+1)return false;const{kemPublicJwk}=subject.data;if(kemPublicJwk.x===member.kemPublicJwk.x&&kemPublicJwk.y===member.kemPublicJwk.y)return false;if(!await verifyMemberKem(member,subject.data))return false;state.members.set(member.memberId,{...member,kemPublicJwk:subject.data.kemPublicJwk,kemEpoch:subject.data.kemEpoch,kemSig:subject.data.kemSig});return true}case "scope-policy":{const subject=safeParseTotal(ScopePolicySubjectSchema,entry.subject);if(!subject.success||state.shredded.has(subject.data.scopeId))return false;const{categories,members}=subject.data;if(!strictlyIncreasing(categories.map(category=>SEALED_CATEGORIES.indexOf(category))))return false;if(!strictlyIncreasing(members)||!members.every(memberId=>state.members.has(memberId)))return false;if(!(state.scopes.get(subject.data.scopeId)?.members??members).includes(entry.signer))return "member_chain_signer_invalid";state.scopes.set(subject.data.scopeId,{categories,members,policySeq:entry.seq});return true}case "shred":{const subject=safeParseTotal(ShredSubjectSchema,entry.subject);if(!subject.success)return false;const scope=state.scopes.get(subject.data.scopeId);if(scope===void 0)return false;if(!scope.members.includes(entry.signer))return "member_chain_signer_invalid";state.scopes.delete(subject.data.scopeId);state.shredded.add(subject.data.scopeId);return true}}}function strictlyIncreasing(values){return values.every((value,index)=>index===0||values[index-1]<value)}async function verifyMemberChainFromAnchor(input){const parsed=safeParseTotal(AnchorInputSchema,input);if(!parsed.success)return {ok:false,reason:"member_chain_anchor_missing"};const{tenantId,entries,self,approverSigningPublicJwk:approver,authenticatedHead}=parsed.data;let approverId;let selfText;try{approverId=await deriveMemberId(approver);selfText=canonicalizeJson(self);}catch{return {ok:false,reason:"member_chain_anchor_missing"}}const isAnchor=(entry,signerKey)=>{if(entry.op!=="add"||entry.signer!==approverId||signerKey.x!==approver.x||signerKey.y!==approver.y)return false;try{return canonicalizeJson(entry.subject.member)===selfText}catch{return false}};const outcome=await walk(emptyMemberChainState(tenantId),entries,isAnchor);if(!outcome.ok)return outcome;const anchor=outcome.marked;if(anchor===void 0)return {ok:false,reason:"member_chain_anchor_missing"};if(authenticatedHead!==void 0){const head=safeParseTotal(HeadSchema,authenticatedHead);if(!head.success||head.data.seq!==anchor.seq||head.data.hash!==anchor.hash){return {ok:false,reason:"member_chain_head_mismatch",index:anchor.index}}}return {ok:true,state:outcome.state,anchorSeq:anchor.seq}}
1583
8
 
1584
9
  export { CLIENT_PAIRING_MAC_DOMAIN, DEVICE_PAIRING_MAC_DOMAIN, MEMBER_CHAIN_OPS, MEMBER_FINGERPRINT_DOMAIN, MEMBER_KEY_BUNDLE_VERSION, PAIRING_VERIFICATION_SECRET_BYTES, SEALED_CATEGORIES, SEALED_CONTENT_SUITE, SEALED_CONTENT_VERSION, SEALED_DIRECTIONS, SealedContentHeaderV1Schema, applyMemberChainEntries, beginMemberKemRotation, computeClientPairingMac, computeDevicePairingMac, createMemberKeyBundle, decodePairingVerificationSecret, deriveMemberId, deriveScopeKeyId, emptyMemberChainState, encodePairingVerificationSecret, finishMemberKemRotation, formatPairingInput, generateMemberKeyRecord, generatePairingVerificationSecret, generateScopeKey, memberApprovalCode, memberChainEntryHash, memberChainScopeRecipients, memberFingerprint, openMemberKeyRecord, openScopeKeyPackage, openSealedContent, parseMemberKeyBundle, parsePairingInput, sealContent, sealScopeKeyPackage, signMemberChainEntry, verifyClientPairingMac, verifyDevicePairingMac, verifyMemberChainEntry, verifyMemberChainFromAnchor, verifyMemberKem };
1585
10
  //# sourceMappingURL=index.js.map