/** * Sealed provider provisioning v1 (plan web-sealed-provisioning §3). * * A browser seals a provider API key to one device's long-lived P-256 * sealing key; the cloud relays only ciphertext; the device opens it in * memory. This module is the single byte authority both ends share: * * - the request schema (header + non-secret config + optional sealed secret), * - canonical AAD bytes, config digest, and request digest, * - the sealing-key claim canonical bytes a device signs at registration, * - plaintext padding (2-byte length prefix, zero-padded to 256-byte blocks), * - one-shot seal/open on top of `hpke.ts`. * * Every function is WebCrypto-only and `node:`-free so the Web bundle and the * device use literally the same code. Nothing here reads a clock, a network, * or a store; time windows, replay ledgers, placement and enrollment are the * device's (`@byok-sdk/keys`) decisions. * * Error messages never include plaintext, ciphertext, or key material. */ import { z } from 'zod'; import { type JsonObject } from './attestation'; import { type WebCryptoKey } from './webcrypto'; export type { WebCryptoKey } from './webcrypto'; /** The only HPKE suite v1 admits. There is no negotiation and no second value. */ export declare const PROVIDER_SECRET_HPKE_SUITE = "hpke-base.dhkem-p256-hkdf-sha256.hkdf-sha256.aes-128-gcm"; /** HPKE `info`, versioned; a v2 is a new constant, never a parser branch. */ export declare const PROVIDER_SECRET_HPKE_INFO = "byok-sdk.provider-secret.v1"; export declare const PROVIDER_PROVISIONING_REQUEST_VERSION = 1; export declare const PROVIDER_PROVISIONING_OPERATIONS: readonly ['configure', 'update_model', 'replace_secret', 'delete']; export type ProviderProvisioningOperation = (typeof PROVIDER_PROVISIONING_OPERATIONS)[number]; /** Operations whose request must carry exactly one sealed secret. */ export declare const PROVIDER_PROVISIONING_SECRET_OPERATIONS: readonly ['configure', 'replace_secret']; /** Upper bound for `expiresAt - issuedAt` (plan D11). */ export declare const PROVIDER_PROVISIONING_MAX_TTL_MS: number; /** Plaintext is padded to a multiple of this many bytes so ciphertext length hides key length. */ export declare const PROVIDER_SECRET_PAD_BLOCK_BYTES = 256; /** Largest padded plaintext v1 accepts; the secret itself is at most this minus the 2-byte prefix. */ export declare const PROVIDER_SECRET_MAX_PADDED_BYTES = 4096; export declare const PROVIDER_SECRET_MAX_BYTES: number; /** Length of a sealing key id: base64url(sha256(raw public key)) truncated to 22 characters. */ export declare const SEALING_KEY_ID_LENGTH = 22; export declare function encodeBase64Url(bytes: Uint8Array): string; /** Decode canonical unpadded base64url, or throw — never a repaired approximation. */ export declare function decodeBase64Url(value: string, field: string): Uint8Array; /** * The only public-key shape v1 accepts. Strict: a `d` (private) member, an * `alg`, `use`, `key_ops` or any other extra member is rejected rather than * ignored, so a private JWK can never be published by mistake. */ export declare const SealingPublicJwkSchema: z.ZodObject<{ kty: z.ZodLiteral<"EC">; crv: z.ZodLiteral<"P-256">; x: z.ZodString; y: z.ZodString; }, z.core.$strict>; export type SealingPublicJwk = z.infer; export declare const SealingKeyIdSchema: z.ZodString; /** Uncompressed SEC1 bytes (`0x04 || x || y`) of a sealing public JWK. */ export declare function sealingPublicKeyBytes(publicJwk: SealingPublicJwk): Uint8Array; /** `base64url(sha256(0x04 || x || y))[0..22]` — the device's and the browser's shared key id rule. */ export declare function deriveSealingKeyId(publicJwk: SealingPublicJwk): Promise; /** * The claim a device signs (through its device-proof signer, host glue) when * it registers its sealing key. The proof body is these canonical bytes. */ export declare const SealingKeyClaimV1Schema: z.ZodObject<{ version: z.ZodLiteral<1>; keyId: z.ZodString; epoch: z.ZodNumber; publicJwk: z.ZodObject<{ kty: z.ZodLiteral<"EC">; crv: z.ZodLiteral<"P-256">; x: z.ZodString; y: z.ZodString; }, z.core.$strict>; }, z.core.$strict>; export type SealingKeyClaimV1 = z.infer; /** Canonical UTF-8 bytes of a sealing-key claim. */ export declare function sealingKeyClaimCanonicalBytes(claim: SealingKeyClaimV1): Uint8Array; /** * The non-secret configuration, one exact shape per operation. An operation * carries only the fields it acts on — `delete` has no model, `replace_secret` * declares only the provider kind its key is scoped to — so nothing is * accepted and silently ignored. */ export declare const ProviderProvisioningConfigV1Schema: z.ZodDiscriminatedUnion<[z.ZodObject<{ agentId: z.ZodString; providerKind: z.ZodString; modelId: z.ZodString; piModel: z.ZodCustom; capabilities: z.ZodArray; operation: z.ZodLiteral<"configure">; }, z.core.$strict>, z.ZodObject<{ agentId: z.ZodString; providerKind: z.ZodString; modelId: z.ZodString; piModel: z.ZodCustom; capabilities: z.ZodArray; operation: z.ZodLiteral<"update_model">; }, z.core.$strict>, z.ZodObject<{ operation: z.ZodLiteral<"replace_secret">; agentId: z.ZodString; providerKind: z.ZodString; }, z.core.$strict>, z.ZodObject<{ operation: z.ZodLiteral<"delete">; agentId: z.ZodString; }, z.core.$strict>], "operation">; export type ProviderProvisioningConfigV1 = z.infer; /** The credential-free provider triple the Host believes is current (A3), or `null` for none. */ export declare const ProviderProvisioningExpectedProfileSchema: z.ZodUnion]>; export type ProviderProvisioningExpectedProfile = z.infer; /** * Opaque, Host-issued identity version: the enrollment revision (changes on * re-pair / re-enrollment of the same device id) or the agent placement * revision. The device compares it byte-for-byte with its own snapshot. */ export declare const ProviderProvisioningIdentityRevisionSchema: z.ZodString; export declare const ProviderProvisioningHeaderV1Schema: z.ZodObject<{ tenantId: z.ZodString; deviceId: z.ZodString; agentId: z.ZodString; requestId: z.ZodString; operation: z.ZodEnum<{ configure: "configure"; delete: "delete"; replace_secret: "replace_secret"; update_model: "update_model"; }>; operationGeneration: z.ZodNumber; expectedProfile: z.ZodUnion]>; expectedEnrollmentRevision: z.ZodString; expectedPlacementRevision: z.ZodString; configDigest: z.ZodString; issuedAt: z.ZodString & z.ZodType>; expiresAt: z.ZodString & z.ZodType>; }, z.core.$strict>; export type ProviderProvisioningHeaderV1 = z.infer; export declare const SealedProviderSecretV1Schema: z.ZodObject<{ suite: z.ZodLiteral<"hpke-base.dhkem-p256-hkdf-sha256.hkdf-sha256.aes-128-gcm">; keyId: z.ZodString; enc: z.ZodString; ciphertext: z.ZodString; }, z.core.$strict>; export type SealedProviderSecretV1 = z.infer; export declare const ProviderProvisioningRequestV1Schema: z.ZodObject<{ version: z.ZodLiteral<1>; header: z.ZodObject<{ tenantId: z.ZodString; deviceId: z.ZodString; agentId: z.ZodString; requestId: z.ZodString; operation: z.ZodEnum<{ configure: "configure"; delete: "delete"; replace_secret: "replace_secret"; update_model: "update_model"; }>; operationGeneration: z.ZodNumber; expectedProfile: z.ZodUnion]>; expectedEnrollmentRevision: z.ZodString; expectedPlacementRevision: z.ZodString; configDigest: z.ZodString; issuedAt: z.ZodString & z.ZodType>; expiresAt: z.ZodString & z.ZodType>; }, z.core.$strict>; config: z.ZodDiscriminatedUnion<[z.ZodObject<{ agentId: z.ZodString; providerKind: z.ZodString; modelId: z.ZodString; piModel: z.ZodCustom; capabilities: z.ZodArray; operation: z.ZodLiteral<"configure">; }, z.core.$strict>, z.ZodObject<{ agentId: z.ZodString; providerKind: z.ZodString; modelId: z.ZodString; piModel: z.ZodCustom; capabilities: z.ZodArray; operation: z.ZodLiteral<"update_model">; }, z.core.$strict>, z.ZodObject<{ operation: z.ZodLiteral<"replace_secret">; agentId: z.ZodString; providerKind: z.ZodString; }, z.core.$strict>, z.ZodObject<{ operation: z.ZodLiteral<"delete">; agentId: z.ZodString; }, z.core.$strict>], "operation">; sealed: z.ZodOptional; keyId: z.ZodString; enc: z.ZodString; ciphertext: z.ZodString; }, z.core.$strict>>; }, z.core.$strict>; export type ProviderProvisioningRequestV1 = z.infer; /** Parse a request fail-closed. Issue text names paths and rules only, never values. */ export declare function parseProviderProvisioningRequest(input: unknown): ProviderProvisioningRequestV1; /** `sha256:` over the canonical JSON of the non-secret config. */ export declare function providerProvisioningConfigDigest(config: ProviderProvisioningConfigV1): Promise; /** * Canonical AAD bytes. Every header field plus the sealing key id is bound, * so tampering with any of tenant, device, key, request, agent, operation, * generation, expected triple, expected enrollment and placement revisions, * config digest, or time window fails the AEAD. */ export declare function providerSecretAadBytes(header: ProviderProvisioningHeaderV1, keyId: string): Uint8Array; /** * `sha256:` over the canonical JSON of the complete request. A device * compares it for an already-seen `requestId`: equal returns the stored * result, different is a conflict. */ export declare function providerProvisioningRequestDigest(request: ProviderProvisioningRequestV1): Promise; /** Fail closed unless `header.configDigest` is the digest of `config`. */ export declare function assertProviderProvisioningConfigDigest(request: ProviderProvisioningRequestV1): Promise; /** `u16be(len) || utf8(secret) || zeros`, total a multiple of 256 bytes. */ export declare function padProviderSecret(secret: string): Uint8Array; /** Strict inverse of {@link padProviderSecret}: block size, bounds, zero padding, and fatal UTF-8 are all checked. */ export declare function unpadProviderSecret(padded: Uint8Array): string; export interface SealProviderProvisioningRequestInput { /** The header without its digest; the digest is computed here from `config`. */ readonly header: Omit; readonly config: ProviderProvisioningConfigV1; /** Required exactly for secret-bearing operations. */ readonly recipient?: { readonly keyId: string; readonly publicJwk: SealingPublicJwk; }; /** Required exactly for secret-bearing operations. */ readonly secret?: string; } /** * Build one immutable provisioning request. Secret-bearing operations are * sealed once, to a freshly generated ephemeral key; the key id is re-derived * from the public JWK so a claim whose id does not match its key is refused * before anything is encrypted. */ export declare function sealProviderProvisioningRequest(input: SealProviderProvisioningRequestInput): Promise; export interface ProviderSecretRecipient { readonly keyId: string; readonly publicJwk: SealingPublicJwk; /** Non-extractable ECDH private key; never leaves the device. */ readonly privateKey: WebCryptoKey; } /** * Open the sealed secret of a request, in memory. Checks, in order: request * schema, suite (schema literal), recipient key id, config digest, ciphertext * length against the padding grammar, HPKE open under the request's own AAD, * then strict unpadding. Time windows, replay, and placement are not checked * here — they are device state. */ export declare function openProviderProvisioningSecret(input: { readonly request: ProviderProvisioningRequestV1; readonly recipient: ProviderSecretRecipient; }): Promise;