@byok-sdk/core 0.22.0 → 0.24.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.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Sealed provider provisioning v1 (plan web-sealed-provisioning §3).
3
+ *
4
+ * A browser seals a provider API key to one device's long-lived P-256
5
+ * sealing key; the cloud relays only ciphertext; the device opens it in
6
+ * memory. This module is the single byte authority both ends share:
7
+ *
8
+ * - the request schema (header + non-secret config + optional sealed secret),
9
+ * - canonical AAD bytes, config digest, and request digest,
10
+ * - the sealing-key claim canonical bytes a device signs at registration,
11
+ * - plaintext padding (2-byte length prefix, zero-padded to 256-byte blocks),
12
+ * - one-shot seal/open on top of `hpke.ts`.
13
+ *
14
+ * Every function is WebCrypto-only and `node:`-free so the Web bundle and the
15
+ * device use literally the same code. Nothing here reads a clock, a network,
16
+ * or a store; time windows, replay ledgers, placement and enrollment are the
17
+ * device's (`@byok-sdk/keys`) decisions.
18
+ *
19
+ * Error messages never include plaintext, ciphertext, or key material.
20
+ */
21
+ import { z } from 'zod';
22
+ import { type JsonObject } from './attestation';
23
+ import { type WebCryptoKey } from './webcrypto';
24
+ export type { WebCryptoKey } from './webcrypto';
25
+ /** The only HPKE suite v1 admits. There is no negotiation and no second value. */
26
+ export declare const PROVIDER_SECRET_HPKE_SUITE = "hpke-base.dhkem-p256-hkdf-sha256.hkdf-sha256.aes-128-gcm";
27
+ /** HPKE `info`, versioned; a v2 is a new constant, never a parser branch. */
28
+ export declare const PROVIDER_SECRET_HPKE_INFO = "byok-sdk.provider-secret.v1";
29
+ export declare const PROVIDER_PROVISIONING_REQUEST_VERSION = 1;
30
+ export declare const PROVIDER_PROVISIONING_OPERATIONS: readonly ['configure', 'update_model', 'replace_secret', 'delete'];
31
+ export type ProviderProvisioningOperation = (typeof PROVIDER_PROVISIONING_OPERATIONS)[number];
32
+ /** Operations whose request must carry exactly one sealed secret. */
33
+ export declare const PROVIDER_PROVISIONING_SECRET_OPERATIONS: readonly ['configure', 'replace_secret'];
34
+ /** Upper bound for `expiresAt - issuedAt` (plan D11). */
35
+ export declare const PROVIDER_PROVISIONING_MAX_TTL_MS: number;
36
+ /** Plaintext is padded to a multiple of this many bytes so ciphertext length hides key length. */
37
+ export declare const PROVIDER_SECRET_PAD_BLOCK_BYTES = 256;
38
+ /** Largest padded plaintext v1 accepts; the secret itself is at most this minus the 2-byte prefix. */
39
+ export declare const PROVIDER_SECRET_MAX_PADDED_BYTES = 4096;
40
+ export declare const PROVIDER_SECRET_MAX_BYTES: number;
41
+ /** Length of a sealing key id: base64url(sha256(raw public key)) truncated to 22 characters. */
42
+ export declare const SEALING_KEY_ID_LENGTH = 22;
43
+ export declare function encodeBase64Url(bytes: Uint8Array): string;
44
+ /** Decode canonical unpadded base64url, or throw — never a repaired approximation. */
45
+ export declare function decodeBase64Url(value: string, field: string): Uint8Array<ArrayBuffer>;
46
+ /**
47
+ * The only public-key shape v1 accepts. Strict: a `d` (private) member, an
48
+ * `alg`, `use`, `key_ops` or any other extra member is rejected rather than
49
+ * ignored, so a private JWK can never be published by mistake.
50
+ */
51
+ export declare const SealingPublicJwkSchema: z.ZodObject<{
52
+ kty: z.ZodLiteral<"EC">;
53
+ crv: z.ZodLiteral<"P-256">;
54
+ x: z.ZodString;
55
+ y: z.ZodString;
56
+ }, z.core.$strict>;
57
+ export type SealingPublicJwk = z.infer<typeof SealingPublicJwkSchema>;
58
+ export declare const SealingKeyIdSchema: z.ZodString;
59
+ /** Uncompressed SEC1 bytes (`0x04 || x || y`) of a sealing public JWK. */
60
+ export declare function sealingPublicKeyBytes(publicJwk: SealingPublicJwk): Uint8Array<ArrayBuffer>;
61
+ /** `base64url(sha256(0x04 || x || y))[0..22]` — the device's and the browser's shared key id rule. */
62
+ export declare function deriveSealingKeyId(publicJwk: SealingPublicJwk): Promise<string>;
63
+ /**
64
+ * The claim a device signs (through its device-proof signer, host glue) when
65
+ * it registers its sealing key. The proof body is these canonical bytes.
66
+ */
67
+ export declare const SealingKeyClaimV1Schema: z.ZodObject<{
68
+ version: z.ZodLiteral<1>;
69
+ keyId: z.ZodString;
70
+ epoch: z.ZodNumber;
71
+ publicJwk: z.ZodObject<{
72
+ kty: z.ZodLiteral<"EC">;
73
+ crv: z.ZodLiteral<"P-256">;
74
+ x: z.ZodString;
75
+ y: z.ZodString;
76
+ }, z.core.$strict>;
77
+ }, z.core.$strict>;
78
+ export type SealingKeyClaimV1 = z.infer<typeof SealingKeyClaimV1Schema>;
79
+ /** Canonical UTF-8 bytes of a sealing-key claim. */
80
+ export declare function sealingKeyClaimCanonicalBytes(claim: SealingKeyClaimV1): Uint8Array;
81
+ /**
82
+ * The non-secret configuration, one exact shape per operation. An operation
83
+ * carries only the fields it acts on — `delete` has no model, `replace_secret`
84
+ * declares only the provider kind its key is scoped to — so nothing is
85
+ * accepted and silently ignored.
86
+ */
87
+ export declare const ProviderProvisioningConfigV1Schema: z.ZodDiscriminatedUnion<[z.ZodObject<{
88
+ agentId: z.ZodString;
89
+ providerKind: z.ZodString;
90
+ modelId: z.ZodString;
91
+ piModel: z.ZodCustom<JsonObject, JsonObject>;
92
+ capabilities: z.ZodArray<z.ZodString>;
93
+ operation: z.ZodLiteral<"configure">;
94
+ }, z.core.$strict>, z.ZodObject<{
95
+ agentId: z.ZodString;
96
+ providerKind: z.ZodString;
97
+ modelId: z.ZodString;
98
+ piModel: z.ZodCustom<JsonObject, JsonObject>;
99
+ capabilities: z.ZodArray<z.ZodString>;
100
+ operation: z.ZodLiteral<"update_model">;
101
+ }, z.core.$strict>, z.ZodObject<{
102
+ operation: z.ZodLiteral<"replace_secret">;
103
+ agentId: z.ZodString;
104
+ providerKind: z.ZodString;
105
+ }, z.core.$strict>, z.ZodObject<{
106
+ operation: z.ZodLiteral<"delete">;
107
+ agentId: z.ZodString;
108
+ }, z.core.$strict>], "operation">;
109
+ export type ProviderProvisioningConfigV1 = z.infer<typeof ProviderProvisioningConfigV1Schema>;
110
+ /** The credential-free provider triple the Host believes is current (A3), or `null` for none. */
111
+ export declare const ProviderProvisioningExpectedProfileSchema: z.ZodUnion<readonly [z.ZodNull, z.ZodObject<{
112
+ profileRef: z.ZodString;
113
+ profileRevision: z.ZodString;
114
+ profileHash: z.ZodString;
115
+ }, z.core.$strict>]>;
116
+ export type ProviderProvisioningExpectedProfile = z.infer<typeof ProviderProvisioningExpectedProfileSchema>;
117
+ /**
118
+ * Opaque, Host-issued identity version: the enrollment revision (changes on
119
+ * re-pair / re-enrollment of the same device id) or the agent placement
120
+ * revision. The device compares it byte-for-byte with its own snapshot.
121
+ */
122
+ export declare const ProviderProvisioningIdentityRevisionSchema: z.ZodString;
123
+ export declare const ProviderProvisioningHeaderV1Schema: z.ZodObject<{
124
+ tenantId: z.ZodString;
125
+ deviceId: z.ZodString;
126
+ agentId: z.ZodString;
127
+ requestId: z.ZodString;
128
+ operation: z.ZodEnum<{
129
+ configure: "configure";
130
+ delete: "delete";
131
+ replace_secret: "replace_secret";
132
+ update_model: "update_model";
133
+ }>;
134
+ operationGeneration: z.ZodNumber;
135
+ expectedProfile: z.ZodUnion<readonly [z.ZodNull, z.ZodObject<{
136
+ profileRef: z.ZodString;
137
+ profileRevision: z.ZodString;
138
+ profileHash: z.ZodString;
139
+ }, z.core.$strict>]>;
140
+ expectedEnrollmentRevision: z.ZodString;
141
+ expectedPlacementRevision: z.ZodString;
142
+ configDigest: z.ZodString;
143
+ issuedAt: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
144
+ expiresAt: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
145
+ }, z.core.$strict>;
146
+ export type ProviderProvisioningHeaderV1 = z.infer<typeof ProviderProvisioningHeaderV1Schema>;
147
+ export declare const SealedProviderSecretV1Schema: z.ZodObject<{
148
+ suite: z.ZodLiteral<"hpke-base.dhkem-p256-hkdf-sha256.hkdf-sha256.aes-128-gcm">;
149
+ keyId: z.ZodString;
150
+ enc: z.ZodString;
151
+ ciphertext: z.ZodString;
152
+ }, z.core.$strict>;
153
+ export type SealedProviderSecretV1 = z.infer<typeof SealedProviderSecretV1Schema>;
154
+ export declare const ProviderProvisioningRequestV1Schema: z.ZodObject<{
155
+ version: z.ZodLiteral<1>;
156
+ header: z.ZodObject<{
157
+ tenantId: z.ZodString;
158
+ deviceId: z.ZodString;
159
+ agentId: z.ZodString;
160
+ requestId: z.ZodString;
161
+ operation: z.ZodEnum<{
162
+ configure: "configure";
163
+ delete: "delete";
164
+ replace_secret: "replace_secret";
165
+ update_model: "update_model";
166
+ }>;
167
+ operationGeneration: z.ZodNumber;
168
+ expectedProfile: z.ZodUnion<readonly [z.ZodNull, z.ZodObject<{
169
+ profileRef: z.ZodString;
170
+ profileRevision: z.ZodString;
171
+ profileHash: z.ZodString;
172
+ }, z.core.$strict>]>;
173
+ expectedEnrollmentRevision: z.ZodString;
174
+ expectedPlacementRevision: z.ZodString;
175
+ configDigest: z.ZodString;
176
+ issuedAt: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
177
+ expiresAt: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
178
+ }, z.core.$strict>;
179
+ config: z.ZodDiscriminatedUnion<[z.ZodObject<{
180
+ agentId: z.ZodString;
181
+ providerKind: z.ZodString;
182
+ modelId: z.ZodString;
183
+ piModel: z.ZodCustom<JsonObject, JsonObject>;
184
+ capabilities: z.ZodArray<z.ZodString>;
185
+ operation: z.ZodLiteral<"configure">;
186
+ }, z.core.$strict>, z.ZodObject<{
187
+ agentId: z.ZodString;
188
+ providerKind: z.ZodString;
189
+ modelId: z.ZodString;
190
+ piModel: z.ZodCustom<JsonObject, JsonObject>;
191
+ capabilities: z.ZodArray<z.ZodString>;
192
+ operation: z.ZodLiteral<"update_model">;
193
+ }, z.core.$strict>, z.ZodObject<{
194
+ operation: z.ZodLiteral<"replace_secret">;
195
+ agentId: z.ZodString;
196
+ providerKind: z.ZodString;
197
+ }, z.core.$strict>, z.ZodObject<{
198
+ operation: z.ZodLiteral<"delete">;
199
+ agentId: z.ZodString;
200
+ }, z.core.$strict>], "operation">;
201
+ sealed: z.ZodOptional<z.ZodObject<{
202
+ suite: z.ZodLiteral<"hpke-base.dhkem-p256-hkdf-sha256.hkdf-sha256.aes-128-gcm">;
203
+ keyId: z.ZodString;
204
+ enc: z.ZodString;
205
+ ciphertext: z.ZodString;
206
+ }, z.core.$strict>>;
207
+ }, z.core.$strict>;
208
+ export type ProviderProvisioningRequestV1 = z.infer<typeof ProviderProvisioningRequestV1Schema>;
209
+ /** Parse a request fail-closed. Issue text names paths and rules only, never values. */
210
+ export declare function parseProviderProvisioningRequest(input: unknown): ProviderProvisioningRequestV1;
211
+ /** `sha256:<hex>` over the canonical JSON of the non-secret config. */
212
+ export declare function providerProvisioningConfigDigest(config: ProviderProvisioningConfigV1): Promise<string>;
213
+ /**
214
+ * Canonical AAD bytes. Every header field plus the sealing key id is bound,
215
+ * so tampering with any of tenant, device, key, request, agent, operation,
216
+ * generation, expected triple, expected enrollment and placement revisions,
217
+ * config digest, or time window fails the AEAD.
218
+ */
219
+ export declare function providerSecretAadBytes(header: ProviderProvisioningHeaderV1, keyId: string): Uint8Array;
220
+ /**
221
+ * `sha256:<hex>` over the canonical JSON of the complete request. A device
222
+ * compares it for an already-seen `requestId`: equal returns the stored
223
+ * result, different is a conflict.
224
+ */
225
+ export declare function providerProvisioningRequestDigest(request: ProviderProvisioningRequestV1): Promise<string>;
226
+ /** Fail closed unless `header.configDigest` is the digest of `config`. */
227
+ export declare function assertProviderProvisioningConfigDigest(request: ProviderProvisioningRequestV1): Promise<void>;
228
+ /** `u16be(len) || utf8(secret) || zeros`, total a multiple of 256 bytes. */
229
+ export declare function padProviderSecret(secret: string): Uint8Array<ArrayBuffer>;
230
+ /** Strict inverse of {@link padProviderSecret}: block size, bounds, zero padding, and fatal UTF-8 are all checked. */
231
+ export declare function unpadProviderSecret(padded: Uint8Array): string;
232
+ export interface SealProviderProvisioningRequestInput {
233
+ /** The header without its digest; the digest is computed here from `config`. */
234
+ readonly header: Omit<ProviderProvisioningHeaderV1, 'configDigest'>;
235
+ readonly config: ProviderProvisioningConfigV1;
236
+ /** Required exactly for secret-bearing operations. */
237
+ readonly recipient?: {
238
+ readonly keyId: string;
239
+ readonly publicJwk: SealingPublicJwk;
240
+ };
241
+ /** Required exactly for secret-bearing operations. */
242
+ readonly secret?: string;
243
+ }
244
+ /**
245
+ * Build one immutable provisioning request. Secret-bearing operations are
246
+ * sealed once, to a freshly generated ephemeral key; the key id is re-derived
247
+ * from the public JWK so a claim whose id does not match its key is refused
248
+ * before anything is encrypted.
249
+ */
250
+ export declare function sealProviderProvisioningRequest(input: SealProviderProvisioningRequestInput): Promise<ProviderProvisioningRequestV1>;
251
+ export interface ProviderSecretRecipient {
252
+ readonly keyId: string;
253
+ readonly publicJwk: SealingPublicJwk;
254
+ /** Non-extractable ECDH private key; never leaves the device. */
255
+ readonly privateKey: WebCryptoKey;
256
+ }
257
+ /**
258
+ * Open the sealed secret of a request, in memory. Checks, in order: request
259
+ * schema, suite (schema literal), recipient key id, config digest, ciphertext
260
+ * length against the padding grammar, HPKE open under the request's own AAD,
261
+ * then strict unpadding. Time windows, replay, and placement are not checked
262
+ * here — they are device state.
263
+ */
264
+ export declare function openProviderProvisioningSecret(input: {
265
+ readonly request: ProviderProvisioningRequestV1;
266
+ readonly recipient: ProviderSecretRecipient;
267
+ }): Promise<string>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A WebCrypto `CryptoKey`, declared structurally so core's published types
3
+ * need neither the DOM lib nor Node's types. Every runtime's native
4
+ * `CryptoKey` satisfies it.
5
+ */
6
+ export interface WebCryptoKey {
7
+ readonly type: string;
8
+ readonly extractable: boolean;
9
+ readonly algorithm: object;
10
+ readonly usages: readonly string[];
11
+ }
12
+ /** The subset of `SubtleCrypto` this package calls, typed loosely on purpose (see {@link WebCryptoKey}). */
13
+ export interface WebCryptoSubtle {
14
+ importKey(format: 'raw' | 'jwk', keyData: object, algorithm: object, extractable: boolean, usages: string[]): Promise<WebCryptoKey>;
15
+ exportKey(format: 'raw', key: WebCryptoKey): Promise<ArrayBuffer>;
16
+ generateKey(algorithm: object, extractable: boolean, usages: string[]): Promise<{
17
+ publicKey: WebCryptoKey;
18
+ privateKey: WebCryptoKey;
19
+ }>;
20
+ deriveBits(algorithm: object, baseKey: WebCryptoKey, length: number): Promise<ArrayBuffer>;
21
+ sign(algorithm: string, key: WebCryptoKey, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer>;
22
+ encrypt(algorithm: object, key: WebCryptoKey, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer>;
23
+ decrypt(algorithm: object, key: WebCryptoKey, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer>;
24
+ digest(algorithm: string, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer>;
25
+ }
26
+ /** The WebCrypto subtle interface, or a closed-set failure when the runtime lacks it. */
27
+ export declare function webCryptoSubtle(): WebCryptoSubtle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byok-sdk/core",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "BYOK SDK platform contracts: branded tenant identity, tenant-first store ports, device proof canonicalization, and the composition conformance reference",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "homepage": "https://github.com/Ancienttwo/byok-sdk#readme",
16
16
  "engines": {
17
- "node": ">=22.22.0"
17
+ "node": ">=24.15.0"
18
18
  },
19
19
  "sideEffects": false,
20
20
  "main": "./dist/index.js",
@@ -45,5 +45,8 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "zod": "^4.4.3"
48
+ },
49
+ "devDependencies": {
50
+ "@hpke/core": "1.9.0"
48
51
  }
49
52
  }