@fgv/ts-extras 5.1.0-32 → 5.1.0-33

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 (30) hide show
  1. package/dist/packlets/crypto-utils/index.browser.js +3 -2
  2. package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
  3. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +287 -0
  4. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  5. package/dist/packlets/crypto-utils/keystore/index.browser.js +36 -0
  6. package/dist/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  7. package/dist/packlets/crypto-utils/keystore/index.js +2 -0
  8. package/dist/packlets/crypto-utils/keystore/index.js.map +1 -1
  9. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -1
  10. package/dist/ts-extras.d.ts +153 -3
  11. package/lib/packlets/crypto-utils/index.browser.d.ts +1 -1
  12. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
  13. package/lib/packlets/crypto-utils/index.browser.js +3 -2
  14. package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
  15. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts +148 -0
  16. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts.map +1 -0
  17. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +324 -0
  18. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  19. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts +10 -0
  20. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts.map +1 -0
  21. package/lib/packlets/crypto-utils/keystore/index.browser.js +76 -0
  22. package/lib/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  23. package/lib/packlets/crypto-utils/keystore/index.d.ts +1 -0
  24. package/lib/packlets/crypto-utils/keystore/index.d.ts.map +1 -1
  25. package/lib/packlets/crypto-utils/keystore/index.js +4 -1
  26. package/lib/packlets/crypto-utils/keystore/index.js.map +1 -1
  27. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts +6 -3
  28. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts.map +1 -1
  29. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -1
  30. package/package.json +15 -10
@@ -751,6 +751,115 @@ declare type EncryptedFileFormat = typeof Constants.ENCRYPTED_FILE_FORMAT;
751
751
  */
752
752
  declare const encryptedFileFormat: Converter<EncryptedFileFormat>;
753
753
 
754
+ /**
755
+ * {@link CryptoUtils.KeyStore.IPrivateKeyStorage | IPrivateKeyStorage}
756
+ * implementation that persists each private key as its own AES-256-GCM-encrypted
757
+ * file in a directory. The file content is the key's JWK, encrypted with a
758
+ * consumer-supplied 32-byte key via the supplied
759
+ * {@link CryptoUtils.ICryptoProvider | crypto provider}.
760
+ *
761
+ * `supportsNonExtractable` is `false`: persisting to disk requires exporting the
762
+ * private key to JWK, which only works for `extractable: true` keys. The
763
+ * keystore generates extractable keys when a backend reports `false` here.
764
+ *
765
+ * I/O goes through the {@link FileTree.FileTree | FileTree} abstraction (default
766
+ * `FsTree`), so the same implementation works against an in-memory tree (tests)
767
+ * or any other Node-compatible backend.
768
+ *
769
+ * This backend is **Node-only**: it round-trips private keys through
770
+ * `node:crypto` (`crypto.webcrypto.subtle`), so it is intentionally excluded
771
+ * from the browser entry point. Browser consumers should use
772
+ * `IdbPrivateKeyStorage` from `@fgv/ts-web-extras` instead.
773
+ *
774
+ * Single-process assumption: there is no inter-process locking. Concurrent
775
+ * writers to the same directory may race.
776
+ *
777
+ * @public
778
+ */
779
+ declare class EncryptedFilePrivateKeyStorage implements IPrivateKeyStorage {
780
+ /**
781
+ * `false` — disk persistence round-trips via JWK, which requires extractable
782
+ * keys.
783
+ */
784
+ readonly supportsNonExtractable: false;
785
+ private readonly _directory;
786
+ private readonly _encryptionKey;
787
+ private readonly _cryptoProvider;
788
+ private constructor();
789
+ /**
790
+ * Creates a new {@link CryptoUtils.KeyStore.EncryptedFilePrivateKeyStorage}.
791
+ * @param params - {@link CryptoUtils.KeyStore.IEncryptedFilePrivateKeyStorageCreateParams}.
792
+ * @returns `Success` with the new instance, or `Failure` if the encryption
793
+ * key is the wrong size or the storage directory cannot be opened.
794
+ */
795
+ static create(params: IEncryptedFilePrivateKeyStorageCreateParams): Result<EncryptedFilePrivateKeyStorage>;
796
+ /**
797
+ * Stores `key` under `id` as an encrypted JWK file.
798
+ * @param id - Storage handle. Must be a safe filename token
799
+ * (`[A-Za-z0-9._-]+`, not `.`/`..`).
800
+ * @param key - The extractable private `CryptoKey` to persist.
801
+ */
802
+ store(id: string, key: CryptoKey): Promise<Result<string>>;
803
+ /**
804
+ * Loads the private key stored under `id`, decrypting and re-importing it from
805
+ * JWK.
806
+ * @param id - Storage handle.
807
+ */
808
+ load(id: string): Promise<Result<CryptoKey>>;
809
+ /**
810
+ * Deletes the entry stored under `id`. Missing ids fail (the read path is
811
+ * keystore-driven and never asks to delete an id it did not store).
812
+ * @param id - Storage handle.
813
+ */
814
+ delete(id: string): Promise<Result<string>>;
815
+ /**
816
+ * Lists every stored id.
817
+ */
818
+ list(): Promise<Result<readonly string[]>>;
819
+ private _fileNameFor;
820
+ /**
821
+ * Validates the synchronous preconditions for a store: the id is filename-safe,
822
+ * the key is actually a private key, and its algorithm is one we support.
823
+ * Returns the resolved filename and algorithm so the async pipeline can run
824
+ * without re-deriving them.
825
+ */
826
+ private _validateKeyToStore;
827
+ /**
828
+ * Exports `key` to JWK, wraps it in the stored envelope, encrypts it with
829
+ * AES-256-GCM, and writes the resulting file as serialized JSON to `fileName`.
830
+ * Returns the stored `id` on success.
831
+ */
832
+ private _encryptAndWrite;
833
+ private _algorithmOf;
834
+ private _findFile;
835
+ private _writeFile;
836
+ /**
837
+ * Reads `file`, decrypts the AES-256-GCM envelope, and validates it into the
838
+ * typed `IStoredPrivateKeyEnvelope`. Read, decrypt, and shape failures
839
+ * all surface as a decrypt failure for `id`.
840
+ */
841
+ private _decryptEnvelope;
842
+ /**
843
+ * Parses and shape-validates the stored JWK, then re-imports it as a private
844
+ * `CryptoKey` for the envelope's algorithm. The WebCrypto JWK-import algorithm
845
+ * descriptor is shared between public and private keys for every supported
846
+ * algorithm, so `IKeyPairAlgorithmParams.importPublicKey` is reused here;
847
+ * the public/private distinction is carried by the requested `usages`.
848
+ */
849
+ private _importPrivateKey;
850
+ /**
851
+ * Computes the key usages to request when re-importing a stored private key.
852
+ * WebCrypto rejects `importKey` if the requested usages include operations
853
+ * absent from the JWK's `key_ops`, so a key originally created with a narrower
854
+ * usage set than the algorithm default (e.g. an ECDH key with only
855
+ * `deriveBits`) would fail to load against the algorithm-wide defaults.
856
+ * Intersect the algorithm's private usages with the JWK's recorded `key_ops`
857
+ * so we request exactly the operations the stored key actually supports;
858
+ * fall back to the algorithm's private usages when `key_ops` is absent.
859
+ */
860
+ private _importUsagesFor;
861
+ }
862
+
754
863
  /**
755
864
  * Supported encryption algorithms.
756
865
  * @public
@@ -2260,6 +2369,42 @@ declare interface IEncryptedFile<TMetadata = JsonValue> {
2260
2369
  readonly keyDerivation?: IKeyDerivationParams;
2261
2370
  }
2262
2371
 
2372
+ /**
2373
+ * Parameters for {@link CryptoUtils.KeyStore.EncryptedFilePrivateKeyStorage.create}.
2374
+ * @public
2375
+ */
2376
+ declare interface IEncryptedFilePrivateKeyStorageCreateParams {
2377
+ /**
2378
+ * Filesystem path to the directory that holds the encrypted private-key
2379
+ * files. Used only when {@link CryptoUtils.KeyStore.IEncryptedFilePrivateKeyStorageCreateParams.tree}
2380
+ * is omitted (the default `FsTree` backing). The directory must already
2381
+ * exist.
2382
+ */
2383
+ readonly directory: string;
2384
+ /**
2385
+ * Raw AES-256-GCM key (32 bytes) used to encrypt each file's JWK content.
2386
+ * Consumer-supplied and decoupled from the keystore's password lifecycle —
2387
+ * derive it however the application sees fit (typically the same
2388
+ * password-derived key material the keystore vault uses).
2389
+ */
2390
+ readonly encryptionKey: Uint8Array;
2391
+ /**
2392
+ * {@link CryptoUtils.ICryptoProvider | Crypto provider} used for the
2393
+ * AES-256-GCM encrypt/decrypt of each file's contents.
2394
+ */
2395
+ readonly cryptoProvider: ICryptoProvider;
2396
+ /**
2397
+ * Optional {@link FileTree.IFileTreeDirectoryItem | FileTree directory}
2398
+ * override. When supplied it is used as the storage directory directly and
2399
+ * {@link CryptoUtils.KeyStore.IEncryptedFilePrivateKeyStorageCreateParams.directory} is ignored —
2400
+ * pass an in-memory tree for tests, or another Node-compatible backend. When
2401
+ * omitted, a mutable `FsTree` rooted at `directory` is used. (This backend is
2402
+ * Node-only — it round-trips keys through `node:crypto` — so a browser file
2403
+ * tree is not a supported target.)
2404
+ */
2405
+ readonly tree?: FileTree.IFileTreeDirectoryItem;
2406
+ }
2407
+
2263
2408
  /**
2264
2409
  * Configuration for encrypted file handling during loading.
2265
2410
  * @public
@@ -3101,9 +3246,12 @@ declare interface IPbkdf2KeyDerivationParams {
3101
3246
 
3102
3247
  /**
3103
3248
  * Pluggable backend that persists raw asymmetric private keys outside of the
3104
- * encrypted keystore vault. Concrete implementations live in platform-specific
3105
- * packages (e.g. an IndexedDB-backed implementation in `@fgv/ts-web-extras` or
3106
- * an encrypted-file implementation in `@fgv/ts-chocolate`).
3249
+ * encrypted keystore vault. Concrete implementations:
3250
+ * - {@link CryptoUtils.KeyStore.EncryptedFilePrivateKeyStorage} in
3251
+ * `@fgv/ts-extras` directory-on-disk, AES-256-GCM-encrypted JWK per key
3252
+ * (Node; `supportsNonExtractable: false`).
3253
+ * - `IdbPrivateKeyStorage` in `@fgv/ts-web-extras` — IndexedDB-backed, stores
3254
+ * `CryptoKey` objects directly (browser; `supportsNonExtractable: true`).
3107
3255
  *
3108
3256
  * The keystore writes storage-first: a private key is always stored here
3109
3257
  * before the corresponding public-key vault entry is committed. Conversely,
@@ -3666,6 +3814,8 @@ declare namespace KeyStore {
3666
3814
  export {
3667
3815
  Converters_2 as Converters,
3668
3816
  KeyStore_2 as KeyStore,
3817
+ EncryptedFilePrivateKeyStorage,
3818
+ IEncryptedFilePrivateKeyStorageCreateParams,
3669
3819
  isKeyStoreFile,
3670
3820
  allKeyPairAlgorithms,
3671
3821
  KeyPairAlgorithm,
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export * from './model';
7
7
  export { AES_256_KEY_SIZE, DEFAULT_ALGORITHM, ENCRYPTED_FILE_FORMAT, GCM_AUTH_TAG_SIZE, GCM_IV_SIZE } from './constants';
8
- import * as KeyStore from './keystore';
8
+ import * as KeyStore from './keystore/index.browser';
9
9
  export { KeyStore };
10
10
  import * as Converters from './converters';
11
11
  export { Converters };
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.browser.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AAGH,cAAc,SAAS,CAAC;AAGxB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAGpB,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AAGtB,OAAO,EAAE,wBAAwB,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAGvG,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAM3F,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,0BAA0B,EAC1B,QAAQ,EACR,cAAc,EACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,eAAe,CAAC;AAIvB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.browser.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AAGH,cAAc,SAAS,CAAC;AAGxB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACZ,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,CAAC;AAGpB,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,CAAC;AAGtB,OAAO,EAAE,wBAAwB,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAGvG,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAM3F,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,0BAA0B,EAC1B,QAAQ,EACR,cAAc,EACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,eAAe,CAAC;AAIvB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
@@ -70,8 +70,9 @@ Object.defineProperty(exports, "DEFAULT_ALGORITHM", { enumerable: true, get: fun
70
70
  Object.defineProperty(exports, "ENCRYPTED_FILE_FORMAT", { enumerable: true, get: function () { return constants_1.ENCRYPTED_FILE_FORMAT; } });
71
71
  Object.defineProperty(exports, "GCM_AUTH_TAG_SIZE", { enumerable: true, get: function () { return constants_1.GCM_AUTH_TAG_SIZE; } });
72
72
  Object.defineProperty(exports, "GCM_IV_SIZE", { enumerable: true, get: function () { return constants_1.GCM_IV_SIZE; } });
73
- // KeyStore namespace
74
- const KeyStore = __importStar(require("./keystore"));
73
+ // KeyStore namespace (browser-safe barrel — omits the Node-only
74
+ // EncryptedFilePrivateKeyStorage so the browser entry stays free of node:crypto)
75
+ const KeyStore = __importStar(require("./keystore/index.browser"));
75
76
  exports.KeyStore = KeyStore;
76
77
  // Converters namespace
77
78
  const Converters = __importStar(require("./converters"));
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.browser.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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEZ;;;;GAIG;AAEH,iCAAiC;AACjC,0CAAwB;AAExB,YAAY;AACZ,yCAMqB;AALnB,6GAAA,gBAAgB,OAAA;AAChB,8GAAA,iBAAiB,OAAA;AACjB,kHAAA,qBAAqB,OAAA;AACrB,8GAAA,iBAAiB,OAAA;AACjB,wGAAA,WAAW,OAAA;AAGb,qBAAqB;AACrB,qDAAuC;AAC9B,4BAAQ;AAEjB,uBAAuB;AACvB,yDAA2C;AAClC,gCAAU;AAEnB,6BAA6B;AAC7B,uEAAuG;AAA9F,oIAAA,wBAAwB,OAAA;AAEjC,8DAA8D;AAC9D,mEAA2F;AAAzD,gIAAA,sBAAsB,OAAA;AAExD,8DAA8D;AAC9D,4DAA4D;AAE5D,yBAAyB;AACzB,iDAOyB;AANvB,oHAAA,mBAAmB,OAAA;AACnB,4GAAA,WAAW,OAAA;AACX,2GAAA,UAAU,OAAA;AAEV,yGAAA,QAAQ,OAAA;AACR,+GAAA,cAAc,OAAA;AAGhB,yBAAyB;AACzB,6CAKuB;AAJrB,6HAAA,8BAA8B,OAAA;AAC9B,+HAAA,gCAAgC,OAAA;AAChC,uHAAA,wBAAwB,OAAA;AACxB,uHAAA,wBAAwB,OAAA;AAG1B,qFAAqF;AACrF,uFAAuF;AACvF,+CAA+D;AAAtD,4GAAA,YAAY,OAAA","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\n/**\n * Crypto utilities for encrypted file handling and key management (browser version).\n * Note: For browser crypto provider, use \\@fgv/ts-web-extras.\n * @packageDocumentation\n */\n\n// Re-export all types from model\nexport * from './model';\n\n// Constants\nexport {\n AES_256_KEY_SIZE,\n DEFAULT_ALGORITHM,\n ENCRYPTED_FILE_FORMAT,\n GCM_AUTH_TAG_SIZE,\n GCM_IV_SIZE\n} from './constants';\n\n// KeyStore namespace\nimport * as KeyStore from './keystore';\nexport { KeyStore };\n\n// Converters namespace\nimport * as Converters from './converters';\nexport { Converters };\n\n// Direct encryption provider\nexport { DirectEncryptionProvider, IDirectEncryptionProviderParams } from './directEncryptionProvider';\n\n// WebCrypto parameter table for asymmetric keypair algorithms\nexport { IKeyPairAlgorithmParams, keyPairAlgorithmParams } from './keyPairAlgorithmParams';\n\n// Note: NodeCryptoProvider is NOT exported in browser version\n// Use BrowserCryptoProvider from @fgv/ts-web-extras instead\n\n// Encrypted file helpers\nexport {\n createEncryptedFile,\n decryptFile,\n fromBase64,\n ICreateEncryptedFileParams,\n toBase64,\n tryDecryptFile\n} from './encryptedFile';\n\n// Multibase/SPKI helpers\nexport {\n exportPublicKeyAsMultibaseSpki,\n importPublicKeyFromMultibaseSpki,\n multibaseBase64UrlDecode,\n multibaseBase64UrlEncode\n} from './spkiHelpers';\n\n// HPKE base mode (RFC 9180) — DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM\n// hpkeProvider.ts has no Node-specific imports and is safe in the browser entry point.\nexport { HpkeProvider, IHpkeSealResult } from './hpkeProvider';\n"]}
1
+ {"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/index.browser.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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEZ;;;;GAIG;AAEH,iCAAiC;AACjC,0CAAwB;AAExB,YAAY;AACZ,yCAMqB;AALnB,6GAAA,gBAAgB,OAAA;AAChB,8GAAA,iBAAiB,OAAA;AACjB,kHAAA,qBAAqB,OAAA;AACrB,8GAAA,iBAAiB,OAAA;AACjB,wGAAA,WAAW,OAAA;AAGb,gEAAgE;AAChE,iFAAiF;AACjF,mEAAqD;AAC5C,4BAAQ;AAEjB,uBAAuB;AACvB,yDAA2C;AAClC,gCAAU;AAEnB,6BAA6B;AAC7B,uEAAuG;AAA9F,oIAAA,wBAAwB,OAAA;AAEjC,8DAA8D;AAC9D,mEAA2F;AAAzD,gIAAA,sBAAsB,OAAA;AAExD,8DAA8D;AAC9D,4DAA4D;AAE5D,yBAAyB;AACzB,iDAOyB;AANvB,oHAAA,mBAAmB,OAAA;AACnB,4GAAA,WAAW,OAAA;AACX,2GAAA,UAAU,OAAA;AAEV,yGAAA,QAAQ,OAAA;AACR,+GAAA,cAAc,OAAA;AAGhB,yBAAyB;AACzB,6CAKuB;AAJrB,6HAAA,8BAA8B,OAAA;AAC9B,+HAAA,gCAAgC,OAAA;AAChC,uHAAA,wBAAwB,OAAA;AACxB,uHAAA,wBAAwB,OAAA;AAG1B,qFAAqF;AACrF,uFAAuF;AACvF,+CAA+D;AAAtD,4GAAA,YAAY,OAAA","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\n/**\n * Crypto utilities for encrypted file handling and key management (browser version).\n * Note: For browser crypto provider, use \\@fgv/ts-web-extras.\n * @packageDocumentation\n */\n\n// Re-export all types from model\nexport * from './model';\n\n// Constants\nexport {\n AES_256_KEY_SIZE,\n DEFAULT_ALGORITHM,\n ENCRYPTED_FILE_FORMAT,\n GCM_AUTH_TAG_SIZE,\n GCM_IV_SIZE\n} from './constants';\n\n// KeyStore namespace (browser-safe barrel — omits the Node-only\n// EncryptedFilePrivateKeyStorage so the browser entry stays free of node:crypto)\nimport * as KeyStore from './keystore/index.browser';\nexport { KeyStore };\n\n// Converters namespace\nimport * as Converters from './converters';\nexport { Converters };\n\n// Direct encryption provider\nexport { DirectEncryptionProvider, IDirectEncryptionProviderParams } from './directEncryptionProvider';\n\n// WebCrypto parameter table for asymmetric keypair algorithms\nexport { IKeyPairAlgorithmParams, keyPairAlgorithmParams } from './keyPairAlgorithmParams';\n\n// Note: NodeCryptoProvider is NOT exported in browser version\n// Use BrowserCryptoProvider from @fgv/ts-web-extras instead\n\n// Encrypted file helpers\nexport {\n createEncryptedFile,\n decryptFile,\n fromBase64,\n ICreateEncryptedFileParams,\n toBase64,\n tryDecryptFile\n} from './encryptedFile';\n\n// Multibase/SPKI helpers\nexport {\n exportPublicKeyAsMultibaseSpki,\n importPublicKeyFromMultibaseSpki,\n multibaseBase64UrlDecode,\n multibaseBase64UrlEncode\n} from './spkiHelpers';\n\n// HPKE base mode (RFC 9180) — DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM\n// hpkeProvider.ts has no Node-specific imports and is safe in the browser entry point.\nexport { HpkeProvider, IHpkeSealResult } from './hpkeProvider';\n"]}
@@ -0,0 +1,148 @@
1
+ import { Result } from '@fgv/ts-utils';
2
+ import { FileTree } from '@fgv/ts-json-base';
3
+ import { ICryptoProvider } from '../model';
4
+ import { IPrivateKeyStorage } from './privateKeyStorage';
5
+ /**
6
+ * Parameters for {@link CryptoUtils.KeyStore.EncryptedFilePrivateKeyStorage.create}.
7
+ * @public
8
+ */
9
+ export interface IEncryptedFilePrivateKeyStorageCreateParams {
10
+ /**
11
+ * Filesystem path to the directory that holds the encrypted private-key
12
+ * files. Used only when {@link CryptoUtils.KeyStore.IEncryptedFilePrivateKeyStorageCreateParams.tree}
13
+ * is omitted (the default `FsTree` backing). The directory must already
14
+ * exist.
15
+ */
16
+ readonly directory: string;
17
+ /**
18
+ * Raw AES-256-GCM key (32 bytes) used to encrypt each file's JWK content.
19
+ * Consumer-supplied and decoupled from the keystore's password lifecycle —
20
+ * derive it however the application sees fit (typically the same
21
+ * password-derived key material the keystore vault uses).
22
+ */
23
+ readonly encryptionKey: Uint8Array;
24
+ /**
25
+ * {@link CryptoUtils.ICryptoProvider | Crypto provider} used for the
26
+ * AES-256-GCM encrypt/decrypt of each file's contents.
27
+ */
28
+ readonly cryptoProvider: ICryptoProvider;
29
+ /**
30
+ * Optional {@link FileTree.IFileTreeDirectoryItem | FileTree directory}
31
+ * override. When supplied it is used as the storage directory directly and
32
+ * {@link CryptoUtils.KeyStore.IEncryptedFilePrivateKeyStorageCreateParams.directory} is ignored —
33
+ * pass an in-memory tree for tests, or another Node-compatible backend. When
34
+ * omitted, a mutable `FsTree` rooted at `directory` is used. (This backend is
35
+ * Node-only — it round-trips keys through `node:crypto` — so a browser file
36
+ * tree is not a supported target.)
37
+ */
38
+ readonly tree?: FileTree.IFileTreeDirectoryItem;
39
+ }
40
+ /**
41
+ * {@link CryptoUtils.KeyStore.IPrivateKeyStorage | IPrivateKeyStorage}
42
+ * implementation that persists each private key as its own AES-256-GCM-encrypted
43
+ * file in a directory. The file content is the key's JWK, encrypted with a
44
+ * consumer-supplied 32-byte key via the supplied
45
+ * {@link CryptoUtils.ICryptoProvider | crypto provider}.
46
+ *
47
+ * `supportsNonExtractable` is `false`: persisting to disk requires exporting the
48
+ * private key to JWK, which only works for `extractable: true` keys. The
49
+ * keystore generates extractable keys when a backend reports `false` here.
50
+ *
51
+ * I/O goes through the {@link FileTree.FileTree | FileTree} abstraction (default
52
+ * `FsTree`), so the same implementation works against an in-memory tree (tests)
53
+ * or any other Node-compatible backend.
54
+ *
55
+ * This backend is **Node-only**: it round-trips private keys through
56
+ * `node:crypto` (`crypto.webcrypto.subtle`), so it is intentionally excluded
57
+ * from the browser entry point. Browser consumers should use
58
+ * `IdbPrivateKeyStorage` from `@fgv/ts-web-extras` instead.
59
+ *
60
+ * Single-process assumption: there is no inter-process locking. Concurrent
61
+ * writers to the same directory may race.
62
+ *
63
+ * @public
64
+ */
65
+ export declare class EncryptedFilePrivateKeyStorage implements IPrivateKeyStorage {
66
+ /**
67
+ * `false` — disk persistence round-trips via JWK, which requires extractable
68
+ * keys.
69
+ */
70
+ readonly supportsNonExtractable: false;
71
+ private readonly _directory;
72
+ private readonly _encryptionKey;
73
+ private readonly _cryptoProvider;
74
+ private constructor();
75
+ /**
76
+ * Creates a new {@link CryptoUtils.KeyStore.EncryptedFilePrivateKeyStorage}.
77
+ * @param params - {@link CryptoUtils.KeyStore.IEncryptedFilePrivateKeyStorageCreateParams}.
78
+ * @returns `Success` with the new instance, or `Failure` if the encryption
79
+ * key is the wrong size or the storage directory cannot be opened.
80
+ */
81
+ static create(params: IEncryptedFilePrivateKeyStorageCreateParams): Result<EncryptedFilePrivateKeyStorage>;
82
+ /**
83
+ * Stores `key` under `id` as an encrypted JWK file.
84
+ * @param id - Storage handle. Must be a safe filename token
85
+ * (`[A-Za-z0-9._-]+`, not `.`/`..`).
86
+ * @param key - The extractable private `CryptoKey` to persist.
87
+ */
88
+ store(id: string, key: CryptoKey): Promise<Result<string>>;
89
+ /**
90
+ * Loads the private key stored under `id`, decrypting and re-importing it from
91
+ * JWK.
92
+ * @param id - Storage handle.
93
+ */
94
+ load(id: string): Promise<Result<CryptoKey>>;
95
+ /**
96
+ * Deletes the entry stored under `id`. Missing ids fail (the read path is
97
+ * keystore-driven and never asks to delete an id it did not store).
98
+ * @param id - Storage handle.
99
+ */
100
+ delete(id: string): Promise<Result<string>>;
101
+ /**
102
+ * Lists every stored id.
103
+ */
104
+ list(): Promise<Result<readonly string[]>>;
105
+ private _fileNameFor;
106
+ /**
107
+ * Validates the synchronous preconditions for a store: the id is filename-safe,
108
+ * the key is actually a private key, and its algorithm is one we support.
109
+ * Returns the resolved filename and algorithm so the async pipeline can run
110
+ * without re-deriving them.
111
+ */
112
+ private _validateKeyToStore;
113
+ /**
114
+ * Exports `key` to JWK, wraps it in the stored envelope, encrypts it with
115
+ * AES-256-GCM, and writes the resulting file as serialized JSON to `fileName`.
116
+ * Returns the stored `id` on success.
117
+ */
118
+ private _encryptAndWrite;
119
+ private _algorithmOf;
120
+ private _findFile;
121
+ private _writeFile;
122
+ /**
123
+ * Reads `file`, decrypts the AES-256-GCM envelope, and validates it into the
124
+ * typed `IStoredPrivateKeyEnvelope`. Read, decrypt, and shape failures
125
+ * all surface as a decrypt failure for `id`.
126
+ */
127
+ private _decryptEnvelope;
128
+ /**
129
+ * Parses and shape-validates the stored JWK, then re-imports it as a private
130
+ * `CryptoKey` for the envelope's algorithm. The WebCrypto JWK-import algorithm
131
+ * descriptor is shared between public and private keys for every supported
132
+ * algorithm, so `IKeyPairAlgorithmParams.importPublicKey` is reused here;
133
+ * the public/private distinction is carried by the requested `usages`.
134
+ */
135
+ private _importPrivateKey;
136
+ /**
137
+ * Computes the key usages to request when re-importing a stored private key.
138
+ * WebCrypto rejects `importKey` if the requested usages include operations
139
+ * absent from the JWK's `key_ops`, so a key originally created with a narrower
140
+ * usage set than the algorithm default (e.g. an ECDH key with only
141
+ * `deriveBits`) would fail to load against the algorithm-wide defaults.
142
+ * Intersect the algorithm's private usages with the JWK's recorded `key_ops`
143
+ * so we request exactly the operations the stored key actually supports;
144
+ * fall back to the algorithm's private usages when `key_ops` is absent.
145
+ */
146
+ private _importUsagesFor;
147
+ }
148
+ //# sourceMappingURL=encryptedFilePrivateKeyStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryptedFilePrivateKeyStorage.d.ts","sourceRoot":"","sources":["../../../../src/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.ts"],"names":[],"mappings":"AAqBA,OAAO,EAML,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAc,MAAM,mBAAmB,CAAC;AAGzD,OAAO,EAAE,eAAe,EAAoB,MAAM,UAAU,CAAC;AAG7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,2CAA2C;IAC1D;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC;IAEnC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC;IAEzC;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,sBAAsB,CAAC;CACjD;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,8BAA+B,YAAW,kBAAkB;IACvE;;;OAGG;IACH,SAAgB,sBAAsB,EAAE,KAAK,CAAS;IAEtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAa;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAElD,OAAO;IAYP;;;;;OAKG;WACW,MAAM,CAClB,MAAM,EAAE,2CAA2C,GAClD,MAAM,CAAC,8BAA8B,CAAC;IAgBzC;;;;;OAKG;IACU,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAMvE;;;;OAIG;IACU,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAUzD;;;;OAIG;IACU,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAsBxD;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAWvD,OAAO,CAAC,YAAY;IAOpB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;;;OAIG;YACW,gBAAgB;IAuB9B,OAAO,CAAC,YAAY;IA6BpB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,UAAU;IAiBlB;;;;OAIG;YACW,gBAAgB;IAW9B;;;;;;OAMG;YACW,iBAAiB;IAsB/B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;CAQzB"}