@fgv/ts-extras 5.1.0-40 → 5.1.0-41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/packlets/crypto-utils/hpkeProvider.js +34 -12
  2. package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -1
  3. package/dist/packlets/crypto-utils/index.browser.js +1 -1
  4. package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
  5. package/dist/packlets/crypto-utils/index.js +1 -1
  6. package/dist/packlets/crypto-utils/index.js.map +1 -1
  7. package/dist/packlets/crypto-utils/keystore/converters.js +3 -4
  8. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -1
  9. package/dist/packlets/crypto-utils/keystore/keyStore.js +155 -16
  10. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  11. package/dist/packlets/crypto-utils/keystore/model.js +8 -3
  12. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
  13. package/dist/packlets/crypto-utils/model.js.map +1 -1
  14. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +66 -0
  15. package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
  16. package/dist/packlets/crypto-utils/spkiHelpers.js +31 -0
  17. package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -1
  18. package/dist/ts-extras.d.ts +289 -7
  19. package/lib/packlets/crypto-utils/hpkeProvider.d.ts +11 -3
  20. package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -1
  21. package/lib/packlets/crypto-utils/hpkeProvider.js +34 -12
  22. package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -1
  23. package/lib/packlets/crypto-utils/index.browser.d.ts +1 -1
  24. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
  25. package/lib/packlets/crypto-utils/index.browser.js +2 -1
  26. package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
  27. package/lib/packlets/crypto-utils/index.d.ts +1 -1
  28. package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
  29. package/lib/packlets/crypto-utils/index.js +2 -1
  30. package/lib/packlets/crypto-utils/index.js.map +1 -1
  31. package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -1
  32. package/lib/packlets/crypto-utils/keystore/converters.js +2 -3
  33. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -1
  34. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +68 -3
  35. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
  36. package/lib/packlets/crypto-utils/keystore/keyStore.js +154 -15
  37. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  38. package/lib/packlets/crypto-utils/keystore/model.d.ts +75 -2
  39. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
  40. package/lib/packlets/crypto-utils/keystore/model.js +9 -4
  41. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
  42. package/lib/packlets/crypto-utils/model.d.ts +89 -0
  43. package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
  44. package/lib/packlets/crypto-utils/model.js.map +1 -1
  45. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +25 -1
  46. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -1
  47. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +66 -0
  48. package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
  49. package/lib/packlets/crypto-utils/spkiHelpers.d.ts +15 -0
  50. package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -1
  51. package/lib/packlets/crypto-utils/spkiHelpers.js +32 -0
  52. package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -1
  53. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../src/packlets/crypto-utils/keystore/model.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAYZ,2EAA2E;AAC3E,2EAA2E;AAC3E,OAAO,EAAE,oBAAoB,EAAoB,MAAM,UAAU,CAAC;AAYlE;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAmB,aAAa,CAAC;AAE7D;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAW,MAAM,CAAC;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,EAAE,CAAC;AAc1C;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAA+C;IACzF,gBAAgB;IAChB,SAAS;CACV,CAAC;AAWF;;;GAGG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAgD;IAC3F,oBAAoB;CACrB,CAAC;AAQF;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsC;IACvE,GAAG,gCAAgC;IACnC,GAAG,+BAA+B;CACnC,CAAC;AAsYF;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAW,MAAM,CAAC;AA+GxD,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,OAAO,GAAG,CAAC,MAAM,KAAK,eAAe,CAAC;AACxC,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport {\n EncryptionAlgorithm,\n ICryptoProvider,\n IArgon2idParams,\n IKeyDerivationParams,\n IPbkdf2KeyDerivationParams,\n KeyPairAlgorithm\n} from '../model';\nimport { IPrivateKeyStorage } from './privateKeyStorage';\n\n// Re-export so consumers can continue to access the algorithm enum via the\n// CryptoUtils.KeyStore namespace alongside the rest of the keystore types.\nexport { allKeyPairAlgorithms, KeyPairAlgorithm } from '../model';\n\n// ============================================================================\n// Key Store Format Types\n// ============================================================================\n\n/**\n * Format version for key store files.\n * @public\n */\nexport type KeyStoreFormat = 'keystore-v1';\n\n/**\n * Current format version constant.\n * @public\n */\nexport const KEYSTORE_FORMAT: KeyStoreFormat = 'keystore-v1';\n\n/**\n * Default PBKDF2 iterations for key store encryption.\n * Higher than regular files since this protects the master key vault.\n * @public\n */\nexport const DEFAULT_KEYSTORE_ITERATIONS: number = 600000;\n\n/**\n * Minimum salt length for key derivation.\n * @public\n */\nexport const MIN_SALT_LENGTH: number = 16;\n\n// ============================================================================\n// Key Store Vault Contents (Decrypted State)\n// ============================================================================\n\n/**\n * Discriminator for symmetric secret types stored in the vault.\n * - `'encryption-key'`: A 32-byte AES-256 encryption key.\n * - `'api-key'`: An arbitrary-length API key string (UTF-8 encoded).\n * @public\n */\nexport type KeyStoreSymmetricSecretType = 'encryption-key' | 'api-key';\n\n/**\n * All valid symmetric secret types.\n * @public\n */\nexport const allKeyStoreSymmetricSecretTypes: ReadonlyArray<KeyStoreSymmetricSecretType> = [\n 'encryption-key',\n 'api-key'\n];\n\n/**\n * Discriminator for asymmetric secret types stored in the vault.\n * - `'asymmetric-keypair'`: A public/private key pair. The public key is held in\n * the vault as a JWK; the private key lives in the supplied\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage} provider.\n * @public\n */\nexport type KeyStoreAsymmetricSecretType = 'asymmetric-keypair';\n\n/**\n * All valid asymmetric secret types.\n * @public\n */\nexport const allKeyStoreAsymmetricSecretTypes: ReadonlyArray<KeyStoreAsymmetricSecretType> = [\n 'asymmetric-keypair'\n];\n\n/**\n * Discriminator for any secret type stored in the vault.\n * @public\n */\nexport type KeyStoreSecretType = KeyStoreSymmetricSecretType | KeyStoreAsymmetricSecretType;\n\n/**\n * All valid key store secret types.\n * @public\n */\nexport const allKeyStoreSecretTypes: ReadonlyArray<KeyStoreSecretType> = [\n ...allKeyStoreAsymmetricSecretTypes,\n ...allKeyStoreSymmetricSecretTypes\n];\n\n/**\n * A symmetric secret entry stored in the vault (in-memory representation).\n * Holds the raw key material directly — for `'encryption-key'` it is a 32-byte\n * AES-256 key; for `'api-key'` it is the UTF-8 encoded API key string.\n * @public\n */\nexport interface IKeyStoreSymmetricEntry {\n /**\n * Unique name for this secret (used as lookup key).\n */\n readonly name: string;\n\n /**\n * Symmetric secret type discriminator.\n */\n readonly type: KeyStoreSymmetricSecretType;\n\n /**\n * The secret data.\n * - For `'encryption-key'`: 32-byte AES-256 key.\n * - For `'api-key'`: UTF-8 encoded API key string (arbitrary length).\n */\n readonly key: Uint8Array;\n\n /**\n * Optional description for this secret.\n */\n readonly description?: string;\n\n /**\n * When this secret was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * An asymmetric keypair entry stored in the vault (in-memory representation).\n * Holds only the public key (as a JWK) and a stable handle (`id`) the\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage} provider uses to fetch the private key.\n * @public\n */\nexport interface IKeyStoreAsymmetricEntry {\n /**\n * Unique name for this entry (used as vault lookup key, renameable).\n */\n readonly name: string;\n\n /**\n * Asymmetric secret type discriminator.\n */\n readonly type: KeyStoreAsymmetricSecretType;\n\n /**\n * Immutable handle used by {@link CryptoUtils.KeyStore.IPrivateKeyStorage} to address the\n * private key. Independent of `name`; survives renames.\n */\n readonly id: string;\n\n /**\n * Algorithm used to generate this keypair.\n */\n readonly algorithm: KeyPairAlgorithm;\n\n /**\n * The public key as a JSON Web Key.\n */\n readonly publicKeyJwk: JsonWebKey;\n\n /**\n * Optional description for this entry.\n */\n readonly description?: string;\n\n /**\n * When this entry was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * Any vault entry, discriminated by `type`.\n * @public\n */\nexport type IKeyStoreEntry = IKeyStoreSymmetricEntry | IKeyStoreAsymmetricEntry;\n\n/**\n * Backwards-compatible alias for {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntry}.\n * @deprecated Use {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntry} for symmetric\n * entries or {@link CryptoUtils.KeyStore.IKeyStoreEntry} for the discriminated union.\n * @public\n */\nexport type IKeyStoreSecretEntry = IKeyStoreSymmetricEntry;\n\n/**\n * JSON-serializable representation of a symmetric secret entry.\n *\n * @remarks\n * Describes the *normalized* shape after parsing. `type` is required here\n * because the converter (see\n * {@link CryptoUtils.KeyStore.Converters.keystoreSymmetricEntryJson | keystoreSymmetricEntryJson})\n * injects the default `'encryption-key'` when reading vaults written before\n * asymmetric-keypair support added the discriminator. Raw on-wire bytes from\n * a legacy vault may therefore omit `type`; downstream code only ever sees\n * the post-conversion shape declared here.\n *\n * @public\n */\nexport interface IKeyStoreSymmetricEntryJson {\n /**\n * Unique name for this secret.\n */\n readonly name: string;\n\n /**\n * Symmetric secret type discriminator.\n *\n * Required on this normalized model type. Vaults written prior to the\n * asymmetric-keypair support may omit this field on the wire; the\n * converter injects `'encryption-key'` when missing for backwards\n * compatibility, so by the time a value of this type is observed the\n * discriminator is always present.\n */\n readonly type: KeyStoreSymmetricSecretType;\n\n /**\n * Base64-encoded secret data.\n */\n readonly key: string;\n\n /**\n * Optional description.\n */\n readonly description?: string;\n\n /**\n * When this secret was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * JSON-serializable representation of an asymmetric keypair entry.\n * The private key is not present here — it lives in the\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage} provider, addressed by `id`.\n * @public\n */\nexport interface IKeyStoreAsymmetricEntryJson {\n /**\n * Unique name for this entry.\n */\n readonly name: string;\n\n /**\n * Asymmetric secret type discriminator.\n */\n readonly type: KeyStoreAsymmetricSecretType;\n\n /**\n * Immutable handle used by {@link CryptoUtils.KeyStore.IPrivateKeyStorage} to address the\n * private key.\n */\n readonly id: string;\n\n /**\n * Algorithm used to generate this keypair.\n */\n readonly algorithm: KeyPairAlgorithm;\n\n /**\n * The public key as a JSON Web Key.\n */\n readonly publicKeyJwk: JsonWebKey;\n\n /**\n * Optional description.\n */\n readonly description?: string;\n\n /**\n * When this entry was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * Any JSON vault entry, discriminated by `type`.\n * @public\n */\nexport type IKeyStoreEntryJson = IKeyStoreSymmetricEntryJson | IKeyStoreAsymmetricEntryJson;\n\n/**\n * Backwards-compatible alias for {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson}.\n * @deprecated Use {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson} for\n * symmetric entries or {@link CryptoUtils.KeyStore.IKeyStoreEntryJson} for the\n * discriminated union.\n * @public\n */\nexport type IKeyStoreSecretEntryJson = IKeyStoreSymmetricEntryJson;\n\n/**\n * The decrypted vault contents - a versioned map of entries.\n * @public\n */\nexport interface IKeyStoreVaultContents {\n /**\n * Format version for vault contents.\n */\n readonly version: KeyStoreFormat;\n\n /**\n * Map of entry name to entry (symmetric or asymmetric).\n */\n readonly secrets: Record<string, IKeyStoreEntryJson>;\n}\n\n// ============================================================================\n// Key Store File Format (Encrypted State)\n// ============================================================================\n\n/**\n * The encrypted key store file format.\n * @public\n */\nexport interface IKeyStoreFile {\n /**\n * Format identifier.\n */\n readonly format: KeyStoreFormat;\n\n /**\n * Algorithm used for encryption.\n */\n readonly algorithm: EncryptionAlgorithm;\n\n /**\n * Base64-encoded initialization vector.\n */\n readonly iv: string;\n\n /**\n * Base64-encoded authentication tag.\n */\n readonly authTag: string;\n\n /**\n * Base64-encoded encrypted vault contents.\n */\n readonly encryptedData: string;\n\n /**\n * Key derivation parameters for the vault master key (always PBKDF2).\n */\n readonly keyDerivation: IPbkdf2KeyDerivationParams;\n}\n\n// ============================================================================\n// Key Store State and Configuration\n// ============================================================================\n\n/**\n * Key store lock state.\n * @public\n */\nexport type KeyStoreLockState = 'locked' | 'unlocked';\n\n/**\n * Parameters for creating a new key store.\n * @public\n */\nexport interface IKeyStoreCreateParams {\n /**\n * Crypto provider to use.\n */\n readonly cryptoProvider: ICryptoProvider;\n\n /**\n * PBKDF2 iterations (defaults to DEFAULT_KEYSTORE_ITERATIONS).\n */\n readonly iterations?: number;\n\n /**\n * Optional private-key storage backend. Required to use `addKeyPair` /\n * `getKeyPair`; absent backends still permit opening, listing, and reading\n * public-key metadata for asymmetric entries.\n */\n readonly privateKeyStorage?: IPrivateKeyStorage;\n}\n\n/**\n * Parameters for opening an existing key store.\n * @public\n */\nexport interface IKeyStoreOpenParams {\n /**\n * Crypto provider to use.\n */\n readonly cryptoProvider: ICryptoProvider;\n\n /**\n * The encrypted key store file content.\n */\n readonly keystoreFile: IKeyStoreFile;\n\n /**\n * Optional private-key storage backend. Required to use `addKeyPair` /\n * `getKeyPair`; absent backends still permit opening, listing, and reading\n * public-key metadata for asymmetric entries.\n */\n readonly privateKeyStorage?: IPrivateKeyStorage;\n}\n\n/**\n * Result of adding a secret to the key store.\n * @public\n */\nexport interface IAddSecretResult {\n /**\n * The secret entry that was added.\n */\n readonly entry: IKeyStoreSymmetricEntry;\n\n /**\n * Whether this replaced an existing secret.\n */\n readonly replaced: boolean;\n\n /**\n * Best-effort warning from displaced-resource cleanup. Set when this call\n * replaced an asymmetric-keypair entry but the corresponding\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage}.delete failed; the new\n * entry is still committed and the orphaned blob is left for consumer-side\n * GC to reconcile.\n */\n readonly warning?: string;\n}\n\n/**\n * Options for adding a secret.\n * @public\n */\nexport interface IAddSecretOptions {\n /**\n * Optional description for the secret.\n */\n readonly description?: string;\n}\n\n/**\n * Options for importing a secret.\n * @public\n */\nexport interface IImportSecretOptions extends IAddSecretOptions {\n /**\n * Whether to replace an existing secret with the same name.\n */\n readonly replace?: boolean;\n}\n\n/**\n * Options for importing raw key material via {@link KeyStore.importSecret}.\n * Extends {@link IImportSecretOptions} with a type classification.\n * @public\n */\nexport interface IImportKeyOptions extends IImportSecretOptions {\n /**\n * Symmetric secret type classification for the imported key material.\n * @defaultValue 'encryption-key'\n */\n readonly type?: KeyStoreSymmetricSecretType;\n}\n\n/**\n * Options for adding a secret derived from a password.\n * @public\n */\nexport interface IAddSecretFromPasswordOptions extends IAddSecretOptions {\n /**\n * Whether to replace an existing secret with the same name.\n */\n readonly replace?: boolean;\n\n /**\n * PBKDF2 iterations for key derivation.\n * @defaultValue DEFAULT_SECRET_ITERATIONS (350000)\n */\n readonly iterations?: number;\n}\n\n/**\n * Default PBKDF2 iterations for secret-level key derivation.\n * Lower than keystore encryption since these are used more frequently.\n * @public\n */\nexport const DEFAULT_SECRET_ITERATIONS: number = 350000;\n\n/**\n * Result of adding a password-derived secret.\n * Extends {@link IAddSecretResult} with key derivation parameters\n * needed to store alongside encrypted files.\n * @public\n */\nexport interface IAddSecretFromPasswordResult extends IAddSecretResult {\n /**\n * Key derivation parameters used to derive the secret key.\n * Store these in encrypted file metadata so the password alone\n * can re-derive the same key for decryption.\n */\n readonly keyDerivation: IKeyDerivationParams;\n}\n\n/**\n * Options for adding an Argon2id password-derived secret.\n * @public\n */\nexport interface IAddSecretFromPasswordArgon2idOptions {\n /**\n * Argon2id parameters. Defaults to {@link CryptoUtils.ARGON2ID_OWASP_MIN}.\n */\n readonly params?: IArgon2idParams;\n /**\n * Optional description for the secret.\n */\n readonly description?: string;\n /**\n * Whether to replace an existing secret with the same name.\n */\n readonly replace?: boolean;\n}\n\n/**\n * Options for adding an asymmetric keypair to the key store.\n * @public\n */\nexport interface IAddKeyPairOptions {\n /**\n * Algorithm to use for the new keypair.\n */\n readonly algorithm: KeyPairAlgorithm;\n\n /**\n * Optional description for the entry.\n */\n readonly description?: string;\n\n /**\n * Whether to replace an existing entry with the same name.\n * Replacement mints a fresh storage `id` and best-effort deletes the\n * displaced storage blob; see the keystore design doc for details.\n */\n readonly replace?: boolean;\n\n /**\n * Overrides the storage-backend-derived extractability for this key only.\n * Omit to keep the default (`!privateKeyStorage.supportsNonExtractable`).\n * A value the backend cannot honor fails loudly rather than silently\n * downgrading.\n */\n readonly extractable?: boolean;\n}\n\n/**\n * Result of adding an asymmetric keypair to the key store.\n * @public\n */\nexport interface IAddKeyPairResult {\n /**\n * The asymmetric entry that was added.\n */\n readonly entry: IKeyStoreAsymmetricEntry;\n\n /**\n * Whether this replaced an existing entry.\n */\n readonly replaced: boolean;\n\n /**\n * Best-effort warning from displaced-resource cleanup. Set when this call\n * replaced a prior entry but the corresponding\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage}.delete failed; the new\n * keypair is still committed and the orphaned blob is left for consumer-side\n * GC to reconcile.\n */\n readonly warning?: string;\n}\n\n/**\n * Result of removing a secret from the key store.\n * @public\n */\nexport interface IRemoveSecretResult {\n /**\n * The secret entry that was removed from the vault.\n */\n readonly entry: IKeyStoreEntry;\n\n /**\n * Best-effort warning from {@link CryptoUtils.KeyStore.IPrivateKeyStorage}.delete\n * for asymmetric entries when the storage call failed. The vault entry is\n * still considered removed and the orphaned blob is left for consumer-side\n * GC to reconcile.\n */\n readonly warning?: string;\n}\n\n// ============================================================================\n// Detection Helper\n// ============================================================================\n\n/**\n * Checks if a JSON object appears to be a key store file.\n * Uses the format field as a discriminator.\n * @param json - JSON object to check\n * @returns true if the object has the key store format field\n * @public\n */\nexport function isKeyStoreFile(json: unknown): boolean {\n if (typeof json !== 'object' || json === null) {\n return false;\n }\n const obj = json as Record<string, unknown>;\n return obj.format === KEYSTORE_FORMAT;\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../src/packlets/crypto-utils/keystore/model.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAYZ,2EAA2E;AAC3E,2EAA2E;AAC3E,OAAO,EAAE,oBAAoB,EAAoB,MAAM,UAAU,CAAC;AAkBlE;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAkC,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;AAEhG;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAmB,aAAa,CAAC;AAE7D;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAW,MAAM,CAAC;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,EAAE,CAAC;AAc1C;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAA+C;IACzF,gBAAgB;IAChB,SAAS;CACV,CAAC;AAWF;;;GAGG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAgD;IAC3F,oBAAoB;CACrB,CAAC;AAQF;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsC;IACvE,GAAG,gCAAgC;IACnC,GAAG,+BAA+B;CACnC,CAAC;AA6ZF;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAW,MAAM,CAAC;AA0JxD,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,OAAO,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAK,kBAA4C,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC9G,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport {\n EncryptionAlgorithm,\n ICryptoProvider,\n IArgon2idParams,\n IKeyDerivationParams,\n IPbkdf2KeyDerivationParams,\n KeyPairAlgorithm\n} from '../model';\nimport { IPrivateKeyStorage } from './privateKeyStorage';\n\n// Re-export so consumers can continue to access the algorithm enum via the\n// CryptoUtils.KeyStore namespace alongside the rest of the keystore types.\nexport { allKeyPairAlgorithms, KeyPairAlgorithm } from '../model';\n\n// ============================================================================\n// Key Store Format Types\n// ============================================================================\n\n/**\n * Format version for key store files.\n *\n * - `'keystore-v1'`: the original format (no private-key escrow).\n * - `'keystore-v2'`: adds the optional `escrowedPrivateKeyJwk` field on\n * asymmetric-keypair entries. A strict superset of v1 — the field is\n * optional, so a v2 reader opens a v1 vault with no special-casing, and a\n * v1 vault opened and re-saved is silently upgraded to v2.\n * @public\n */\nexport type KeyStoreFormat = 'keystore-v1' | 'keystore-v2';\n\n/**\n * All recognized key store format versions (readable by the current library).\n * @public\n */\nexport const allKeyStoreFormats: ReadonlyArray<KeyStoreFormat> = ['keystore-v1', 'keystore-v2'];\n\n/**\n * Current format version constant. New vaults are written as `'keystore-v2'`.\n * @public\n */\nexport const KEYSTORE_FORMAT: KeyStoreFormat = 'keystore-v2';\n\n/**\n * Default PBKDF2 iterations for key store encryption.\n * Higher than regular files since this protects the master key vault.\n * @public\n */\nexport const DEFAULT_KEYSTORE_ITERATIONS: number = 600000;\n\n/**\n * Minimum salt length for key derivation.\n * @public\n */\nexport const MIN_SALT_LENGTH: number = 16;\n\n// ============================================================================\n// Key Store Vault Contents (Decrypted State)\n// ============================================================================\n\n/**\n * Discriminator for symmetric secret types stored in the vault.\n * - `'encryption-key'`: A 32-byte AES-256 encryption key.\n * - `'api-key'`: An arbitrary-length API key string (UTF-8 encoded).\n * @public\n */\nexport type KeyStoreSymmetricSecretType = 'encryption-key' | 'api-key';\n\n/**\n * All valid symmetric secret types.\n * @public\n */\nexport const allKeyStoreSymmetricSecretTypes: ReadonlyArray<KeyStoreSymmetricSecretType> = [\n 'encryption-key',\n 'api-key'\n];\n\n/**\n * Discriminator for asymmetric secret types stored in the vault.\n * - `'asymmetric-keypair'`: A public/private key pair. The public key is held in\n * the vault as a JWK; the private key lives in the supplied\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage} provider.\n * @public\n */\nexport type KeyStoreAsymmetricSecretType = 'asymmetric-keypair';\n\n/**\n * All valid asymmetric secret types.\n * @public\n */\nexport const allKeyStoreAsymmetricSecretTypes: ReadonlyArray<KeyStoreAsymmetricSecretType> = [\n 'asymmetric-keypair'\n];\n\n/**\n * Discriminator for any secret type stored in the vault.\n * @public\n */\nexport type KeyStoreSecretType = KeyStoreSymmetricSecretType | KeyStoreAsymmetricSecretType;\n\n/**\n * All valid key store secret types.\n * @public\n */\nexport const allKeyStoreSecretTypes: ReadonlyArray<KeyStoreSecretType> = [\n ...allKeyStoreAsymmetricSecretTypes,\n ...allKeyStoreSymmetricSecretTypes\n];\n\n/**\n * A symmetric secret entry stored in the vault (in-memory representation).\n * Holds the raw key material directly — for `'encryption-key'` it is a 32-byte\n * AES-256 key; for `'api-key'` it is the UTF-8 encoded API key string.\n * @public\n */\nexport interface IKeyStoreSymmetricEntry {\n /**\n * Unique name for this secret (used as lookup key).\n */\n readonly name: string;\n\n /**\n * Symmetric secret type discriminator.\n */\n readonly type: KeyStoreSymmetricSecretType;\n\n /**\n * The secret data.\n * - For `'encryption-key'`: 32-byte AES-256 key.\n * - For `'api-key'`: UTF-8 encoded API key string (arbitrary length).\n */\n readonly key: Uint8Array;\n\n /**\n * Optional description for this secret.\n */\n readonly description?: string;\n\n /**\n * When this secret was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * An asymmetric keypair entry stored in the vault (in-memory representation).\n * Holds only the public key (as a JWK) and a stable handle (`id`) the\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage} provider uses to fetch the private key.\n * @public\n */\nexport interface IKeyStoreAsymmetricEntry {\n /**\n * Unique name for this entry (used as vault lookup key, renameable).\n */\n readonly name: string;\n\n /**\n * Asymmetric secret type discriminator.\n */\n readonly type: KeyStoreAsymmetricSecretType;\n\n /**\n * Immutable handle used by {@link CryptoUtils.KeyStore.IPrivateKeyStorage} to address the\n * private key. Independent of `name`; survives renames.\n */\n readonly id: string;\n\n /**\n * Algorithm used to generate this keypair.\n */\n readonly algorithm: KeyPairAlgorithm;\n\n /**\n * The public key as a JSON Web Key.\n */\n readonly publicKeyJwk: JsonWebKey;\n\n /**\n * Optional escrowed copy of the private key, as a JSON Web Key. Present only\n * when the entry was created with `addKeyPair(name, { escrow: true })`.\n *\n * This is an opt-in cross-device recovery affordance: the private key is\n * carried inside the vault's AES-GCM ciphertext (same custody class as\n * `publicKeyJwk`), so a recovered vault plus the master password can\n * reconstitute the signing/decryption identity on a fresh device via\n * `getKeyPair(name, { rehydrate: true })`.\n *\n * SECURITY: when present, the master password becomes the sole gate\n * protecting this private key. Use a strong KDF for escrow-bearing stores.\n * See the {@link CryptoUtils.KeyStore.KeyStore} class docs.\n */\n readonly escrowedPrivateKeyJwk?: JsonWebKey;\n\n /**\n * Optional description for this entry.\n */\n readonly description?: string;\n\n /**\n * When this entry was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * Any vault entry, discriminated by `type`.\n * @public\n */\nexport type IKeyStoreEntry = IKeyStoreSymmetricEntry | IKeyStoreAsymmetricEntry;\n\n/**\n * Backwards-compatible alias for {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntry}.\n * @deprecated Use {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntry} for symmetric\n * entries or {@link CryptoUtils.KeyStore.IKeyStoreEntry} for the discriminated union.\n * @public\n */\nexport type IKeyStoreSecretEntry = IKeyStoreSymmetricEntry;\n\n/**\n * JSON-serializable representation of a symmetric secret entry.\n *\n * @remarks\n * Describes the *normalized* shape after parsing. `type` is required here\n * because the converter (see\n * {@link CryptoUtils.KeyStore.Converters.keystoreSymmetricEntryJson | keystoreSymmetricEntryJson})\n * injects the default `'encryption-key'` when reading vaults written before\n * asymmetric-keypair support added the discriminator. Raw on-wire bytes from\n * a legacy vault may therefore omit `type`; downstream code only ever sees\n * the post-conversion shape declared here.\n *\n * @public\n */\nexport interface IKeyStoreSymmetricEntryJson {\n /**\n * Unique name for this secret.\n */\n readonly name: string;\n\n /**\n * Symmetric secret type discriminator.\n *\n * Required on this normalized model type. Vaults written prior to the\n * asymmetric-keypair support may omit this field on the wire; the\n * converter injects `'encryption-key'` when missing for backwards\n * compatibility, so by the time a value of this type is observed the\n * discriminator is always present.\n */\n readonly type: KeyStoreSymmetricSecretType;\n\n /**\n * Base64-encoded secret data.\n */\n readonly key: string;\n\n /**\n * Optional description.\n */\n readonly description?: string;\n\n /**\n * When this secret was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * JSON-serializable representation of an asymmetric keypair entry.\n * The private key is not present here — it lives in the\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage} provider, addressed by `id`.\n * @public\n */\nexport interface IKeyStoreAsymmetricEntryJson {\n /**\n * Unique name for this entry.\n */\n readonly name: string;\n\n /**\n * Asymmetric secret type discriminator.\n */\n readonly type: KeyStoreAsymmetricSecretType;\n\n /**\n * Immutable handle used by {@link CryptoUtils.KeyStore.IPrivateKeyStorage} to address the\n * private key.\n */\n readonly id: string;\n\n /**\n * Algorithm used to generate this keypair.\n */\n readonly algorithm: KeyPairAlgorithm;\n\n /**\n * The public key as a JSON Web Key.\n */\n readonly publicKeyJwk: JsonWebKey;\n\n /**\n * Optional escrowed copy of the private key, as a JSON Web Key. Present only\n * on `'keystore-v2'` vaults whose entry was created with escrow enabled. A\n * v1 vault omits the field; a v2 reader treats its absence as \"no escrow\".\n */\n readonly escrowedPrivateKeyJwk?: JsonWebKey;\n\n /**\n * Optional description.\n */\n readonly description?: string;\n\n /**\n * When this entry was added (ISO 8601).\n */\n readonly createdAt: string;\n}\n\n/**\n * Any JSON vault entry, discriminated by `type`.\n * @public\n */\nexport type IKeyStoreEntryJson = IKeyStoreSymmetricEntryJson | IKeyStoreAsymmetricEntryJson;\n\n/**\n * Backwards-compatible alias for {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson}.\n * @deprecated Use {@link CryptoUtils.KeyStore.IKeyStoreSymmetricEntryJson} for\n * symmetric entries or {@link CryptoUtils.KeyStore.IKeyStoreEntryJson} for the\n * discriminated union.\n * @public\n */\nexport type IKeyStoreSecretEntryJson = IKeyStoreSymmetricEntryJson;\n\n/**\n * The decrypted vault contents - a versioned map of entries.\n * @public\n */\nexport interface IKeyStoreVaultContents {\n /**\n * Format version for vault contents.\n */\n readonly version: KeyStoreFormat;\n\n /**\n * Map of entry name to entry (symmetric or asymmetric).\n */\n readonly secrets: Record<string, IKeyStoreEntryJson>;\n}\n\n// ============================================================================\n// Key Store File Format (Encrypted State)\n// ============================================================================\n\n/**\n * The encrypted key store file format.\n * @public\n */\nexport interface IKeyStoreFile {\n /**\n * Format identifier.\n */\n readonly format: KeyStoreFormat;\n\n /**\n * Algorithm used for encryption.\n */\n readonly algorithm: EncryptionAlgorithm;\n\n /**\n * Base64-encoded initialization vector.\n */\n readonly iv: string;\n\n /**\n * Base64-encoded authentication tag.\n */\n readonly authTag: string;\n\n /**\n * Base64-encoded encrypted vault contents.\n */\n readonly encryptedData: string;\n\n /**\n * Key derivation parameters for the vault master key (always PBKDF2).\n */\n readonly keyDerivation: IPbkdf2KeyDerivationParams;\n}\n\n// ============================================================================\n// Key Store State and Configuration\n// ============================================================================\n\n/**\n * Key store lock state.\n * @public\n */\nexport type KeyStoreLockState = 'locked' | 'unlocked';\n\n/**\n * Parameters for creating a new key store.\n * @public\n */\nexport interface IKeyStoreCreateParams {\n /**\n * Crypto provider to use.\n */\n readonly cryptoProvider: ICryptoProvider;\n\n /**\n * PBKDF2 iterations (defaults to DEFAULT_KEYSTORE_ITERATIONS).\n */\n readonly iterations?: number;\n\n /**\n * Optional private-key storage backend. Required to use `addKeyPair` /\n * `getKeyPair`; absent backends still permit opening, listing, and reading\n * public-key metadata for asymmetric entries.\n */\n readonly privateKeyStorage?: IPrivateKeyStorage;\n}\n\n/**\n * Parameters for opening an existing key store.\n * @public\n */\nexport interface IKeyStoreOpenParams {\n /**\n * Crypto provider to use.\n */\n readonly cryptoProvider: ICryptoProvider;\n\n /**\n * The encrypted key store file content.\n */\n readonly keystoreFile: IKeyStoreFile;\n\n /**\n * Optional private-key storage backend. Required to use `addKeyPair` /\n * `getKeyPair`; absent backends still permit opening, listing, and reading\n * public-key metadata for asymmetric entries.\n */\n readonly privateKeyStorage?: IPrivateKeyStorage;\n}\n\n/**\n * Result of adding a secret to the key store.\n * @public\n */\nexport interface IAddSecretResult {\n /**\n * The secret entry that was added.\n */\n readonly entry: IKeyStoreSymmetricEntry;\n\n /**\n * Whether this replaced an existing secret.\n */\n readonly replaced: boolean;\n\n /**\n * Best-effort warning from displaced-resource cleanup. Set when this call\n * replaced an asymmetric-keypair entry but the corresponding\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage}.delete failed; the new\n * entry is still committed and the orphaned blob is left for consumer-side\n * GC to reconcile.\n */\n readonly warning?: string;\n}\n\n/**\n * Options for adding a secret.\n * @public\n */\nexport interface IAddSecretOptions {\n /**\n * Optional description for the secret.\n */\n readonly description?: string;\n}\n\n/**\n * Options for importing a secret.\n * @public\n */\nexport interface IImportSecretOptions extends IAddSecretOptions {\n /**\n * Whether to replace an existing secret with the same name.\n */\n readonly replace?: boolean;\n}\n\n/**\n * Options for importing raw key material via {@link KeyStore.importSecret}.\n * Extends {@link IImportSecretOptions} with a type classification.\n * @public\n */\nexport interface IImportKeyOptions extends IImportSecretOptions {\n /**\n * Symmetric secret type classification for the imported key material.\n * @defaultValue 'encryption-key'\n */\n readonly type?: KeyStoreSymmetricSecretType;\n}\n\n/**\n * Options for adding a secret derived from a password.\n * @public\n */\nexport interface IAddSecretFromPasswordOptions extends IAddSecretOptions {\n /**\n * Whether to replace an existing secret with the same name.\n */\n readonly replace?: boolean;\n\n /**\n * PBKDF2 iterations for key derivation.\n * @defaultValue DEFAULT_SECRET_ITERATIONS (350000)\n */\n readonly iterations?: number;\n}\n\n/**\n * Default PBKDF2 iterations for secret-level key derivation.\n * Lower than keystore encryption since these are used more frequently.\n * @public\n */\nexport const DEFAULT_SECRET_ITERATIONS: number = 350000;\n\n/**\n * Result of adding a password-derived secret.\n * Extends {@link IAddSecretResult} with key derivation parameters\n * needed to store alongside encrypted files.\n * @public\n */\nexport interface IAddSecretFromPasswordResult extends IAddSecretResult {\n /**\n * Key derivation parameters used to derive the secret key.\n * Store these in encrypted file metadata so the password alone\n * can re-derive the same key for decryption.\n */\n readonly keyDerivation: IKeyDerivationParams;\n}\n\n/**\n * Options for adding an Argon2id password-derived secret.\n * @public\n */\nexport interface IAddSecretFromPasswordArgon2idOptions {\n /**\n * Argon2id parameters. Defaults to {@link CryptoUtils.ARGON2ID_OWASP_MIN}.\n */\n readonly params?: IArgon2idParams;\n /**\n * Optional description for the secret.\n */\n readonly description?: string;\n /**\n * Whether to replace an existing secret with the same name.\n */\n readonly replace?: boolean;\n}\n\n/**\n * Options for adding an asymmetric keypair to the key store.\n * @public\n */\nexport interface IAddKeyPairOptions {\n /**\n * Algorithm to use for the new keypair.\n */\n readonly algorithm: KeyPairAlgorithm;\n\n /**\n * Optional description for the entry.\n */\n readonly description?: string;\n\n /**\n * Whether to replace an existing entry with the same name.\n * Replacement mints a fresh storage `id` and best-effort deletes the\n * displaced storage blob; see the keystore design doc for details.\n */\n readonly replace?: boolean;\n\n /**\n * Overrides the storage-backend-derived extractability for this key only.\n * Omit to keep the default (`!privateKeyStorage.supportsNonExtractable`).\n * A value the backend cannot honor fails loudly rather than silently\n * downgrading.\n */\n readonly extractable?: boolean;\n\n /**\n * Opt in to private-key escrow. When `true`, an encrypted copy of the\n * private key (as a JWK) is carried inside the vault entry\n * (`escrowedPrivateKeyJwk`), enabling cross-device recovery from the vault\n * file plus the master password alone via\n * `getKeyPair(name, { rehydrate: true })`.\n *\n * Orthogonal to `extractable`: the escrow copy is always captured (the\n * transient keypair is generated extractable so it can be exported to JWK),\n * while the LIVE stored key's extractability still follows the normal rule\n * (`extractable` override, else the backend default). So escrow does not\n * change how extractable the day-to-day key is — only whether a recovery\n * copy is retained in the vault.\n *\n * @defaultValue false — no escrow copy is written (today's behavior).\n *\n * SECURITY: escrow makes the master password the sole gate protecting a\n * private key that would otherwise be unrecoverable from the vault. Use a\n * strong KDF (Argon2id-derived or high-iteration PBKDF2) for escrow-bearing\n * stores. See the {@link CryptoUtils.KeyStore.KeyStore} class docs.\n */\n readonly escrow?: boolean;\n}\n\n/**\n * Options for retrieving an asymmetric keypair via {@link CryptoUtils.KeyStore.KeyStore.getKeyPair}.\n * @public\n */\nexport interface IGetKeyPairOptions {\n /**\n * Opt in to escrow rehydration. When `true` AND no private-key blob exists\n * under the entry's storage `id` AND the entry carries an\n * `escrowedPrivateKeyJwk`, the escrowed JWK is imported (non-extractable\n * where the backend supports it) and stored under the entry's `id` before\n * being returned — filling a gap on a fresh device from the recovered vault.\n *\n * Fill-a-gap only: an existing storage blob is never overwritten. When a\n * blob is present it is loaded as usual and the escrow copy is ignored.\n *\n * @defaultValue false — today's behavior: load from storage or fail.\n */\n readonly rehydrate?: boolean;\n}\n\n/**\n * Result of adding an asymmetric keypair to the key store.\n * @public\n */\nexport interface IAddKeyPairResult {\n /**\n * The asymmetric entry that was added.\n */\n readonly entry: IKeyStoreAsymmetricEntry;\n\n /**\n * Whether this replaced an existing entry.\n */\n readonly replaced: boolean;\n\n /**\n * Best-effort warning from displaced-resource cleanup. Set when this call\n * replaced a prior entry but the corresponding\n * {@link CryptoUtils.KeyStore.IPrivateKeyStorage}.delete failed; the new\n * keypair is still committed and the orphaned blob is left for consumer-side\n * GC to reconcile.\n */\n readonly warning?: string;\n}\n\n/**\n * Result of removing a secret from the key store.\n * @public\n */\nexport interface IRemoveSecretResult {\n /**\n * The secret entry that was removed from the vault.\n */\n readonly entry: IKeyStoreEntry;\n\n /**\n * Best-effort warning from {@link CryptoUtils.KeyStore.IPrivateKeyStorage}.delete\n * for asymmetric entries when the storage call failed. The vault entry is\n * still considered removed and the orphaned blob is left for consumer-side\n * GC to reconcile.\n */\n readonly warning?: string;\n}\n\n// ============================================================================\n// Detection Helper\n// ============================================================================\n\n/**\n * Checks if a JSON object appears to be a key store file.\n * Uses the format field as a discriminator.\n * @param json - JSON object to check\n * @returns true if the object has the key store format field\n * @public\n */\nexport function isKeyStoreFile(json: unknown): boolean {\n if (typeof json !== 'object' || json === null) {\n return false;\n }\n const obj = json as Record<string, unknown>;\n return typeof obj.format === 'string' && (allKeyStoreFormats as ReadonlyArray<string>).includes(obj.format);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/model.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAKZ,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,CAAC;AAmJrB;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAoC;IACnE,YAAY;IACZ,eAAe;IACf,WAAW;IACX,SAAS;IACT,QAAQ;CACT,CAAC;AAqFF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,SAAS,EAAE,KAAK;IAChB,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,EAAE;CACP,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAoB;IAClD,SAAS,EAAE,KAAK;IAChB,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,EAAE;CACP,CAAC;AAubX,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,OAAO,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,qBAAqB,CAAC;AACxD,CAAC","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { JsonValue } from '@fgv/ts-json-base';\nimport { Brand, Result, Uuid } from '@fgv/ts-utils';\n\nimport * as Constants from './constants';\nexport { Constants };\n\n// ============================================================================\n// Public Key Types\n// ============================================================================\n\n/**\n * A multibase base64url-encoded SPKI (SubjectPublicKeyInfo) public key string —\n * a `'m'` multibase prefix followed by a base64url-no-pad body. Produced by\n * {@link CryptoUtils.exportPublicKeyAsMultibaseSpki} and consumed by\n * {@link CryptoUtils.importPublicKeyFromMultibaseSpki}. Obtain the brand at a\n * boundary via {@link CryptoUtils.isValidMultibaseSpkiPublicKey} or the\n * {@link CryptoUtils.Converters.multibaseSpkiPublicKey} converter.\n * @public\n */\nexport type MultibaseSpkiPublicKey = Brand<string, 'MultibaseSpkiPublicKey'>;\n\n// ============================================================================\n// Encryption Types\n// ============================================================================\n\n/**\n * Supported encryption algorithms.\n * @public\n */\nexport type EncryptionAlgorithm = typeof Constants.DEFAULT_ALGORITHM;\n\n/**\n * Format version for encrypted files.\n * @public\n */\nexport type EncryptedFileFormat = typeof Constants.ENCRYPTED_FILE_FORMAT;\n\n/**\n * Named secret for encryption/decryption.\n * @public\n */\nexport interface INamedSecret {\n /**\n * Unique name for this secret (referenced in encrypted files).\n */\n readonly name: string;\n\n /**\n * The actual secret key (32 bytes for AES-256).\n */\n readonly key: Uint8Array;\n}\n\n/**\n * Result of an encryption operation.\n * @public\n */\nexport interface IEncryptionResult {\n /**\n * Initialization vector used for encryption (12 bytes for GCM).\n */\n readonly iv: Uint8Array;\n\n /**\n * Authentication tag from GCM mode (16 bytes).\n */\n readonly authTag: Uint8Array;\n\n /**\n * The encrypted data.\n */\n readonly encryptedData: Uint8Array;\n}\n\n/**\n * Asymmetric keypair algorithms supported by the crypto provider.\n * - `'ecdsa-p256'`: ECDSA over the P-256 curve, for signing.\n * - `'rsa-oaep-2048'`: RSA-OAEP, 2048-bit modulus with SHA-256, for encryption.\n * - `'ecdh-p256'`: ECDH over the P-256 curve, for key agreement\n * (e.g. as the recipient keypair in\n * {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes} /\n * {@link CryptoUtils.ICryptoProvider.unwrapBytes | unwrapBytes}).\n * - `'ed25519'`: EdDSA over the Edwards25519 curve, for signing.\n * Deterministic — the per-signature nonce is derived from the private key\n * and message rather than sampled randomly, eliminating the random-nonce\n * reuse risk that ECDSA carries. Distinct from X25519 (key agreement over\n * the Montgomery form, Curve25519).\n * - `'x25519'`: Diffie-Hellman key agreement over the Montgomery form of\n * Curve25519. Key-agreement only — use `deriveBits`/`deriveKey` to produce\n * a shared secret from one party's private key and the peer's public key.\n * Distinct from Ed25519 (which uses the twisted-Edwards form for signing).\n * @public\n */\nexport type KeyPairAlgorithm = 'ecdsa-p256' | 'rsa-oaep-2048' | 'ecdh-p256' | 'ed25519' | 'x25519';\n\n/**\n * Caller-supplied HKDF parameters that domain-separate one\n * {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes} call from another.\n * Two wraps that share recipient but differ on `salt` or `info` derive distinct\n * wrap keys, so callers should pick values that bind the wrap to its\n * application context (e.g. a content hash for `salt` and a secret name for\n * `info`).\n *\n * Both fields are required; pass an empty `Uint8Array` if the caller has no\n * value to bind on a given axis. Silent defaulting would hide protocol\n * mistakes, so the API does not pick defaults.\n * @public\n */\nexport interface IWrapBytesOptions {\n /**\n * HKDF salt. Domain-separates this wrap from others in different contexts.\n * Caller picks; common choices include a content hash, document id, channel\n * id, etc.\n */\n readonly salt: Uint8Array;\n\n /**\n * HKDF info. Further binds the derived key to a specific use within the\n * calling application. Caller picks; common choices include a secret name,\n * message type, or version tag.\n */\n readonly info: Uint8Array;\n}\n\n/**\n * Output of {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes}. The\n * shape is JSON-serializable so it can travel directly over the wire or be\n * persisted as-is.\n * @public\n */\nexport interface IWrappedBytes {\n /**\n * Sender's ephemeral ECDH P-256 public key as a JSON Web Key. The matching\n * ephemeral private key is dropped after the shared-secret derive.\n */\n readonly ephemeralPublicKey: JsonWebKey;\n\n /**\n * AES-GCM nonce, base64-encoded. 12 bytes (96 bits) — the standard AES-GCM\n * nonce length.\n */\n readonly nonce: string;\n\n /**\n * AES-GCM ciphertext concatenated with the 16-byte authentication tag,\n * base64-encoded. Tampering with either the nonce or the ciphertext causes\n * unwrap to fail GCM authentication.\n */\n readonly ciphertext: string;\n}\n\n/**\n * All valid key pair algorithms.\n * @public\n */\nexport const allKeyPairAlgorithms: ReadonlyArray<KeyPairAlgorithm> = [\n 'ecdsa-p256',\n 'rsa-oaep-2048',\n 'ecdh-p256',\n 'ed25519',\n 'x25519'\n];\n\n/**\n * Supported key derivation functions.\n * @public\n */\nexport type KeyDerivationFunction = 'pbkdf2' | 'argon2id';\n\n/**\n * PBKDF2 key derivation parameters.\n * @public\n */\nexport interface IPbkdf2KeyDerivationParams {\n /** Key derivation function discriminator. */\n readonly kdf: 'pbkdf2';\n /** Base64-encoded salt used for key derivation. */\n readonly salt: string;\n /** Number of iterations used for key derivation. */\n readonly iterations: number;\n}\n\n/**\n * Argon2id key derivation parameters (RFC 9106).\n * @public\n */\nexport interface IArgon2idKeyDerivationParams {\n /** Key derivation function discriminator. */\n readonly kdf: 'argon2id';\n /** Base64-encoded salt used for key derivation. */\n readonly salt: string;\n /** Memory cost in kibibytes. */\n readonly memoryKiB: number;\n /** Number of passes (time cost). */\n readonly iterations: number;\n /** Degree of parallelism. */\n readonly parallelism: number;\n}\n\n/**\n * Key derivation parameters stored in encrypted files.\n * Discriminated union on `kdf` field: `'pbkdf2'` or `'argon2id'`.\n * @public\n */\nexport type IKeyDerivationParams = IPbkdf2KeyDerivationParams | IArgon2idKeyDerivationParams;\n\n// ============================================================================\n// Argon2id Types\n// ============================================================================\n\n/**\n * Parameters for Argon2id key derivation (RFC 9106).\n * All fields are required; fgv does not pick defaults silently.\n * @public\n */\nexport interface IArgon2idParams {\n /**\n * Memory cost in kibibytes (KiB).\n * OWASP 2023 minimum: 19456 (19 MiB). Stronger: 65536 (64 MiB).\n * Constraint: \\>= 8.\n */\n readonly memoryKiB: number;\n\n /**\n * Number of passes (iterations / time cost).\n * OWASP 2023 minimum: 2. Range: \\>= 1.\n */\n readonly iterations: number;\n\n /**\n * Degree of parallelism (threads).\n * Note: WASM-based implementations compute sequentially regardless of this value,\n * but the value is wired into the algorithm and AFFECTS the output hash bytes.\n * Callers must use the same parallelism value consistently for a given secret.\n * Range: 1–255.\n */\n readonly parallelism: number;\n\n /**\n * Number of output bytes (hash length).\n * Typical values: 16 (128-bit), 32 (256-bit, AES-256 key), 64 (512-bit).\n * Constraint: \\>= 4.\n */\n readonly outputBytes: number;\n}\n\n/**\n * Recommended OWASP 2023 minimum Argon2id parameters.\n * Suitable for recovery-row key derivation (high-entropy inputs).\n * @public\n */\nexport const ARGON2ID_OWASP_MIN: IArgon2idParams = {\n memoryKiB: 19456,\n iterations: 2,\n parallelism: 1,\n outputBytes: 32\n} as const;\n\n/**\n * Stronger Argon2id parameters suitable for user-typed passphrases.\n * @public\n */\nexport const ARGON2ID_PASSPHRASE: IArgon2idParams = {\n memoryKiB: 65536,\n iterations: 3,\n parallelism: 1,\n outputBytes: 32\n} as const;\n\n/**\n * Argon2id key derivation provider (RFC 9106).\n *\n * Implementations are in separate packages to avoid WASM bundle costs for\n * consumers who don't need Argon2id:\n * - Node: `@fgv/ts-extras-argon2` (`NodeArgon2Provider`)\n * - Browser: `@fgv/ts-web-extras-argon2` (`BrowserArgon2Provider`)\n *\n * @public\n */\nexport interface IArgon2idProvider {\n /**\n * Derives key material from a password using Argon2id (RFC 9106 §3.1).\n *\n * Returns the raw derived bytes as a `Uint8Array`. Both Node and browser\n * implementations produce bit-identical output for identical inputs.\n *\n * @param password - Password or passphrase. Accepts string (UTF-8) or raw bytes.\n * @param salt - Salt bytes. Must be random and unique per credential (\\>= 16 bytes recommended).\n * @param params - Argon2id parameters. Use `ARGON2ID_OWASP_MIN` as a starting point.\n * @returns Success with derived bytes, Failure with error context.\n */\n argon2id(\n password: Uint8Array | string,\n salt: Uint8Array,\n params: IArgon2idParams\n ): Promise<Result<Uint8Array>>;\n}\n\n/**\n * Generic encrypted file format.\n * This is the JSON structure stored in encrypted files.\n * @typeParam TMetadata - Type of optional unencrypted metadata\n * @public\n */\nexport interface IEncryptedFile<TMetadata = JsonValue> {\n /**\n * Format identifier for versioning.\n */\n readonly format: EncryptedFileFormat;\n\n /**\n * Name of the secret required to decrypt (references INamedSecret.name).\n */\n readonly secretName: string;\n\n /**\n * Algorithm used for encryption.\n */\n readonly algorithm: EncryptionAlgorithm;\n\n /**\n * Base64-encoded initialization vector.\n */\n readonly iv: string;\n\n /**\n * Base64-encoded authentication tag (for GCM mode).\n */\n readonly authTag: string;\n\n /**\n * Base64-encoded encrypted data (JSON string when decrypted).\n */\n readonly encryptedData: string;\n\n /**\n * Optional unencrypted metadata for display/filtering.\n */\n readonly metadata?: TMetadata;\n\n /**\n * Optional key derivation parameters.\n * If present, allows decryption using a password with these parameters.\n * If absent, a pre-derived key must be provided.\n */\n readonly keyDerivation?: IKeyDerivationParams;\n}\n\n// ============================================================================\n// Crypto Provider Interface\n// ============================================================================\n\n/**\n * Crypto provider interface for cross-platform encryption.\n * Implementations provided for Node.js (crypto module) and browser (Web Crypto API).\n * @public\n */\nexport interface ICryptoProvider {\n /**\n * Encrypts plaintext using AES-256-GCM.\n * @param plaintext - UTF-8 string to encrypt\n * @param key - 32-byte encryption key\n * @returns Success with encryption result, or Failure with error\n */\n encrypt(plaintext: string, key: Uint8Array): Promise<Result<IEncryptionResult>>;\n\n /**\n * Decrypts ciphertext using AES-256-GCM.\n * @param encryptedData - Encrypted bytes\n * @param key - 32-byte decryption key\n * @param iv - Initialization vector (12 bytes)\n * @param authTag - GCM authentication tag (16 bytes)\n * @returns Success with decrypted UTF-8 string, or Failure with error\n */\n decrypt(\n encryptedData: Uint8Array,\n key: Uint8Array,\n iv: Uint8Array,\n authTag: Uint8Array\n ): Promise<Result<string>>;\n\n /**\n * Generates a random 32-byte key suitable for AES-256.\n * @returns Success with generated key, or Failure with error\n */\n generateKey(): Promise<Result<Uint8Array>>;\n\n /**\n * Derives a key from a password using PBKDF2.\n * @param password - Password string\n * @param salt - Salt bytes (should be at least 16 bytes)\n * @param iterations - Number of iterations (recommend 100000+)\n * @returns Success with derived 32-byte key, or Failure with error\n */\n deriveKey(password: string, salt: Uint8Array, iterations: number): Promise<Result<Uint8Array>>;\n\n /**\n * Computes a SHA-256 hash of the given data.\n * @param data - UTF-8 string to hash\n * @returns Success with hex-encoded hash string, or Failure with error\n */\n sha256(data: string): Promise<Result<string>>;\n\n // ============================================================================\n // Platform Utility Methods\n // ============================================================================\n\n /**\n * Generates cryptographically secure random bytes.\n * @param length - Number of bytes to generate\n * @returns Success with random bytes, or Failure with error\n */\n generateRandomBytes(length: number): Result<Uint8Array>;\n\n /**\n * Generates a cryptographically random UUIDv4 using the provider's\n * underlying source of randomness. The default Node and browser\n * implementations delegate to `globalThis.crypto.randomUUID`;\n * deterministic providers (e.g. test stubs) may override to produce\n * reproducible values.\n * @returns Success with a canonical UUID, or Failure with error.\n */\n generateUuid(): Result<Uuid>;\n\n /**\n * Encodes binary data to base64 string.\n * @param data - Binary data to encode\n * @returns Base64-encoded string\n */\n toBase64(data: Uint8Array): string;\n\n /**\n * Decodes base64 string to binary data.\n * @param base64 - Base64-encoded string\n * @returns Success with decoded bytes, or Failure if invalid base64\n */\n fromBase64(base64: string): Result<Uint8Array>;\n\n // ============================================================================\n // Asymmetric Key Operations\n // ============================================================================\n\n /**\n * Generates a new asymmetric keypair for the requested algorithm.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} to use.\n * @param extractable - Whether the resulting `CryptoKey` objects may be exported.\n * Set `false` on backends that store `CryptoKey` references directly (e.g.\n * IndexedDB). Set `true` when the private key must round-trip through JWK or\n * PKCS#8 (e.g. encrypted-file backends).\n * @returns Success with the generated `CryptoKeyPair`, or Failure with error context.\n */\n generateKeyPair(algorithm: KeyPairAlgorithm, extractable: boolean): Promise<Result<CryptoKeyPair>>;\n\n /**\n * Exports the public half of a keypair as a JSON Web Key.\n * @param publicKey - The public `CryptoKey` to export. Must be an `extractable`\n * key generated for an asymmetric algorithm.\n * @returns Success with the JWK, or Failure with error context.\n */\n exportPublicKeyJwk(publicKey: CryptoKey): Promise<Result<JsonWebKey>>;\n\n /**\n * Re-imports a public-key JWK as a `CryptoKey` usable for verification or\n * encryption (depending on algorithm).\n * @param jwk - The JSON Web Key produced by {@link CryptoUtils.ICryptoProvider.exportPublicKeyJwk | exportPublicKeyJwk}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} the\n * key was generated for. Determines the import parameters and key usages.\n * @returns Success with the imported public `CryptoKey`, or Failure with error context.\n */\n importPublicKeyJwk(jwk: JsonWebKey, algorithm: KeyPairAlgorithm): Promise<Result<CryptoKey>>;\n\n /**\n * Exports a public `CryptoKey` as a DER-encoded SPKI (SubjectPublicKeyInfo) blob.\n * SPKI is the standard algorithm-agnostic format for public key storage and transport.\n * @param publicKey - The `CryptoKey` to export. Must have `key.type === 'public'`.\n * @returns `Success` with the raw SPKI bytes, or `Failure` with error context.\n */\n exportPublicKeySpki(publicKey: CryptoKey): Promise<Result<Uint8Array>>;\n\n /**\n * Imports a public key from a DER-encoded SPKI blob.\n * @param spkiBytes - The raw SPKI bytes produced by {@link CryptoUtils.ICryptoProvider.exportPublicKeySpki | exportPublicKeySpki}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} the key was generated for.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n */\n importPublicKeySpki(spkiBytes: Uint8Array, algorithm: KeyPairAlgorithm): Promise<Result<CryptoKey>>;\n\n /**\n * Wraps `plaintext` for delivery to the holder of the private key paired\n * with `recipientPublicKey`. Uses ECIES with ECDH P-256, HKDF-SHA256, and\n * AES-GCM-256.\n *\n * Generates a fresh ephemeral keypair per call; the ephemeral private key\n * is discarded after the shared-secret derive. Only the recipient (with the\n * matching private key) and the same HKDF parameters can recover\n * `plaintext`.\n *\n * Empty `plaintext` is permitted; the resulting wrap contains only the\n * 16-byte GCM authentication tag and round-trips back to an empty\n * `Uint8Array`.\n * @param plaintext - The bytes to wrap. Any length supported by AES-GCM\n * (in practice, well below 2^39 - 256 bits).\n * @param recipientPublicKey - The recipient's ECDH P-256 public `CryptoKey`.\n * Must have algorithm name `'ECDH'` and named curve `'P-256'`; mismatched\n * algorithm or curve yields a `Failure` with error context.\n * @param options - HKDF parameters; see {@link CryptoUtils.IWrapBytesOptions | IWrapBytesOptions}.\n * @returns `Success` with the wrapped payload, or `Failure` with error context.\n */\n wrapBytes(\n plaintext: Uint8Array,\n recipientPublicKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<IWrappedBytes>>;\n\n /**\n * Inverse of {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes}.\n * Recovers the original `plaintext` from a wrapped payload using the\n * recipient's private key.\n *\n * Returns a `Failure` (never throws) on any of:\n * - Tampered nonce or ciphertext (AES-GCM authentication fails)\n * - Wrong private key (different shared secret derives a different wrap key)\n * - Wrong HKDF parameters (different wrap key)\n * - Malformed `ephemeralPublicKey` JWK\n * - Malformed base64 in `nonce` or `ciphertext`\n * @param wrapped - The wrapped payload produced by `wrapBytes`.\n * @param recipientPrivateKey - The recipient's ECDH P-256 private\n * `CryptoKey`. Must have algorithm name `'ECDH'` and named curve `'P-256'`,\n * and key usages including `'deriveKey'` or `'deriveBits'`.\n * @param options - The same HKDF parameters used at wrap time.\n * @returns `Success` with the original `plaintext`, or `Failure` with error context.\n */\n unwrapBytes(\n wrapped: IWrappedBytes,\n recipientPrivateKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<Uint8Array>>;\n\n // ============================================================================\n // Signing Operations\n // ============================================================================\n\n /**\n * Signs `data` with `privateKey` using the algorithm inferred from the key.\n * Delegates to `crypto.subtle.sign`; the algorithm is derived from\n * `privateKey.algorithm.name` — ECDSA keys are augmented with\n * `hash: 'SHA-256'` at sign time (the hash is not stored in the key);\n * all other algorithm names are passed through as-is.\n * Intended for Ed25519 and ECDSA-P256 asymmetric private keys; for\n * HMAC-SHA256 authentication codes use {@link ICryptoProvider.hmacSha256} instead.\n * @param privateKey - A `CryptoKey` with `'sign'` usage (e.g. generated by\n * {@link CryptoUtils.ICryptoProvider.generateKeyPair | generateKeyPair} with\n * `'ecdsa-p256'` or `'ed25519'`).\n * @param data - The bytes to sign.\n * @returns `Success` with the raw signature bytes, or `Failure` with error context.\n */\n sign(privateKey: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>>;\n\n /**\n * Verifies a signature produced by {@link ICryptoProvider.sign}.\n * Delegates to `crypto.subtle.verify`; the algorithm is derived from\n * `publicKey.algorithm.name` — ECDSA keys are augmented with\n * `hash: 'SHA-256'`; all other algorithm names are passed through as-is.\n * Intended for Ed25519 and ECDSA-P256 asymmetric public keys; for\n * HMAC-SHA256 verification use {@link ICryptoProvider.verifyHmacSha256} instead.\n * @param publicKey - A `CryptoKey` with `'verify'` usage (e.g. the public\n * half of a keypair generated by\n * {@link CryptoUtils.ICryptoProvider.generateKeyPair | generateKeyPair} with\n * `'ecdsa-p256'` or `'ed25519'`).\n * @param signature - The raw signature bytes produced by `sign`.\n * @param data - The original data that was signed.\n * @returns `Success` with `true` if the signature is valid, `false` if it is\n * not, or `Failure` with error context if the operation itself failed.\n */\n verify(publicKey: CryptoKey, signature: Uint8Array, data: Uint8Array): Promise<Result<boolean>>;\n\n /**\n * Compares two byte arrays in constant time.\n *\n * The comparison visits all bytes of `a` and `b` regardless of where they\n * diverge, accumulating XOR differences with bitwise-OR. No early-return is\n * possible once the length check passes, making timing independent of the\n * byte values. This prevents timing side-channels when comparing MAC outputs,\n * signed-token bytes, or any secret-derived byte sequences.\n *\n * Returns `false` immediately (before the loop) when `a.length !== b.length`;\n * the length mismatch itself is not secret in normal use.\n * @param a - First byte array.\n * @param b - Second byte array.\n * @returns `true` if the arrays have the same length and identical contents,\n * `false` otherwise.\n */\n timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean;\n\n /**\n * Computes an HMAC-SHA256 authentication code for `data` using `key`.\n *\n * The key must be a `CryptoKey` with `'sign'` usage and algorithm name\n * `'HMAC'` (e.g. derived via PBKDF2 or imported with\n * `crypto.subtle.importKey`). Use {@link ICryptoProvider.verifyHmacSha256}\n * for constant-time verification of the output.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param data - The bytes to authenticate.\n * @returns `Success` with the 32-byte MAC, or `Failure` with error context.\n */\n hmacSha256(key: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>>;\n\n /**\n * Verifies an HMAC-SHA256 authentication code in constant time.\n *\n * Computes the expected MAC over `data` with `key`, then compares it to\n * `signature` using {@link ICryptoProvider.timingSafeEqual} so that\n * mismatches do not leak information through timing.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param signature - The MAC bytes to verify (typically 32 bytes).\n * @param data - The original data that was authenticated.\n * @returns `Success` with `true` if the MAC is valid, `false` if it is not,\n * or `Failure` with error context if the MAC computation itself failed.\n */\n verifyHmacSha256(key: CryptoKey, signature: Uint8Array, data: Uint8Array): Promise<Result<boolean>>;\n}\n\n// ============================================================================\n// Encryption Provider Interface\n// ============================================================================\n\n/**\n * High-level interface for encrypting JSON content by secret name.\n *\n * This abstraction unifies two common encryption workflows:\n * - **KeyStore**: looks up the named secret and crypto provider from the vault\n * - **DirectEncryptionProvider**: uses a pre-supplied key and crypto provider,\n * optionally bound to a specific secret name for safety\n *\n * Callers that need to encrypt (e.g. `EditableCollection.save()`) depend on\n * this interface rather than on `KeyStore` directly, allowing mix-and-match.\n *\n * @public\n */\nexport interface IEncryptionProvider {\n /**\n * Encrypts JSON content under a named secret.\n *\n * @param secretName - Name of the secret to encrypt with\n * @param content - JSON-safe content to encrypt\n * @param metadata - Optional unencrypted metadata to include in the encrypted file\n * @returns Success with encrypted file structure, or Failure with error context\n */\n encryptByName<TMetadata = JsonValue>(\n secretName: string,\n content: JsonValue,\n metadata?: TMetadata\n ): Promise<Result<IEncryptedFile<TMetadata>>>;\n}\n\n// ============================================================================\n// Encryption Configuration\n// ============================================================================\n\n/**\n * Behavior when an encrypted file cannot be decrypted.\n * @public\n */\nexport type EncryptedFileErrorMode =\n | 'fail' // Return failure, abort loading\n | 'skip' // Skip file silently, continue loading others\n | 'warn'; // Log warning, skip file, continue loading\n\n/**\n * Function type for dynamic secret retrieval.\n * @public\n */\nexport type SecretProvider = (secretName: string) => Promise<Result<Uint8Array>>;\n\n/**\n * Configuration for encrypted file handling during loading.\n * @public\n */\nexport interface IEncryptionConfig {\n /**\n * Named secrets available for decryption.\n */\n readonly secrets?: ReadonlyArray<INamedSecret>;\n\n /**\n * Alternative: dynamic secret provider function.\n * Called when a secret is not found in the secrets array.\n */\n readonly secretProvider?: SecretProvider;\n\n /**\n * Crypto provider implementation (Node.js or browser).\n */\n readonly cryptoProvider: ICryptoProvider;\n\n /**\n * Behavior when decryption key is missing (default: 'fail').\n */\n readonly onMissingKey?: EncryptedFileErrorMode;\n\n /**\n * Behavior when decryption fails (default: 'fail').\n */\n readonly onDecryptionError?: EncryptedFileErrorMode;\n}\n\n// ============================================================================\n// Detection Helper\n// ============================================================================\n\n/**\n * Checks if a JSON object appears to be an encrypted file.\n * Uses the format field as a discriminator.\n * @param json - JSON object to check\n * @returns true if the object has the encrypted file format field\n * @public\n */\nexport function isEncryptedFile(json: unknown): boolean {\n if (typeof json !== 'object' || json === null) {\n return false;\n }\n const obj = json as Record<string, unknown>;\n return obj.format === Constants.ENCRYPTED_FILE_FORMAT;\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/model.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAKZ,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,CAAC;AAgLrB;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAoC;IACnE,YAAY;IACZ,eAAe;IACf,WAAW;IACX,SAAS;IACT,QAAQ;CACT,CAAC;AAqFF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,SAAS,EAAE,KAAK;IAChB,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,EAAE;CACP,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAoB;IAClD,SAAS,EAAE,KAAK;IAChB,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,EAAE;CACP,CAAC;AAkgBX,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,OAAO,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,qBAAqB,CAAC;AACxD,CAAC","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { JsonValue } from '@fgv/ts-json-base';\nimport { Brand, Result, Uuid } from '@fgv/ts-utils';\n\nimport * as Constants from './constants';\nexport { Constants };\n\n// ============================================================================\n// Public Key Types\n// ============================================================================\n\n/**\n * A multibase base64url-encoded SPKI (SubjectPublicKeyInfo) public key string —\n * a `'m'` multibase prefix followed by a base64url-no-pad body. Produced by\n * {@link CryptoUtils.exportPublicKeyAsMultibaseSpki} and consumed by\n * {@link CryptoUtils.importPublicKeyFromMultibaseSpki}. Obtain the brand at a\n * boundary via {@link CryptoUtils.isValidMultibaseSpkiPublicKey} or the\n * {@link CryptoUtils.Converters.multibaseSpkiPublicKey} converter.\n * @public\n */\nexport type MultibaseSpkiPublicKey = Brand<string, 'MultibaseSpkiPublicKey'>;\n\n// ============================================================================\n// Encryption Types\n// ============================================================================\n\n/**\n * Supported encryption algorithms.\n * @public\n */\nexport type EncryptionAlgorithm = typeof Constants.DEFAULT_ALGORITHM;\n\n/**\n * Format version for encrypted files.\n * @public\n */\nexport type EncryptedFileFormat = typeof Constants.ENCRYPTED_FILE_FORMAT;\n\n/**\n * Named secret for encryption/decryption.\n * @public\n */\nexport interface INamedSecret {\n /**\n * Unique name for this secret (referenced in encrypted files).\n */\n readonly name: string;\n\n /**\n * The actual secret key (32 bytes for AES-256).\n */\n readonly key: Uint8Array;\n}\n\n/**\n * Result of an encryption operation.\n * @public\n */\nexport interface IEncryptionResult {\n /**\n * Initialization vector used for encryption (12 bytes for GCM).\n */\n readonly iv: Uint8Array;\n\n /**\n * Authentication tag from GCM mode (16 bytes).\n */\n readonly authTag: Uint8Array;\n\n /**\n * The encrypted data.\n */\n readonly encryptedData: Uint8Array;\n}\n\n/**\n * Result of a raw-byte AES-256-GCM encryption via\n * {@link CryptoUtils.ICryptoProvider.encryptBytes | encryptBytes}. The\n * authentication tag is returned separately from the ciphertext, mirroring the\n * separated-tag convention of {@link CryptoUtils.IEncryptionResult}. The exact\n * byte layout is:\n * - `ciphertext`: the AES-GCM ciphertext, byte-for-byte the same length as the\n * input plaintext (GCM is a stream cipher — no padding). Empty plaintext\n * yields an empty `ciphertext`.\n * - `authTag`: the 16-byte (128-bit) GCM authentication tag, computed over the\n * ciphertext, the nonce, and any `aad`.\n *\n * The caller persists both fields alongside the (caller-owned) nonce and feeds\n * them back to {@link CryptoUtils.ICryptoProvider.decryptBytes | decryptBytes}.\n * @public\n */\nexport interface IEncryptBytesResult {\n /**\n * The AES-256-GCM ciphertext. Same length as the input plaintext (no tag\n * appended — the tag is carried separately in the `authTag` field).\n */\n readonly ciphertext: Uint8Array;\n\n /**\n * The 16-byte (128-bit) GCM authentication tag.\n */\n readonly authTag: Uint8Array;\n}\n\n/**\n * Asymmetric keypair algorithms supported by the crypto provider.\n * - `'ecdsa-p256'`: ECDSA over the P-256 curve, for signing.\n * - `'rsa-oaep-2048'`: RSA-OAEP, 2048-bit modulus with SHA-256, for encryption.\n * - `'ecdh-p256'`: ECDH over the P-256 curve, for key agreement\n * (e.g. as the recipient keypair in\n * {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes} /\n * {@link CryptoUtils.ICryptoProvider.unwrapBytes | unwrapBytes}).\n * - `'ed25519'`: EdDSA over the Edwards25519 curve, for signing.\n * Deterministic — the per-signature nonce is derived from the private key\n * and message rather than sampled randomly, eliminating the random-nonce\n * reuse risk that ECDSA carries. Distinct from X25519 (key agreement over\n * the Montgomery form, Curve25519).\n * - `'x25519'`: Diffie-Hellman key agreement over the Montgomery form of\n * Curve25519. Key-agreement only — use `deriveBits`/`deriveKey` to produce\n * a shared secret from one party's private key and the peer's public key.\n * Distinct from Ed25519 (which uses the twisted-Edwards form for signing).\n * @public\n */\nexport type KeyPairAlgorithm = 'ecdsa-p256' | 'rsa-oaep-2048' | 'ecdh-p256' | 'ed25519' | 'x25519';\n\n/**\n * Caller-supplied HKDF parameters that domain-separate one\n * {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes} call from another.\n * Two wraps that share recipient but differ on `salt` or `info` derive distinct\n * wrap keys, so callers should pick values that bind the wrap to its\n * application context (e.g. a content hash for `salt` and a secret name for\n * `info`).\n *\n * Both fields are required; pass an empty `Uint8Array` if the caller has no\n * value to bind on a given axis. Silent defaulting would hide protocol\n * mistakes, so the API does not pick defaults.\n * @public\n */\nexport interface IWrapBytesOptions {\n /**\n * HKDF salt. Domain-separates this wrap from others in different contexts.\n * Caller picks; common choices include a content hash, document id, channel\n * id, etc.\n */\n readonly salt: Uint8Array;\n\n /**\n * HKDF info. Further binds the derived key to a specific use within the\n * calling application. Caller picks; common choices include a secret name,\n * message type, or version tag.\n */\n readonly info: Uint8Array;\n}\n\n/**\n * Output of {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes}. The\n * shape is JSON-serializable so it can travel directly over the wire or be\n * persisted as-is.\n * @public\n */\nexport interface IWrappedBytes {\n /**\n * Sender's ephemeral ECDH P-256 public key as a JSON Web Key. The matching\n * ephemeral private key is dropped after the shared-secret derive.\n */\n readonly ephemeralPublicKey: JsonWebKey;\n\n /**\n * AES-GCM nonce, base64-encoded. 12 bytes (96 bits) — the standard AES-GCM\n * nonce length.\n */\n readonly nonce: string;\n\n /**\n * AES-GCM ciphertext concatenated with the 16-byte authentication tag,\n * base64-encoded. Tampering with either the nonce or the ciphertext causes\n * unwrap to fail GCM authentication.\n */\n readonly ciphertext: string;\n}\n\n/**\n * All valid key pair algorithms.\n * @public\n */\nexport const allKeyPairAlgorithms: ReadonlyArray<KeyPairAlgorithm> = [\n 'ecdsa-p256',\n 'rsa-oaep-2048',\n 'ecdh-p256',\n 'ed25519',\n 'x25519'\n];\n\n/**\n * Supported key derivation functions.\n * @public\n */\nexport type KeyDerivationFunction = 'pbkdf2' | 'argon2id';\n\n/**\n * PBKDF2 key derivation parameters.\n * @public\n */\nexport interface IPbkdf2KeyDerivationParams {\n /** Key derivation function discriminator. */\n readonly kdf: 'pbkdf2';\n /** Base64-encoded salt used for key derivation. */\n readonly salt: string;\n /** Number of iterations used for key derivation. */\n readonly iterations: number;\n}\n\n/**\n * Argon2id key derivation parameters (RFC 9106).\n * @public\n */\nexport interface IArgon2idKeyDerivationParams {\n /** Key derivation function discriminator. */\n readonly kdf: 'argon2id';\n /** Base64-encoded salt used for key derivation. */\n readonly salt: string;\n /** Memory cost in kibibytes. */\n readonly memoryKiB: number;\n /** Number of passes (time cost). */\n readonly iterations: number;\n /** Degree of parallelism. */\n readonly parallelism: number;\n}\n\n/**\n * Key derivation parameters stored in encrypted files.\n * Discriminated union on `kdf` field: `'pbkdf2'` or `'argon2id'`.\n * @public\n */\nexport type IKeyDerivationParams = IPbkdf2KeyDerivationParams | IArgon2idKeyDerivationParams;\n\n// ============================================================================\n// Argon2id Types\n// ============================================================================\n\n/**\n * Parameters for Argon2id key derivation (RFC 9106).\n * All fields are required; fgv does not pick defaults silently.\n * @public\n */\nexport interface IArgon2idParams {\n /**\n * Memory cost in kibibytes (KiB).\n * OWASP 2023 minimum: 19456 (19 MiB). Stronger: 65536 (64 MiB).\n * Constraint: \\>= 8.\n */\n readonly memoryKiB: number;\n\n /**\n * Number of passes (iterations / time cost).\n * OWASP 2023 minimum: 2. Range: \\>= 1.\n */\n readonly iterations: number;\n\n /**\n * Degree of parallelism (threads).\n * Note: WASM-based implementations compute sequentially regardless of this value,\n * but the value is wired into the algorithm and AFFECTS the output hash bytes.\n * Callers must use the same parallelism value consistently for a given secret.\n * Range: 1–255.\n */\n readonly parallelism: number;\n\n /**\n * Number of output bytes (hash length).\n * Typical values: 16 (128-bit), 32 (256-bit, AES-256 key), 64 (512-bit).\n * Constraint: \\>= 4.\n */\n readonly outputBytes: number;\n}\n\n/**\n * Recommended OWASP 2023 minimum Argon2id parameters.\n * Suitable for recovery-row key derivation (high-entropy inputs).\n * @public\n */\nexport const ARGON2ID_OWASP_MIN: IArgon2idParams = {\n memoryKiB: 19456,\n iterations: 2,\n parallelism: 1,\n outputBytes: 32\n} as const;\n\n/**\n * Stronger Argon2id parameters suitable for user-typed passphrases.\n * @public\n */\nexport const ARGON2ID_PASSPHRASE: IArgon2idParams = {\n memoryKiB: 65536,\n iterations: 3,\n parallelism: 1,\n outputBytes: 32\n} as const;\n\n/**\n * Argon2id key derivation provider (RFC 9106).\n *\n * Implementations are in separate packages to avoid WASM bundle costs for\n * consumers who don't need Argon2id:\n * - Node: `@fgv/ts-extras-argon2` (`NodeArgon2Provider`)\n * - Browser: `@fgv/ts-web-extras-argon2` (`BrowserArgon2Provider`)\n *\n * @public\n */\nexport interface IArgon2idProvider {\n /**\n * Derives key material from a password using Argon2id (RFC 9106 §3.1).\n *\n * Returns the raw derived bytes as a `Uint8Array`. Both Node and browser\n * implementations produce bit-identical output for identical inputs.\n *\n * @param password - Password or passphrase. Accepts string (UTF-8) or raw bytes.\n * @param salt - Salt bytes. Must be random and unique per credential (\\>= 16 bytes recommended).\n * @param params - Argon2id parameters. Use `ARGON2ID_OWASP_MIN` as a starting point.\n * @returns Success with derived bytes, Failure with error context.\n */\n argon2id(\n password: Uint8Array | string,\n salt: Uint8Array,\n params: IArgon2idParams\n ): Promise<Result<Uint8Array>>;\n}\n\n/**\n * Generic encrypted file format.\n * This is the JSON structure stored in encrypted files.\n * @typeParam TMetadata - Type of optional unencrypted metadata\n * @public\n */\nexport interface IEncryptedFile<TMetadata = JsonValue> {\n /**\n * Format identifier for versioning.\n */\n readonly format: EncryptedFileFormat;\n\n /**\n * Name of the secret required to decrypt (references INamedSecret.name).\n */\n readonly secretName: string;\n\n /**\n * Algorithm used for encryption.\n */\n readonly algorithm: EncryptionAlgorithm;\n\n /**\n * Base64-encoded initialization vector.\n */\n readonly iv: string;\n\n /**\n * Base64-encoded authentication tag (for GCM mode).\n */\n readonly authTag: string;\n\n /**\n * Base64-encoded encrypted data (JSON string when decrypted).\n */\n readonly encryptedData: string;\n\n /**\n * Optional unencrypted metadata for display/filtering.\n */\n readonly metadata?: TMetadata;\n\n /**\n * Optional key derivation parameters.\n * If present, allows decryption using a password with these parameters.\n * If absent, a pre-derived key must be provided.\n */\n readonly keyDerivation?: IKeyDerivationParams;\n}\n\n// ============================================================================\n// Crypto Provider Interface\n// ============================================================================\n\n/**\n * Crypto provider interface for cross-platform encryption.\n * Implementations provided for Node.js (crypto module) and browser (Web Crypto API).\n * @public\n */\nexport interface ICryptoProvider {\n /**\n * Encrypts plaintext using AES-256-GCM.\n * @param plaintext - UTF-8 string to encrypt\n * @param key - 32-byte encryption key\n * @returns Success with encryption result, or Failure with error\n */\n encrypt(plaintext: string, key: Uint8Array): Promise<Result<IEncryptionResult>>;\n\n /**\n * Decrypts ciphertext using AES-256-GCM.\n * @param encryptedData - Encrypted bytes\n * @param key - 32-byte decryption key\n * @param iv - Initialization vector (12 bytes)\n * @param authTag - GCM authentication tag (16 bytes)\n * @returns Success with decrypted UTF-8 string, or Failure with error\n */\n decrypt(\n encryptedData: Uint8Array,\n key: Uint8Array,\n iv: Uint8Array,\n authTag: Uint8Array\n ): Promise<Result<string>>;\n\n /**\n * Encrypts raw bytes using AES-256-GCM with a **caller-supplied nonce** and\n * optional additional authenticated data (AAD).\n *\n * This is the raw-byte sibling of {@link CryptoUtils.ICryptoProvider.encrypt | encrypt}.\n * Unlike `encrypt`, it takes and returns `Uint8Array` (no UTF-8 coding), the\n * caller owns the nonce (rather than the provider generating one), and it\n * binds optional `aad` into the GCM authentication. Use it when you need to\n * bind context (e.g. an actor id, key version, or row kind) into the\n * authentication so a wrapped secret cannot be replayed across\n * users/versions/kinds, or when you manage nonces yourself.\n *\n * @remarks\n * **⚠️ NONCE UNIQUENESS IS THE CALLER'S RESPONSIBILITY AND IS CRITICAL.**\n * Because the caller supplies the nonce, this primitive cannot guarantee\n * uniqueness. Reusing a `(key, nonce)` pair for two different messages is\n * **catastrophic** for AES-GCM: it breaks confidentiality (the XOR of the two\n * plaintexts leaks) AND authentication (the GCM authentication key can be\n * recovered, letting an attacker forge tags for arbitrary messages under that\n * key). The caller MUST use a unique nonce for every message encrypted under a\n * given key — draw it from {@link CryptoUtils.ICryptoProvider.generateRandomBytes | generateRandomBytes(12)}\n * (12 random bytes has negligible collision probability well within a single\n * key's message budget) or from a strictly-increasing counter. Never hardcode\n * a nonce and never reuse one.\n *\n * @param key - 32-byte AES-256 key. Wrong lengths fail with error context.\n * @param nonce - 12-byte (96-bit) GCM nonce. MUST be unique per message under\n * `key` (see the nonce-uniqueness warning above). Wrong lengths fail with\n * error context.\n * @param plaintext - The bytes to encrypt. Empty plaintext is permitted and\n * round-trips (GCM produces a valid tag over zero-length plaintext).\n * @param aad - Optional additional authenticated data bound into the GCM tag\n * but NOT encrypted. If provided at encrypt time, the identical bytes must be\n * supplied to `decryptBytes` or decryption fails authentication. Absent means\n * no AAD.\n * @returns `Success` with the {@link CryptoUtils.IEncryptBytesResult | ciphertext and 16-byte auth tag},\n * or `Failure` with error context.\n */\n encryptBytes(\n key: Uint8Array,\n nonce: Uint8Array,\n plaintext: Uint8Array,\n aad?: Uint8Array\n ): Promise<Result<IEncryptBytesResult>>;\n\n /**\n * Decrypts raw bytes produced by\n * {@link CryptoUtils.ICryptoProvider.encryptBytes | encryptBytes} using\n * AES-256-GCM. The inverse of `encryptBytes`: the `ciphertext` and `authTag`\n * from an `encryptBytes` result, together with the same `key`, `nonce`, and\n * `aad`, recover the original plaintext.\n *\n * Fails (never throws) on any authentication failure: a tampered ciphertext\n * or tag, the wrong key or nonce, or an `aad` that differs from the one used\n * at encrypt time. AES-GCM authentication is fail-closed — a mismatched `aad`\n * fails exactly as a tampered ciphertext does.\n *\n * @param key - 32-byte AES-256 key (the same key used to encrypt).\n * @param nonce - 12-byte (96-bit) GCM nonce (the same nonce used to encrypt).\n * @param ciphertext - The ciphertext from the `encryptBytes` result.\n * @param authTag - The 16-byte (128-bit) GCM authentication tag from the\n * `encryptBytes` result.\n * @param aad - The identical additional authenticated data supplied at encrypt\n * time (or absent if none was supplied). A mismatch fails authentication.\n * @returns `Success` with the decrypted plaintext bytes, or `Failure` with\n * error context (including all authentication failures).\n */\n decryptBytes(\n key: Uint8Array,\n nonce: Uint8Array,\n ciphertext: Uint8Array,\n authTag: Uint8Array,\n aad?: Uint8Array\n ): Promise<Result<Uint8Array>>;\n\n /**\n * Generates a random 32-byte key suitable for AES-256.\n * @returns Success with generated key, or Failure with error\n */\n generateKey(): Promise<Result<Uint8Array>>;\n\n /**\n * Derives a key from a password using PBKDF2.\n * @param password - Password string\n * @param salt - Salt bytes (should be at least 16 bytes)\n * @param iterations - Number of iterations (recommend 100000+)\n * @returns Success with derived 32-byte key, or Failure with error\n */\n deriveKey(password: string, salt: Uint8Array, iterations: number): Promise<Result<Uint8Array>>;\n\n /**\n * Computes a SHA-256 hash of the given data.\n * @param data - UTF-8 string to hash\n * @returns Success with hex-encoded hash string, or Failure with error\n */\n sha256(data: string): Promise<Result<string>>;\n\n // ============================================================================\n // Platform Utility Methods\n // ============================================================================\n\n /**\n * Generates cryptographically secure random bytes.\n * @param length - Number of bytes to generate\n * @returns Success with random bytes, or Failure with error\n */\n generateRandomBytes(length: number): Result<Uint8Array>;\n\n /**\n * Generates a cryptographically random UUIDv4 using the provider's\n * underlying source of randomness. The default Node and browser\n * implementations delegate to `globalThis.crypto.randomUUID`;\n * deterministic providers (e.g. test stubs) may override to produce\n * reproducible values.\n * @returns Success with a canonical UUID, or Failure with error.\n */\n generateUuid(): Result<Uuid>;\n\n /**\n * Encodes binary data to base64 string.\n * @param data - Binary data to encode\n * @returns Base64-encoded string\n */\n toBase64(data: Uint8Array): string;\n\n /**\n * Decodes base64 string to binary data.\n * @param base64 - Base64-encoded string\n * @returns Success with decoded bytes, or Failure if invalid base64\n */\n fromBase64(base64: string): Result<Uint8Array>;\n\n // ============================================================================\n // Asymmetric Key Operations\n // ============================================================================\n\n /**\n * Generates a new asymmetric keypair for the requested algorithm.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} to use.\n * @param extractable - Whether the resulting `CryptoKey` objects may be exported.\n * Set `false` on backends that store `CryptoKey` references directly (e.g.\n * IndexedDB). Set `true` when the private key must round-trip through JWK or\n * PKCS#8 (e.g. encrypted-file backends).\n * @returns Success with the generated `CryptoKeyPair`, or Failure with error context.\n */\n generateKeyPair(algorithm: KeyPairAlgorithm, extractable: boolean): Promise<Result<CryptoKeyPair>>;\n\n /**\n * Exports the public half of a keypair as a JSON Web Key.\n * @param publicKey - The public `CryptoKey` to export. Must be an `extractable`\n * key generated for an asymmetric algorithm.\n * @returns Success with the JWK, or Failure with error context.\n */\n exportPublicKeyJwk(publicKey: CryptoKey): Promise<Result<JsonWebKey>>;\n\n /**\n * Re-imports a public-key JWK as a `CryptoKey` usable for verification or\n * encryption (depending on algorithm).\n * @param jwk - The JSON Web Key produced by {@link CryptoUtils.ICryptoProvider.exportPublicKeyJwk | exportPublicKeyJwk}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} the\n * key was generated for. Determines the import parameters and key usages.\n * @returns Success with the imported public `CryptoKey`, or Failure with error context.\n */\n importPublicKeyJwk(jwk: JsonWebKey, algorithm: KeyPairAlgorithm): Promise<Result<CryptoKey>>;\n\n /**\n * Exports a public `CryptoKey` as a DER-encoded SPKI (SubjectPublicKeyInfo) blob.\n * SPKI is the standard algorithm-agnostic format for public key storage and transport.\n * @param publicKey - The `CryptoKey` to export. Must have `key.type === 'public'`.\n * @returns `Success` with the raw SPKI bytes, or `Failure` with error context.\n */\n exportPublicKeySpki(publicKey: CryptoKey): Promise<Result<Uint8Array>>;\n\n /**\n * Imports a public key from a DER-encoded SPKI blob.\n * @param spkiBytes - The raw SPKI bytes produced by {@link CryptoUtils.ICryptoProvider.exportPublicKeySpki | exportPublicKeySpki}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} the key was generated for.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n */\n importPublicKeySpki(spkiBytes: Uint8Array, algorithm: KeyPairAlgorithm): Promise<Result<CryptoKey>>;\n\n /**\n * Wraps `plaintext` for delivery to the holder of the private key paired\n * with `recipientPublicKey`. Uses ECIES with ECDH P-256, HKDF-SHA256, and\n * AES-GCM-256.\n *\n * Generates a fresh ephemeral keypair per call; the ephemeral private key\n * is discarded after the shared-secret derive. Only the recipient (with the\n * matching private key) and the same HKDF parameters can recover\n * `plaintext`.\n *\n * Empty `plaintext` is permitted; the resulting wrap contains only the\n * 16-byte GCM authentication tag and round-trips back to an empty\n * `Uint8Array`.\n * @param plaintext - The bytes to wrap. Any length supported by AES-GCM\n * (in practice, well below 2^39 - 256 bits).\n * @param recipientPublicKey - The recipient's ECDH P-256 public `CryptoKey`.\n * Must have algorithm name `'ECDH'` and named curve `'P-256'`; mismatched\n * algorithm or curve yields a `Failure` with error context.\n * @param options - HKDF parameters; see {@link CryptoUtils.IWrapBytesOptions | IWrapBytesOptions}.\n * @returns `Success` with the wrapped payload, or `Failure` with error context.\n */\n wrapBytes(\n plaintext: Uint8Array,\n recipientPublicKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<IWrappedBytes>>;\n\n /**\n * Inverse of {@link CryptoUtils.ICryptoProvider.wrapBytes | wrapBytes}.\n * Recovers the original `plaintext` from a wrapped payload using the\n * recipient's private key.\n *\n * Returns a `Failure` (never throws) on any of:\n * - Tampered nonce or ciphertext (AES-GCM authentication fails)\n * - Wrong private key (different shared secret derives a different wrap key)\n * - Wrong HKDF parameters (different wrap key)\n * - Malformed `ephemeralPublicKey` JWK\n * - Malformed base64 in `nonce` or `ciphertext`\n * @param wrapped - The wrapped payload produced by `wrapBytes`.\n * @param recipientPrivateKey - The recipient's ECDH P-256 private\n * `CryptoKey`. Must have algorithm name `'ECDH'` and named curve `'P-256'`,\n * and key usages including `'deriveKey'` or `'deriveBits'`.\n * @param options - The same HKDF parameters used at wrap time.\n * @returns `Success` with the original `plaintext`, or `Failure` with error context.\n */\n unwrapBytes(\n wrapped: IWrappedBytes,\n recipientPrivateKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<Uint8Array>>;\n\n // ============================================================================\n // Signing Operations\n // ============================================================================\n\n /**\n * Signs `data` with `privateKey` using the algorithm inferred from the key.\n * Delegates to `crypto.subtle.sign`; the algorithm is derived from\n * `privateKey.algorithm.name` — ECDSA keys are augmented with\n * `hash: 'SHA-256'` at sign time (the hash is not stored in the key);\n * all other algorithm names are passed through as-is.\n * Intended for Ed25519 and ECDSA-P256 asymmetric private keys; for\n * HMAC-SHA256 authentication codes use {@link ICryptoProvider.hmacSha256} instead.\n * @param privateKey - A `CryptoKey` with `'sign'` usage (e.g. generated by\n * {@link CryptoUtils.ICryptoProvider.generateKeyPair | generateKeyPair} with\n * `'ecdsa-p256'` or `'ed25519'`).\n * @param data - The bytes to sign.\n * @returns `Success` with the raw signature bytes, or `Failure` with error context.\n */\n sign(privateKey: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>>;\n\n /**\n * Verifies a signature produced by {@link ICryptoProvider.sign}.\n * Delegates to `crypto.subtle.verify`; the algorithm is derived from\n * `publicKey.algorithm.name` — ECDSA keys are augmented with\n * `hash: 'SHA-256'`; all other algorithm names are passed through as-is.\n * Intended for Ed25519 and ECDSA-P256 asymmetric public keys; for\n * HMAC-SHA256 verification use {@link ICryptoProvider.verifyHmacSha256} instead.\n * @param publicKey - A `CryptoKey` with `'verify'` usage (e.g. the public\n * half of a keypair generated by\n * {@link CryptoUtils.ICryptoProvider.generateKeyPair | generateKeyPair} with\n * `'ecdsa-p256'` or `'ed25519'`).\n * @param signature - The raw signature bytes produced by `sign`.\n * @param data - The original data that was signed.\n * @returns `Success` with `true` if the signature is valid, `false` if it is\n * not, or `Failure` with error context if the operation itself failed.\n */\n verify(publicKey: CryptoKey, signature: Uint8Array, data: Uint8Array): Promise<Result<boolean>>;\n\n /**\n * Compares two byte arrays in constant time.\n *\n * The comparison visits all bytes of `a` and `b` regardless of where they\n * diverge, accumulating XOR differences with bitwise-OR. No early-return is\n * possible once the length check passes, making timing independent of the\n * byte values. This prevents timing side-channels when comparing MAC outputs,\n * signed-token bytes, or any secret-derived byte sequences.\n *\n * Returns `false` immediately (before the loop) when `a.length !== b.length`;\n * the length mismatch itself is not secret in normal use.\n * @param a - First byte array.\n * @param b - Second byte array.\n * @returns `true` if the arrays have the same length and identical contents,\n * `false` otherwise.\n */\n timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean;\n\n /**\n * Computes an HMAC-SHA256 authentication code for `data` using `key`.\n *\n * The key must be a `CryptoKey` with `'sign'` usage and algorithm name\n * `'HMAC'` (e.g. derived via PBKDF2 or imported with\n * `crypto.subtle.importKey`). Use {@link ICryptoProvider.verifyHmacSha256}\n * for constant-time verification of the output.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param data - The bytes to authenticate.\n * @returns `Success` with the 32-byte MAC, or `Failure` with error context.\n */\n hmacSha256(key: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>>;\n\n /**\n * Verifies an HMAC-SHA256 authentication code in constant time.\n *\n * Computes the expected MAC over `data` with `key`, then compares it to\n * `signature` using {@link ICryptoProvider.timingSafeEqual} so that\n * mismatches do not leak information through timing.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param signature - The MAC bytes to verify (typically 32 bytes).\n * @param data - The original data that was authenticated.\n * @returns `Success` with `true` if the MAC is valid, `false` if it is not,\n * or `Failure` with error context if the MAC computation itself failed.\n */\n verifyHmacSha256(key: CryptoKey, signature: Uint8Array, data: Uint8Array): Promise<Result<boolean>>;\n}\n\n// ============================================================================\n// Encryption Provider Interface\n// ============================================================================\n\n/**\n * High-level interface for encrypting JSON content by secret name.\n *\n * This abstraction unifies two common encryption workflows:\n * - **KeyStore**: looks up the named secret and crypto provider from the vault\n * - **DirectEncryptionProvider**: uses a pre-supplied key and crypto provider,\n * optionally bound to a specific secret name for safety\n *\n * Callers that need to encrypt (e.g. `EditableCollection.save()`) depend on\n * this interface rather than on `KeyStore` directly, allowing mix-and-match.\n *\n * @public\n */\nexport interface IEncryptionProvider {\n /**\n * Encrypts JSON content under a named secret.\n *\n * @param secretName - Name of the secret to encrypt with\n * @param content - JSON-safe content to encrypt\n * @param metadata - Optional unencrypted metadata to include in the encrypted file\n * @returns Success with encrypted file structure, or Failure with error context\n */\n encryptByName<TMetadata = JsonValue>(\n secretName: string,\n content: JsonValue,\n metadata?: TMetadata\n ): Promise<Result<IEncryptedFile<TMetadata>>>;\n}\n\n// ============================================================================\n// Encryption Configuration\n// ============================================================================\n\n/**\n * Behavior when an encrypted file cannot be decrypted.\n * @public\n */\nexport type EncryptedFileErrorMode =\n | 'fail' // Return failure, abort loading\n | 'skip' // Skip file silently, continue loading others\n | 'warn'; // Log warning, skip file, continue loading\n\n/**\n * Function type for dynamic secret retrieval.\n * @public\n */\nexport type SecretProvider = (secretName: string) => Promise<Result<Uint8Array>>;\n\n/**\n * Configuration for encrypted file handling during loading.\n * @public\n */\nexport interface IEncryptionConfig {\n /**\n * Named secrets available for decryption.\n */\n readonly secrets?: ReadonlyArray<INamedSecret>;\n\n /**\n * Alternative: dynamic secret provider function.\n * Called when a secret is not found in the secrets array.\n */\n readonly secretProvider?: SecretProvider;\n\n /**\n * Crypto provider implementation (Node.js or browser).\n */\n readonly cryptoProvider: ICryptoProvider;\n\n /**\n * Behavior when decryption key is missing (default: 'fail').\n */\n readonly onMissingKey?: EncryptedFileErrorMode;\n\n /**\n * Behavior when decryption fails (default: 'fail').\n */\n readonly onDecryptionError?: EncryptedFileErrorMode;\n}\n\n// ============================================================================\n// Detection Helper\n// ============================================================================\n\n/**\n * Checks if a JSON object appears to be an encrypted file.\n * Uses the format field as a discriminator.\n * @param json - JSON object to check\n * @returns true if the object has the encrypted file format field\n * @public\n */\nexport function isEncryptedFile(json: unknown): boolean {\n if (typeof json !== 'object' || json === null) {\n return false;\n }\n const obj = json as Record<string, unknown>;\n return obj.format === Constants.ENCRYPTED_FILE_FORMAT;\n}\n"]}
@@ -81,6 +81,72 @@ export class NodeCryptoProvider {
81
81
  return decrypted.toString('utf8');
82
82
  }).withErrorFormat((e) => `Decryption failed: ${e}`);
83
83
  }
84
+ /**
85
+ * Encrypts raw bytes using AES-256-GCM with a caller-supplied nonce and
86
+ * optional AAD. See {@link CryptoUtils.ICryptoProvider.encryptBytes | ICryptoProvider.encryptBytes}
87
+ * — in particular the caller's responsibility to use a unique `nonce` per
88
+ * message under a given `key`.
89
+ * @param key - 32-byte AES-256 key.
90
+ * @param nonce - 12-byte GCM nonce (must be unique per message under `key`).
91
+ * @param plaintext - The bytes to encrypt (empty permitted).
92
+ * @param aad - Optional additional authenticated data bound into the tag.
93
+ * @returns `Success` with the ciphertext and 16-byte auth tag, or `Failure` with an error.
94
+ */
95
+ async encryptBytes(key, nonce, plaintext, aad) {
96
+ if (key.length !== Constants.AES_256_KEY_SIZE) {
97
+ return fail(`encryptBytes: key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);
98
+ }
99
+ if (nonce.length !== Constants.GCM_IV_SIZE) {
100
+ return fail(`encryptBytes: nonce must be ${Constants.GCM_IV_SIZE} bytes, got ${nonce.length}`);
101
+ }
102
+ return captureResult(() => {
103
+ const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(key), Buffer.from(nonce), {
104
+ authTagLength: Constants.GCM_AUTH_TAG_SIZE
105
+ });
106
+ if (aad !== undefined) {
107
+ cipher.setAAD(Buffer.from(aad));
108
+ }
109
+ const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
110
+ const authTag = cipher.getAuthTag();
111
+ return {
112
+ ciphertext: new Uint8Array(encrypted),
113
+ authTag: new Uint8Array(authTag)
114
+ };
115
+ });
116
+ }
117
+ /**
118
+ * Decrypts raw bytes produced by {@link NodeCryptoProvider.encryptBytes} using
119
+ * AES-256-GCM. See {@link CryptoUtils.ICryptoProvider.decryptBytes | ICryptoProvider.decryptBytes}.
120
+ * Fails (never throws) on any authentication failure, including a mismatched `aad`.
121
+ * @param key - 32-byte AES-256 key.
122
+ * @param nonce - 12-byte GCM nonce (the same one used to encrypt).
123
+ * @param ciphertext - The ciphertext from the `encryptBytes` result.
124
+ * @param authTag - The 16-byte GCM auth tag from the `encryptBytes` result.
125
+ * @param aad - The identical AAD supplied at encrypt time (or absent).
126
+ * @returns `Success` with the decrypted plaintext bytes, or `Failure` with an error.
127
+ */
128
+ async decryptBytes(key, nonce, ciphertext, authTag, aad) {
129
+ if (key.length !== Constants.AES_256_KEY_SIZE) {
130
+ return fail(`decryptBytes: key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);
131
+ }
132
+ if (nonce.length !== Constants.GCM_IV_SIZE) {
133
+ return fail(`decryptBytes: nonce must be ${Constants.GCM_IV_SIZE} bytes, got ${nonce.length}`);
134
+ }
135
+ if (authTag.length !== Constants.GCM_AUTH_TAG_SIZE) {
136
+ return fail(`decryptBytes: auth tag must be ${Constants.GCM_AUTH_TAG_SIZE} bytes, got ${authTag.length}`);
137
+ }
138
+ return captureResult(() => {
139
+ const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(key), Buffer.from(nonce), {
140
+ authTagLength: Constants.GCM_AUTH_TAG_SIZE
141
+ });
142
+ if (aad !== undefined) {
143
+ decipher.setAAD(Buffer.from(aad));
144
+ }
145
+ decipher.setAuthTag(Buffer.from(authTag));
146
+ const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
147
+ return new Uint8Array(decrypted);
148
+ }).withErrorFormat((e) => `decryptBytes failed: ${e}`);
149
+ }
84
150
  /**
85
151
  * Generates a random 32-byte key suitable for AES-256.
86
152
  * @returns `Success` with generated key, or `Failure` with an error.
@@ -1 +1 @@
1
- {"version":3,"file":"nodeCryptoProvider.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/nodeCryptoProvider.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport * as crypto from 'crypto';\nimport {\n captureAsyncResult,\n captureResult,\n fail,\n Failure,\n generateUuid,\n Result,\n succeed,\n Success,\n Uuid\n} from '@fgv/ts-utils';\nimport * as Constants from './constants';\nimport { keyPairAlgorithmParams } from './keyPairAlgorithmParams';\nimport {\n ICryptoProvider,\n IEncryptionResult,\n IWrapBytesOptions,\n IWrappedBytes,\n KeyPairAlgorithm\n} from './model';\n\n/**\n * Node.js implementation of {@link CryptoUtils.ICryptoProvider} using the built-in crypto module.\n * Uses AES-256-GCM for authenticated encryption.\n * @public\n */\nexport class NodeCryptoProvider implements ICryptoProvider {\n /**\n * Encrypts plaintext using AES-256-GCM.\n * @param plaintext - UTF-8 string to encrypt\n * @param key - 32-byte encryption key\n * @returns `Success` with encryption result, or `Failure` with an error.\n */\n public async encrypt(plaintext: string, key: Uint8Array): Promise<Result<IEncryptionResult>> {\n return captureResult(() => {\n if (key.length !== Constants.AES_256_KEY_SIZE) {\n throw new Error(`Key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);\n }\n\n // Generate random IV\n const iv = crypto.randomBytes(Constants.GCM_IV_SIZE);\n\n // Create cipher\n const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\n\n // Encrypt\n const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n\n // Get auth tag\n const authTag = cipher.getAuthTag();\n\n return {\n iv: new Uint8Array(iv),\n authTag: new Uint8Array(authTag),\n encryptedData: new Uint8Array(encrypted)\n };\n });\n }\n\n /**\n * Decrypts ciphertext using AES-256-GCM.\n * @param encryptedData - Encrypted bytes\n * @param key - 32-byte decryption key\n * @param iv - Initialization vector (12 bytes)\n * @param authTag - GCM authentication tag (16 bytes)\n * @returns `Success` with decrypted UTF-8 string, or `Failure` with an error.\n */\n public async decrypt(\n encryptedData: Uint8Array,\n key: Uint8Array,\n iv: Uint8Array,\n authTag: Uint8Array\n ): Promise<Result<string>> {\n if (key.length !== Constants.AES_256_KEY_SIZE) {\n return fail(`Key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);\n }\n if (iv.length !== Constants.GCM_IV_SIZE) {\n return fail(`IV must be ${Constants.GCM_IV_SIZE} bytes, got ${iv.length}`);\n }\n if (authTag.length !== Constants.GCM_AUTH_TAG_SIZE) {\n return fail(`Auth tag must be ${Constants.GCM_AUTH_TAG_SIZE} bytes, got ${authTag.length}`);\n }\n\n return captureResult(() => {\n // Create decipher\n const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(key), Buffer.from(iv));\n\n // Set auth tag\n decipher.setAuthTag(Buffer.from(authTag));\n\n // Decrypt\n const decrypted = Buffer.concat([decipher.update(Buffer.from(encryptedData)), decipher.final()]);\n\n return decrypted.toString('utf8');\n }).withErrorFormat((e) => `Decryption failed: ${e}`);\n }\n\n /**\n * Generates a random 32-byte key suitable for AES-256.\n * @returns `Success` with generated key, or `Failure` with an error.\n */\n public async generateKey(): Promise<Result<Uint8Array>> {\n return captureResult(() => {\n const key = crypto.randomBytes(Constants.AES_256_KEY_SIZE);\n return new Uint8Array(key);\n });\n }\n\n /**\n * Derives a key from a password using PBKDF2.\n * @param password - Password string\n * @param salt - Salt bytes (should be at least 16 bytes)\n * @param iterations - Number of iterations (recommend 100000+)\n * @returns `Success` with derived 32-byte key, or `Failure` with an error.\n */\n public async deriveKey(\n password: string,\n salt: Uint8Array,\n iterations: number\n ): Promise<Result<Uint8Array>> {\n if (iterations < 1) {\n return fail('Iterations must be at least 1');\n }\n if (salt.length < 8) {\n return fail('Salt should be at least 8 bytes');\n }\n\n return new Promise((resolve) => {\n crypto.pbkdf2(\n password,\n Buffer.from(salt),\n iterations,\n Constants.AES_256_KEY_SIZE,\n 'sha256',\n (err, derivedKey) => {\n /* c8 ignore next 3 - PBKDF2 internal errors are hard to trigger with valid parameters */\n if (err) {\n resolve(fail(`Key derivation failed: ${err.message}`));\n } else {\n resolve(succeed(new Uint8Array(derivedKey)));\n }\n }\n );\n });\n }\n\n /**\n * Computes a SHA-256 hash of the given data.\n * @param data - UTF-8 string to hash\n * @returns `Success` with hex-encoded hash string, or `Failure` with an error.\n */\n public async sha256(data: string): Promise<Result<string>> {\n return captureResult(() => {\n const hash = crypto.createHash('sha256');\n hash.update(data, 'utf8');\n return hash.digest('hex');\n });\n }\n\n // ============================================================================\n // Platform Utility Methods\n // ============================================================================\n\n /**\n * Generates cryptographically secure random bytes.\n * @param length - Number of bytes to generate\n * @returns Success with random bytes, or Failure with error\n */\n public generateRandomBytes(length: number): Result<Uint8Array> {\n if (length < 1) {\n return Failure.with('Length must be at least 1');\n }\n return captureResult(() => new Uint8Array(crypto.randomBytes(length)));\n }\n\n /**\n * Generates a cryptographically random UUIDv4 via the platform Web Crypto API.\n * @returns `Success` with the generated UUID, or `Failure` if the runtime\n * does not expose `globalThis.crypto.randomUUID`.\n */\n public generateUuid(): Result<Uuid> {\n return captureResult(() => generateUuid());\n }\n\n /**\n * Encodes binary data to base64 string.\n * @param data - Binary data to encode\n * @returns Base64-encoded string\n */\n public toBase64(data: Uint8Array): string {\n return Buffer.from(data).toString('base64');\n }\n\n /**\n * Decodes base64 string to binary data.\n * @param base64 - Base64-encoded string\n * @returns Success with decoded bytes, or Failure if invalid base64\n */\n public fromBase64(base64: string): Result<Uint8Array> {\n // Check for obviously invalid characters\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64)) {\n return Failure.with('Invalid base64 string');\n }\n return Success.with(new Uint8Array(Buffer.from(base64, 'base64')));\n }\n\n // ============================================================================\n // Asymmetric Key Operations\n // ============================================================================\n\n /**\n * Generates a new asymmetric keypair using Node's WebCrypto.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} to use.\n * @param extractable - Whether the resulting keys may be exported.\n * @returns `Success` with the generated `CryptoKeyPair`, or `Failure` with an error.\n */\n public async generateKeyPair(\n algorithm: KeyPairAlgorithm,\n extractable: boolean\n ): Promise<Result<CryptoKeyPair>> {\n const params = keyPairAlgorithmParams[algorithm];\n // Widening upcast to `AlgorithmIdentifier` steers TS to subtle.generateKey's\n // broad overload, which accepts the Ed25519 `{ name: 'Ed25519' }` shape and\n // returns `CryptoKey | CryptoKeyPair`. The narrowing back to `CryptoKeyPair`\n // is a runtime check via the `in` operator, not a type assertion.\n const result = await captureAsyncResult(async () => {\n const generated = await crypto.webcrypto.subtle.generateKey(\n params.generateKey as AlgorithmIdentifier,\n extractable,\n [...params.keyPairUsages]\n );\n if ('privateKey' in generated && 'publicKey' in generated) {\n return generated;\n }\n /* c8 ignore next - unreachable: every entry in keyPairAlgorithmParams produces a keypair */\n throw new Error(`${algorithm} unexpectedly produced a single CryptoKey`);\n });\n return result.withErrorFormat((e) => `Failed to generate ${algorithm} keypair: ${e}`);\n }\n\n /**\n * Exports a public `CryptoKey` as a JSON Web Key.\n * @remarks\n * Rejects non-public keys at runtime. WebCrypto's `exportKey('jwk', ...)`\n * does not enforce public-vs-private; without this guard a caller that\n * passed an extractable private key would receive its private fields\n * (`d`, `p`, `q`, ...) as JWK, defeating the method's name.\n * @param publicKey - Extractable public key to export.\n * @returns `Success` with the JWK, or `Failure` if not a public key or if export fails.\n */\n public async exportPublicKeyJwk(publicKey: CryptoKey): Promise<Result<JsonWebKey>> {\n if (publicKey.type !== 'public') {\n return fail(`exportPublicKeyJwk requires a public CryptoKey, got '${publicKey.type}'`);\n }\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.exportKey('jwk', publicKey));\n return result.withErrorFormat((e) => `Failed to export public key as JWK: ${e}`);\n }\n\n /**\n * Imports a public-key JWK as a `CryptoKey` for the requested algorithm.\n * @param jwk - The JSON Web Key produced by a prior export.\n * @param algorithm - The algorithm the key was generated for.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with an error.\n */\n public async importPublicKeyJwk(jwk: JsonWebKey, algorithm: KeyPairAlgorithm): Promise<Result<CryptoKey>> {\n const params = keyPairAlgorithmParams[algorithm];\n const result = await captureAsyncResult(() =>\n crypto.webcrypto.subtle.importKey('jwk', jwk, params.importPublicKey, true, params.publicKeyUsages)\n );\n return result.withErrorFormat((e) => `Failed to import ${algorithm} public key from JWK: ${e}`);\n }\n\n /**\n * Exports a public `CryptoKey` as a DER-encoded SPKI blob.\n * @param publicKey - The public `CryptoKey` to export.\n * @returns `Success` with the raw SPKI bytes, or `Failure` with error context.\n */\n public async exportPublicKeySpki(publicKey: CryptoKey): Promise<Result<Uint8Array>> {\n if (publicKey.type !== 'public') {\n return fail(`exportPublicKeySpki requires a public CryptoKey, got '${publicKey.type}'`);\n }\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.exportKey('spki', publicKey));\n return result\n .withErrorFormat((e) => `exportPublicKeySpki: failed to export key: ${e}`)\n .onSuccess((buf) => succeed(new Uint8Array(buf)));\n }\n\n /**\n * Imports a public key from a DER-encoded SPKI blob.\n * @param spkiBytes - The raw SPKI bytes.\n * @param algorithm - The algorithm the key was generated for.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n */\n public async importPublicKeySpki(\n spkiBytes: Uint8Array,\n algorithm: KeyPairAlgorithm\n ): Promise<Result<CryptoKey>> {\n const params = keyPairAlgorithmParams[algorithm];\n const result = await captureAsyncResult(() =>\n crypto.webcrypto.subtle.importKey(\n 'spki',\n spkiBytes,\n params.importPublicKey as AlgorithmIdentifier,\n true,\n [...params.publicKeyUsages]\n )\n );\n return result.withErrorFormat(\n (e) => `importPublicKeySpki: failed to import ${algorithm} public key from SPKI: ${e}`\n );\n }\n\n /**\n * Signs `data` with `privateKey` using the algorithm inferred from the key.\n * @param privateKey - A signing `CryptoKey` (`'ecdsa-p256'` or `'ed25519'`).\n * @param data - The bytes to sign.\n * @returns `Success` with the raw signature bytes, or `Failure` with error context.\n */\n public async sign(privateKey: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>> {\n const algorithm = signAlgorithmFromKey(privateKey);\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.sign(algorithm, privateKey, data));\n return result\n .withErrorFormat((e) => `sign failed: ${e}`)\n .onSuccess((buf) => succeed(new Uint8Array(buf)));\n }\n\n /**\n * Verifies a signature produced by {@link NodeCryptoProvider.sign}.\n * @param publicKey - A verify `CryptoKey` (`'ecdsa-p256'` or `'ed25519'`).\n * @param signature - The raw signature bytes.\n * @param data - The original data that was signed.\n * @returns `Success` with `true` if valid, `false` if not, or `Failure` with error context.\n */\n public async verify(\n publicKey: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array\n ): Promise<Result<boolean>> {\n const algorithm = signAlgorithmFromKey(publicKey);\n const result = await captureAsyncResult(() =>\n crypto.webcrypto.subtle.verify(algorithm, publicKey, signature, data)\n );\n return result.withErrorFormat((e) => `verify failed: ${e}`);\n }\n\n /**\n * Compares two byte arrays in constant time using Node's native\n * `crypto.timingSafeEqual`. Returns `false` for mismatched lengths\n * rather than throwing (Node's native throws on length mismatch).\n * @param a - First byte array.\n * @param b - Second byte array.\n * @returns `true` if lengths match and all bytes are equal, `false` otherwise.\n */\n public timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n return crypto.timingSafeEqual(a, b);\n }\n\n /**\n * Computes an HMAC-SHA256 MAC for `data` using `key`.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param data - The bytes to authenticate.\n * @returns `Success` with the 32-byte MAC, or `Failure` with error context.\n */\n public async hmacSha256(key: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>> {\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.sign({ name: 'HMAC' }, key, data));\n return result\n .withErrorFormat((e) => `hmacSha256 failed: ${e}`)\n .onSuccess((buf) => succeed(new Uint8Array(buf)));\n }\n\n /**\n * Verifies an HMAC-SHA256 MAC in constant time.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param signature - The MAC bytes to verify.\n * @param data - The original data that was authenticated.\n * @returns `Success` with `true` if valid, `false` if not, or `Failure` with error context.\n */\n public async verifyHmacSha256(\n key: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array\n ): Promise<Result<boolean>> {\n return (await this.hmacSha256(key, data))\n .withErrorFormat((e) => `verifyHmacSha256 failed: ${e}`)\n .onSuccess((mac) => succeed(this.timingSafeEqual(mac, signature)));\n }\n\n /**\n * Wraps `plaintext` for the holder of `recipientPublicKey` using\n * ECIES (ECDH P-256 + HKDF-SHA256 + AES-GCM-256). See\n * {@link CryptoUtils.ICryptoProvider.wrapBytes | ICryptoProvider.wrapBytes}.\n * @param plaintext - The bytes to wrap.\n * @param recipientPublicKey - The recipient's ECDH P-256 public `CryptoKey`.\n * @param options - HKDF salt and info; see {@link CryptoUtils.IWrapBytesOptions | IWrapBytesOptions}.\n * @returns `Success` with the wrapped payload, or `Failure` with an error.\n */\n public async wrapBytes(\n plaintext: Uint8Array,\n recipientPublicKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<IWrappedBytes>> {\n const recipientCheck = checkEcdhP256(recipientPublicKey, 'public', 'recipient public key');\n if (recipientCheck.isFailure()) {\n return fail(`wrapBytes failed: ${recipientCheck.message}`);\n }\n const subtle = crypto.webcrypto.subtle;\n const result = await captureAsyncResult(async () => {\n const ephemeral = (await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, [\n 'deriveKey'\n ])) as CryptoKeyPair;\n const hkdfBase = await subtle.deriveKey(\n { name: 'ECDH', public: recipientPublicKey },\n ephemeral.privateKey,\n { name: 'HKDF' },\n false,\n ['deriveKey']\n );\n const wrapKey = await subtle.deriveKey(\n { name: 'HKDF', salt: options.salt, info: options.info, hash: 'SHA-256' },\n hkdfBase,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt']\n );\n const nonce = crypto.randomBytes(Constants.GCM_IV_SIZE);\n const ctBuf = await subtle.encrypt({ name: 'AES-GCM', iv: nonce }, wrapKey, plaintext);\n const ephemeralPublicKey = await subtle.exportKey('jwk', ephemeral.publicKey);\n return {\n ephemeralPublicKey,\n nonce: this.toBase64(nonce),\n ciphertext: this.toBase64(new Uint8Array(ctBuf))\n };\n });\n return result.withErrorFormat((e) => `wrapBytes failed: ${e}`);\n }\n\n /**\n * Unwraps a payload produced by `wrapBytes` using the recipient's private\n * key. See {@link CryptoUtils.ICryptoProvider.unwrapBytes | ICryptoProvider.unwrapBytes}.\n * @param wrapped - The wrapped payload.\n * @param recipientPrivateKey - The recipient's ECDH P-256 private `CryptoKey`.\n * @param options - HKDF salt and info matching the wrap call.\n * @returns `Success` with the original `plaintext`, or `Failure` with an error.\n */\n public async unwrapBytes(\n wrapped: IWrappedBytes,\n recipientPrivateKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<Uint8Array>> {\n const recipientCheck = checkEcdhP256(recipientPrivateKey, 'private', 'recipient private key');\n if (recipientCheck.isFailure()) {\n return fail(`unwrapBytes failed: ${recipientCheck.message}`);\n }\n const nonceResult = this.fromBase64(wrapped.nonce);\n if (nonceResult.isFailure()) {\n return fail(`unwrapBytes failed: nonce: ${nonceResult.message}`);\n }\n if (nonceResult.value.length !== Constants.GCM_IV_SIZE) {\n return fail(\n `unwrapBytes failed: nonce must be ${Constants.GCM_IV_SIZE} bytes (got ${nonceResult.value.length})`\n );\n }\n const ciphertextResult = this.fromBase64(wrapped.ciphertext);\n if (ciphertextResult.isFailure()) {\n return fail(`unwrapBytes failed: ciphertext: ${ciphertextResult.message}`);\n }\n if (ciphertextResult.value.length < Constants.GCM_AUTH_TAG_SIZE) {\n return fail(\n `unwrapBytes failed: ciphertext must be at least ${Constants.GCM_AUTH_TAG_SIZE} bytes (got ${ciphertextResult.value.length})`\n );\n }\n const subtle = crypto.webcrypto.subtle;\n const result = await captureAsyncResult(async () => {\n const ephemeralPub = await subtle.importKey(\n 'jwk',\n wrapped.ephemeralPublicKey,\n { name: 'ECDH', namedCurve: 'P-256' },\n false,\n []\n );\n const hkdfBase = await subtle.deriveKey(\n { name: 'ECDH', public: ephemeralPub },\n recipientPrivateKey,\n { name: 'HKDF' },\n false,\n ['deriveKey']\n );\n const wrapKey = await subtle.deriveKey(\n { name: 'HKDF', salt: options.salt, info: options.info, hash: 'SHA-256' },\n hkdfBase,\n { name: 'AES-GCM', length: 256 },\n false,\n ['decrypt']\n );\n const ptBuf = await subtle.decrypt(\n { name: 'AES-GCM', iv: nonceResult.value },\n wrapKey,\n ciphertextResult.value\n );\n return new Uint8Array(ptBuf);\n });\n return result.withErrorFormat((e) => `unwrapBytes failed: ${e}`);\n }\n}\n\n/**\n * Derives the algorithm identifier needed by `crypto.subtle.sign/verify`\n * from the key's embedded `algorithm` property. ECDSA requires an explicit\n * `hash` parameter that is not stored on the key object itself; all other\n * supported signing algorithms (`Ed25519`) use the key algorithm as-is.\n */\nfunction signAlgorithmFromKey(key: CryptoKey): AlgorithmIdentifier | EcdsaParams {\n if (key.algorithm.name === 'ECDSA') {\n return { name: 'ECDSA', hash: 'SHA-256' };\n }\n return key.algorithm as AlgorithmIdentifier;\n}\n\n/**\n * Verifies that `key` is an ECDH P-256 `CryptoKey` of the expected `keyType`\n * (public or private). Used by the wrap/unwrap methods to surface a clean\n * `Failure` instead of letting the WebCrypto deriveKey call throw a less\n * informative error later in the pipeline. Key usages are intentionally not\n * checked here: WebCrypto already produces a specific error if `deriveKey` is\n * not in `usages`, and `deriveBits` is an equally valid alternative usage that\n * an explicit check would have to track.\n * @param key - The CryptoKey to validate.\n * @param keyType - The required `key.type` ('public' for wrap, 'private' for unwrap).\n * @param label - Human-readable role label included in the failure message.\n * @returns `Success` with the key (unchanged) when the algorithm, curve, and\n * type all match; otherwise `Failure` with `<label> must be ECDH P-256 (...)`.\n */\nfunction checkEcdhP256(key: CryptoKey, keyType: 'public' | 'private', label: string): Result<CryptoKey> {\n if (key.algorithm.name !== 'ECDH') {\n return fail(`${label} must be ECDH P-256 (got algorithm '${key.algorithm.name}')`);\n }\n const namedCurve = (key.algorithm as EcKeyAlgorithm).namedCurve;\n if (namedCurve !== 'P-256') {\n return fail(`${label} must be ECDH P-256 (got curve '${namedCurve}')`);\n }\n if (key.type !== keyType) {\n return fail(`${label} must be a ${keyType} CryptoKey (got '${key.type}')`);\n }\n return succeed(key);\n}\n\n/**\n * Singleton instance of {@link CryptoUtils.NodeCryptoProvider}.\n * @public\n */\nexport const nodeCryptoProvider: NodeCryptoProvider = new NodeCryptoProvider();\n"]}
1
+ {"version":3,"file":"nodeCryptoProvider.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/nodeCryptoProvider.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2024 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport * as crypto from 'crypto';\nimport {\n captureAsyncResult,\n captureResult,\n fail,\n Failure,\n generateUuid,\n Result,\n succeed,\n Success,\n Uuid\n} from '@fgv/ts-utils';\nimport * as Constants from './constants';\nimport { keyPairAlgorithmParams } from './keyPairAlgorithmParams';\nimport {\n ICryptoProvider,\n IEncryptBytesResult,\n IEncryptionResult,\n IWrapBytesOptions,\n IWrappedBytes,\n KeyPairAlgorithm\n} from './model';\n\n/**\n * Node.js implementation of {@link CryptoUtils.ICryptoProvider} using the built-in crypto module.\n * Uses AES-256-GCM for authenticated encryption.\n * @public\n */\nexport class NodeCryptoProvider implements ICryptoProvider {\n /**\n * Encrypts plaintext using AES-256-GCM.\n * @param plaintext - UTF-8 string to encrypt\n * @param key - 32-byte encryption key\n * @returns `Success` with encryption result, or `Failure` with an error.\n */\n public async encrypt(plaintext: string, key: Uint8Array): Promise<Result<IEncryptionResult>> {\n return captureResult(() => {\n if (key.length !== Constants.AES_256_KEY_SIZE) {\n throw new Error(`Key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);\n }\n\n // Generate random IV\n const iv = crypto.randomBytes(Constants.GCM_IV_SIZE);\n\n // Create cipher\n const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\n\n // Encrypt\n const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n\n // Get auth tag\n const authTag = cipher.getAuthTag();\n\n return {\n iv: new Uint8Array(iv),\n authTag: new Uint8Array(authTag),\n encryptedData: new Uint8Array(encrypted)\n };\n });\n }\n\n /**\n * Decrypts ciphertext using AES-256-GCM.\n * @param encryptedData - Encrypted bytes\n * @param key - 32-byte decryption key\n * @param iv - Initialization vector (12 bytes)\n * @param authTag - GCM authentication tag (16 bytes)\n * @returns `Success` with decrypted UTF-8 string, or `Failure` with an error.\n */\n public async decrypt(\n encryptedData: Uint8Array,\n key: Uint8Array,\n iv: Uint8Array,\n authTag: Uint8Array\n ): Promise<Result<string>> {\n if (key.length !== Constants.AES_256_KEY_SIZE) {\n return fail(`Key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);\n }\n if (iv.length !== Constants.GCM_IV_SIZE) {\n return fail(`IV must be ${Constants.GCM_IV_SIZE} bytes, got ${iv.length}`);\n }\n if (authTag.length !== Constants.GCM_AUTH_TAG_SIZE) {\n return fail(`Auth tag must be ${Constants.GCM_AUTH_TAG_SIZE} bytes, got ${authTag.length}`);\n }\n\n return captureResult(() => {\n // Create decipher\n const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(key), Buffer.from(iv));\n\n // Set auth tag\n decipher.setAuthTag(Buffer.from(authTag));\n\n // Decrypt\n const decrypted = Buffer.concat([decipher.update(Buffer.from(encryptedData)), decipher.final()]);\n\n return decrypted.toString('utf8');\n }).withErrorFormat((e) => `Decryption failed: ${e}`);\n }\n\n /**\n * Encrypts raw bytes using AES-256-GCM with a caller-supplied nonce and\n * optional AAD. See {@link CryptoUtils.ICryptoProvider.encryptBytes | ICryptoProvider.encryptBytes}\n * — in particular the caller's responsibility to use a unique `nonce` per\n * message under a given `key`.\n * @param key - 32-byte AES-256 key.\n * @param nonce - 12-byte GCM nonce (must be unique per message under `key`).\n * @param plaintext - The bytes to encrypt (empty permitted).\n * @param aad - Optional additional authenticated data bound into the tag.\n * @returns `Success` with the ciphertext and 16-byte auth tag, or `Failure` with an error.\n */\n public async encryptBytes(\n key: Uint8Array,\n nonce: Uint8Array,\n plaintext: Uint8Array,\n aad?: Uint8Array\n ): Promise<Result<IEncryptBytesResult>> {\n if (key.length !== Constants.AES_256_KEY_SIZE) {\n return fail(`encryptBytes: key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);\n }\n if (nonce.length !== Constants.GCM_IV_SIZE) {\n return fail(`encryptBytes: nonce must be ${Constants.GCM_IV_SIZE} bytes, got ${nonce.length}`);\n }\n\n return captureResult(() => {\n const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(key), Buffer.from(nonce), {\n authTagLength: Constants.GCM_AUTH_TAG_SIZE\n });\n if (aad !== undefined) {\n cipher.setAAD(Buffer.from(aad));\n }\n const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);\n const authTag = cipher.getAuthTag();\n return {\n ciphertext: new Uint8Array(encrypted),\n authTag: new Uint8Array(authTag)\n };\n });\n }\n\n /**\n * Decrypts raw bytes produced by {@link NodeCryptoProvider.encryptBytes} using\n * AES-256-GCM. See {@link CryptoUtils.ICryptoProvider.decryptBytes | ICryptoProvider.decryptBytes}.\n * Fails (never throws) on any authentication failure, including a mismatched `aad`.\n * @param key - 32-byte AES-256 key.\n * @param nonce - 12-byte GCM nonce (the same one used to encrypt).\n * @param ciphertext - The ciphertext from the `encryptBytes` result.\n * @param authTag - The 16-byte GCM auth tag from the `encryptBytes` result.\n * @param aad - The identical AAD supplied at encrypt time (or absent).\n * @returns `Success` with the decrypted plaintext bytes, or `Failure` with an error.\n */\n public async decryptBytes(\n key: Uint8Array,\n nonce: Uint8Array,\n ciphertext: Uint8Array,\n authTag: Uint8Array,\n aad?: Uint8Array\n ): Promise<Result<Uint8Array>> {\n if (key.length !== Constants.AES_256_KEY_SIZE) {\n return fail(`decryptBytes: key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);\n }\n if (nonce.length !== Constants.GCM_IV_SIZE) {\n return fail(`decryptBytes: nonce must be ${Constants.GCM_IV_SIZE} bytes, got ${nonce.length}`);\n }\n if (authTag.length !== Constants.GCM_AUTH_TAG_SIZE) {\n return fail(\n `decryptBytes: auth tag must be ${Constants.GCM_AUTH_TAG_SIZE} bytes, got ${authTag.length}`\n );\n }\n\n return captureResult(() => {\n const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(key), Buffer.from(nonce), {\n authTagLength: Constants.GCM_AUTH_TAG_SIZE\n });\n if (aad !== undefined) {\n decipher.setAAD(Buffer.from(aad));\n }\n decipher.setAuthTag(Buffer.from(authTag));\n const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);\n return new Uint8Array(decrypted);\n }).withErrorFormat((e) => `decryptBytes failed: ${e}`);\n }\n\n /**\n * Generates a random 32-byte key suitable for AES-256.\n * @returns `Success` with generated key, or `Failure` with an error.\n */\n public async generateKey(): Promise<Result<Uint8Array>> {\n return captureResult(() => {\n const key = crypto.randomBytes(Constants.AES_256_KEY_SIZE);\n return new Uint8Array(key);\n });\n }\n\n /**\n * Derives a key from a password using PBKDF2.\n * @param password - Password string\n * @param salt - Salt bytes (should be at least 16 bytes)\n * @param iterations - Number of iterations (recommend 100000+)\n * @returns `Success` with derived 32-byte key, or `Failure` with an error.\n */\n public async deriveKey(\n password: string,\n salt: Uint8Array,\n iterations: number\n ): Promise<Result<Uint8Array>> {\n if (iterations < 1) {\n return fail('Iterations must be at least 1');\n }\n if (salt.length < 8) {\n return fail('Salt should be at least 8 bytes');\n }\n\n return new Promise((resolve) => {\n crypto.pbkdf2(\n password,\n Buffer.from(salt),\n iterations,\n Constants.AES_256_KEY_SIZE,\n 'sha256',\n (err, derivedKey) => {\n /* c8 ignore next 3 - PBKDF2 internal errors are hard to trigger with valid parameters */\n if (err) {\n resolve(fail(`Key derivation failed: ${err.message}`));\n } else {\n resolve(succeed(new Uint8Array(derivedKey)));\n }\n }\n );\n });\n }\n\n /**\n * Computes a SHA-256 hash of the given data.\n * @param data - UTF-8 string to hash\n * @returns `Success` with hex-encoded hash string, or `Failure` with an error.\n */\n public async sha256(data: string): Promise<Result<string>> {\n return captureResult(() => {\n const hash = crypto.createHash('sha256');\n hash.update(data, 'utf8');\n return hash.digest('hex');\n });\n }\n\n // ============================================================================\n // Platform Utility Methods\n // ============================================================================\n\n /**\n * Generates cryptographically secure random bytes.\n * @param length - Number of bytes to generate\n * @returns Success with random bytes, or Failure with error\n */\n public generateRandomBytes(length: number): Result<Uint8Array> {\n if (length < 1) {\n return Failure.with('Length must be at least 1');\n }\n return captureResult(() => new Uint8Array(crypto.randomBytes(length)));\n }\n\n /**\n * Generates a cryptographically random UUIDv4 via the platform Web Crypto API.\n * @returns `Success` with the generated UUID, or `Failure` if the runtime\n * does not expose `globalThis.crypto.randomUUID`.\n */\n public generateUuid(): Result<Uuid> {\n return captureResult(() => generateUuid());\n }\n\n /**\n * Encodes binary data to base64 string.\n * @param data - Binary data to encode\n * @returns Base64-encoded string\n */\n public toBase64(data: Uint8Array): string {\n return Buffer.from(data).toString('base64');\n }\n\n /**\n * Decodes base64 string to binary data.\n * @param base64 - Base64-encoded string\n * @returns Success with decoded bytes, or Failure if invalid base64\n */\n public fromBase64(base64: string): Result<Uint8Array> {\n // Check for obviously invalid characters\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64)) {\n return Failure.with('Invalid base64 string');\n }\n return Success.with(new Uint8Array(Buffer.from(base64, 'base64')));\n }\n\n // ============================================================================\n // Asymmetric Key Operations\n // ============================================================================\n\n /**\n * Generates a new asymmetric keypair using Node's WebCrypto.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm | algorithm} to use.\n * @param extractable - Whether the resulting keys may be exported.\n * @returns `Success` with the generated `CryptoKeyPair`, or `Failure` with an error.\n */\n public async generateKeyPair(\n algorithm: KeyPairAlgorithm,\n extractable: boolean\n ): Promise<Result<CryptoKeyPair>> {\n const params = keyPairAlgorithmParams[algorithm];\n // Widening upcast to `AlgorithmIdentifier` steers TS to subtle.generateKey's\n // broad overload, which accepts the Ed25519 `{ name: 'Ed25519' }` shape and\n // returns `CryptoKey | CryptoKeyPair`. The narrowing back to `CryptoKeyPair`\n // is a runtime check via the `in` operator, not a type assertion.\n const result = await captureAsyncResult(async () => {\n const generated = await crypto.webcrypto.subtle.generateKey(\n params.generateKey as AlgorithmIdentifier,\n extractable,\n [...params.keyPairUsages]\n );\n if ('privateKey' in generated && 'publicKey' in generated) {\n return generated;\n }\n /* c8 ignore next - unreachable: every entry in keyPairAlgorithmParams produces a keypair */\n throw new Error(`${algorithm} unexpectedly produced a single CryptoKey`);\n });\n return result.withErrorFormat((e) => `Failed to generate ${algorithm} keypair: ${e}`);\n }\n\n /**\n * Exports a public `CryptoKey` as a JSON Web Key.\n * @remarks\n * Rejects non-public keys at runtime. WebCrypto's `exportKey('jwk', ...)`\n * does not enforce public-vs-private; without this guard a caller that\n * passed an extractable private key would receive its private fields\n * (`d`, `p`, `q`, ...) as JWK, defeating the method's name.\n * @param publicKey - Extractable public key to export.\n * @returns `Success` with the JWK, or `Failure` if not a public key or if export fails.\n */\n public async exportPublicKeyJwk(publicKey: CryptoKey): Promise<Result<JsonWebKey>> {\n if (publicKey.type !== 'public') {\n return fail(`exportPublicKeyJwk requires a public CryptoKey, got '${publicKey.type}'`);\n }\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.exportKey('jwk', publicKey));\n return result.withErrorFormat((e) => `Failed to export public key as JWK: ${e}`);\n }\n\n /**\n * Imports a public-key JWK as a `CryptoKey` for the requested algorithm.\n * @param jwk - The JSON Web Key produced by a prior export.\n * @param algorithm - The algorithm the key was generated for.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with an error.\n */\n public async importPublicKeyJwk(jwk: JsonWebKey, algorithm: KeyPairAlgorithm): Promise<Result<CryptoKey>> {\n const params = keyPairAlgorithmParams[algorithm];\n const result = await captureAsyncResult(() =>\n crypto.webcrypto.subtle.importKey('jwk', jwk, params.importPublicKey, true, params.publicKeyUsages)\n );\n return result.withErrorFormat((e) => `Failed to import ${algorithm} public key from JWK: ${e}`);\n }\n\n /**\n * Exports a public `CryptoKey` as a DER-encoded SPKI blob.\n * @param publicKey - The public `CryptoKey` to export.\n * @returns `Success` with the raw SPKI bytes, or `Failure` with error context.\n */\n public async exportPublicKeySpki(publicKey: CryptoKey): Promise<Result<Uint8Array>> {\n if (publicKey.type !== 'public') {\n return fail(`exportPublicKeySpki requires a public CryptoKey, got '${publicKey.type}'`);\n }\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.exportKey('spki', publicKey));\n return result\n .withErrorFormat((e) => `exportPublicKeySpki: failed to export key: ${e}`)\n .onSuccess((buf) => succeed(new Uint8Array(buf)));\n }\n\n /**\n * Imports a public key from a DER-encoded SPKI blob.\n * @param spkiBytes - The raw SPKI bytes.\n * @param algorithm - The algorithm the key was generated for.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n */\n public async importPublicKeySpki(\n spkiBytes: Uint8Array,\n algorithm: KeyPairAlgorithm\n ): Promise<Result<CryptoKey>> {\n const params = keyPairAlgorithmParams[algorithm];\n const result = await captureAsyncResult(() =>\n crypto.webcrypto.subtle.importKey(\n 'spki',\n spkiBytes,\n params.importPublicKey as AlgorithmIdentifier,\n true,\n [...params.publicKeyUsages]\n )\n );\n return result.withErrorFormat(\n (e) => `importPublicKeySpki: failed to import ${algorithm} public key from SPKI: ${e}`\n );\n }\n\n /**\n * Signs `data` with `privateKey` using the algorithm inferred from the key.\n * @param privateKey - A signing `CryptoKey` (`'ecdsa-p256'` or `'ed25519'`).\n * @param data - The bytes to sign.\n * @returns `Success` with the raw signature bytes, or `Failure` with error context.\n */\n public async sign(privateKey: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>> {\n const algorithm = signAlgorithmFromKey(privateKey);\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.sign(algorithm, privateKey, data));\n return result\n .withErrorFormat((e) => `sign failed: ${e}`)\n .onSuccess((buf) => succeed(new Uint8Array(buf)));\n }\n\n /**\n * Verifies a signature produced by {@link NodeCryptoProvider.sign}.\n * @param publicKey - A verify `CryptoKey` (`'ecdsa-p256'` or `'ed25519'`).\n * @param signature - The raw signature bytes.\n * @param data - The original data that was signed.\n * @returns `Success` with `true` if valid, `false` if not, or `Failure` with error context.\n */\n public async verify(\n publicKey: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array\n ): Promise<Result<boolean>> {\n const algorithm = signAlgorithmFromKey(publicKey);\n const result = await captureAsyncResult(() =>\n crypto.webcrypto.subtle.verify(algorithm, publicKey, signature, data)\n );\n return result.withErrorFormat((e) => `verify failed: ${e}`);\n }\n\n /**\n * Compares two byte arrays in constant time using Node's native\n * `crypto.timingSafeEqual`. Returns `false` for mismatched lengths\n * rather than throwing (Node's native throws on length mismatch).\n * @param a - First byte array.\n * @param b - Second byte array.\n * @returns `true` if lengths match and all bytes are equal, `false` otherwise.\n */\n public timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n return crypto.timingSafeEqual(a, b);\n }\n\n /**\n * Computes an HMAC-SHA256 MAC for `data` using `key`.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param data - The bytes to authenticate.\n * @returns `Success` with the 32-byte MAC, or `Failure` with error context.\n */\n public async hmacSha256(key: CryptoKey, data: Uint8Array): Promise<Result<Uint8Array>> {\n const result = await captureAsyncResult(() => crypto.webcrypto.subtle.sign({ name: 'HMAC' }, key, data));\n return result\n .withErrorFormat((e) => `hmacSha256 failed: ${e}`)\n .onSuccess((buf) => succeed(new Uint8Array(buf)));\n }\n\n /**\n * Verifies an HMAC-SHA256 MAC in constant time.\n * @param key - An HMAC `CryptoKey` with `'sign'` usage.\n * @param signature - The MAC bytes to verify.\n * @param data - The original data that was authenticated.\n * @returns `Success` with `true` if valid, `false` if not, or `Failure` with error context.\n */\n public async verifyHmacSha256(\n key: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array\n ): Promise<Result<boolean>> {\n return (await this.hmacSha256(key, data))\n .withErrorFormat((e) => `verifyHmacSha256 failed: ${e}`)\n .onSuccess((mac) => succeed(this.timingSafeEqual(mac, signature)));\n }\n\n /**\n * Wraps `plaintext` for the holder of `recipientPublicKey` using\n * ECIES (ECDH P-256 + HKDF-SHA256 + AES-GCM-256). See\n * {@link CryptoUtils.ICryptoProvider.wrapBytes | ICryptoProvider.wrapBytes}.\n * @param plaintext - The bytes to wrap.\n * @param recipientPublicKey - The recipient's ECDH P-256 public `CryptoKey`.\n * @param options - HKDF salt and info; see {@link CryptoUtils.IWrapBytesOptions | IWrapBytesOptions}.\n * @returns `Success` with the wrapped payload, or `Failure` with an error.\n */\n public async wrapBytes(\n plaintext: Uint8Array,\n recipientPublicKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<IWrappedBytes>> {\n const recipientCheck = checkEcdhP256(recipientPublicKey, 'public', 'recipient public key');\n if (recipientCheck.isFailure()) {\n return fail(`wrapBytes failed: ${recipientCheck.message}`);\n }\n const subtle = crypto.webcrypto.subtle;\n const result = await captureAsyncResult(async () => {\n const ephemeral = (await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, [\n 'deriveKey'\n ])) as CryptoKeyPair;\n const hkdfBase = await subtle.deriveKey(\n { name: 'ECDH', public: recipientPublicKey },\n ephemeral.privateKey,\n { name: 'HKDF' },\n false,\n ['deriveKey']\n );\n const wrapKey = await subtle.deriveKey(\n { name: 'HKDF', salt: options.salt, info: options.info, hash: 'SHA-256' },\n hkdfBase,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt']\n );\n const nonce = crypto.randomBytes(Constants.GCM_IV_SIZE);\n const ctBuf = await subtle.encrypt({ name: 'AES-GCM', iv: nonce }, wrapKey, plaintext);\n const ephemeralPublicKey = await subtle.exportKey('jwk', ephemeral.publicKey);\n return {\n ephemeralPublicKey,\n nonce: this.toBase64(nonce),\n ciphertext: this.toBase64(new Uint8Array(ctBuf))\n };\n });\n return result.withErrorFormat((e) => `wrapBytes failed: ${e}`);\n }\n\n /**\n * Unwraps a payload produced by `wrapBytes` using the recipient's private\n * key. See {@link CryptoUtils.ICryptoProvider.unwrapBytes | ICryptoProvider.unwrapBytes}.\n * @param wrapped - The wrapped payload.\n * @param recipientPrivateKey - The recipient's ECDH P-256 private `CryptoKey`.\n * @param options - HKDF salt and info matching the wrap call.\n * @returns `Success` with the original `plaintext`, or `Failure` with an error.\n */\n public async unwrapBytes(\n wrapped: IWrappedBytes,\n recipientPrivateKey: CryptoKey,\n options: IWrapBytesOptions\n ): Promise<Result<Uint8Array>> {\n const recipientCheck = checkEcdhP256(recipientPrivateKey, 'private', 'recipient private key');\n if (recipientCheck.isFailure()) {\n return fail(`unwrapBytes failed: ${recipientCheck.message}`);\n }\n const nonceResult = this.fromBase64(wrapped.nonce);\n if (nonceResult.isFailure()) {\n return fail(`unwrapBytes failed: nonce: ${nonceResult.message}`);\n }\n if (nonceResult.value.length !== Constants.GCM_IV_SIZE) {\n return fail(\n `unwrapBytes failed: nonce must be ${Constants.GCM_IV_SIZE} bytes (got ${nonceResult.value.length})`\n );\n }\n const ciphertextResult = this.fromBase64(wrapped.ciphertext);\n if (ciphertextResult.isFailure()) {\n return fail(`unwrapBytes failed: ciphertext: ${ciphertextResult.message}`);\n }\n if (ciphertextResult.value.length < Constants.GCM_AUTH_TAG_SIZE) {\n return fail(\n `unwrapBytes failed: ciphertext must be at least ${Constants.GCM_AUTH_TAG_SIZE} bytes (got ${ciphertextResult.value.length})`\n );\n }\n const subtle = crypto.webcrypto.subtle;\n const result = await captureAsyncResult(async () => {\n const ephemeralPub = await subtle.importKey(\n 'jwk',\n wrapped.ephemeralPublicKey,\n { name: 'ECDH', namedCurve: 'P-256' },\n false,\n []\n );\n const hkdfBase = await subtle.deriveKey(\n { name: 'ECDH', public: ephemeralPub },\n recipientPrivateKey,\n { name: 'HKDF' },\n false,\n ['deriveKey']\n );\n const wrapKey = await subtle.deriveKey(\n { name: 'HKDF', salt: options.salt, info: options.info, hash: 'SHA-256' },\n hkdfBase,\n { name: 'AES-GCM', length: 256 },\n false,\n ['decrypt']\n );\n const ptBuf = await subtle.decrypt(\n { name: 'AES-GCM', iv: nonceResult.value },\n wrapKey,\n ciphertextResult.value\n );\n return new Uint8Array(ptBuf);\n });\n return result.withErrorFormat((e) => `unwrapBytes failed: ${e}`);\n }\n}\n\n/**\n * Derives the algorithm identifier needed by `crypto.subtle.sign/verify`\n * from the key's embedded `algorithm` property. ECDSA requires an explicit\n * `hash` parameter that is not stored on the key object itself; all other\n * supported signing algorithms (`Ed25519`) use the key algorithm as-is.\n */\nfunction signAlgorithmFromKey(key: CryptoKey): AlgorithmIdentifier | EcdsaParams {\n if (key.algorithm.name === 'ECDSA') {\n return { name: 'ECDSA', hash: 'SHA-256' };\n }\n return key.algorithm as AlgorithmIdentifier;\n}\n\n/**\n * Verifies that `key` is an ECDH P-256 `CryptoKey` of the expected `keyType`\n * (public or private). Used by the wrap/unwrap methods to surface a clean\n * `Failure` instead of letting the WebCrypto deriveKey call throw a less\n * informative error later in the pipeline. Key usages are intentionally not\n * checked here: WebCrypto already produces a specific error if `deriveKey` is\n * not in `usages`, and `deriveBits` is an equally valid alternative usage that\n * an explicit check would have to track.\n * @param key - The CryptoKey to validate.\n * @param keyType - The required `key.type` ('public' for wrap, 'private' for unwrap).\n * @param label - Human-readable role label included in the failure message.\n * @returns `Success` with the key (unchanged) when the algorithm, curve, and\n * type all match; otherwise `Failure` with `<label> must be ECDH P-256 (...)`.\n */\nfunction checkEcdhP256(key: CryptoKey, keyType: 'public' | 'private', label: string): Result<CryptoKey> {\n if (key.algorithm.name !== 'ECDH') {\n return fail(`${label} must be ECDH P-256 (got algorithm '${key.algorithm.name}')`);\n }\n const namedCurve = (key.algorithm as EcKeyAlgorithm).namedCurve;\n if (namedCurve !== 'P-256') {\n return fail(`${label} must be ECDH P-256 (got curve '${namedCurve}')`);\n }\n if (key.type !== keyType) {\n return fail(`${label} must be a ${keyType} CryptoKey (got '${key.type}')`);\n }\n return succeed(key);\n}\n\n/**\n * Singleton instance of {@link CryptoUtils.NodeCryptoProvider}.\n * @public\n */\nexport const nodeCryptoProvider: NodeCryptoProvider = new NodeCryptoProvider();\n"]}
@@ -206,4 +206,35 @@ export async function importPublicKeyFromMultibaseSpki(encoded, algorithm, provi
206
206
  }
207
207
  return (await provider.importPublicKeySpki(decodeResult.value, algorithm)).withErrorFormat((e) => `importPublicKeyFromMultibaseSpki: ${e}`);
208
208
  }
209
+ // DER prefix for an X25519 SubjectPublicKeyInfo: SEQUENCE { SEQUENCE { OID id-X25519 }, BIT STRING }.
210
+ // SEQUENCE(42) / SEQUENCE(5) / OID 1.3.101.110 (id-X25519) / BIT STRING(33, 0 unused bits).
211
+ const _X25519_SPKI_PREFIX = [
212
+ 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00
213
+ ];
214
+ const _X25519_SPKI_LENGTH = _X25519_SPKI_PREFIX.length + 32;
215
+ /**
216
+ * Strips the fixed DER prefix from an X25519 SubjectPublicKeyInfo blob, returning the raw
217
+ * 32-byte public key.
218
+ *
219
+ * An X25519 SPKI is always exactly 44 bytes: a fixed 12-byte prefix (SEQUENCE / AlgorithmIdentifier
220
+ * with OID 1.3.101.110 / BIT STRING) followed by the 32-byte raw key. Use this to convert a
221
+ * SPKI-held recipient public key into the raw form accepted by {@link HpkeProvider.openBase}'s
222
+ * `recipientPublicKey` parameter.
223
+ *
224
+ * @param spki - The DER-encoded X25519 SubjectPublicKeyInfo bytes.
225
+ * @returns `Success` with the raw 32-byte public key, or `Failure` if `spki` is not a
226
+ * well-formed 44-byte X25519 SPKI blob.
227
+ * @public
228
+ */
229
+ export function spkiToRawX25519(spki) {
230
+ if (spki.length !== _X25519_SPKI_LENGTH) {
231
+ return fail(`spkiToRawX25519: expected ${_X25519_SPKI_LENGTH} bytes, got ${spki.length}`);
232
+ }
233
+ for (let i = 0; i < _X25519_SPKI_PREFIX.length; i++) {
234
+ if (spki[i] !== _X25519_SPKI_PREFIX[i]) {
235
+ return fail(`spkiToRawX25519: byte ${i} does not match the expected X25519 SPKI prefix`);
236
+ }
237
+ }
238
+ return succeed(spki.slice(_X25519_SPKI_PREFIX.length));
239
+ }
209
240
  //# sourceMappingURL=spkiHelpers.js.map