@fgv/ts-extras 5.1.0-27 → 5.1.0-28
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.
- package/dist/packlets/crypto-utils/converters.js +24 -4
- package/dist/packlets/crypto-utils/converters.js.map +1 -1
- package/dist/packlets/crypto-utils/hpkeProvider.js +333 -0
- package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -0
- package/dist/packlets/crypto-utils/index.browser.js +3 -0
- package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
- package/dist/packlets/crypto-utils/index.js +2 -0
- package/dist/packlets/crypto-utils/index.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/converters.js +2 -2
- package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/keyStore.js +108 -3
- package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
- package/dist/packlets/crypto-utils/model.js +21 -0
- package/dist/packlets/crypto-utils/model.js.map +1 -1
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js +74 -0
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
- package/dist/ts-extras.d.ts +434 -18
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/packlets/crypto-utils/converters.d.ts +12 -1
- package/lib/packlets/crypto-utils/converters.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/converters.js +25 -5
- package/lib/packlets/crypto-utils/converters.js.map +1 -1
- package/lib/packlets/crypto-utils/hpkeProvider.d.ts +142 -0
- package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/hpkeProvider.js +337 -0
- package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts +1 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.js +5 -1
- package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
- package/lib/packlets/crypto-utils/index.d.ts +1 -0
- package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.js +4 -1
- package/lib/packlets/crypto-utils/index.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/converters.js +1 -1
- package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +32 -2
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.js +116 -11
- package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/model.d.ts +21 -3
- package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
- package/lib/packlets/crypto-utils/model.d.ts +165 -9
- package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/model.js +22 -1
- package/lib/packlets/crypto-utils/model.js.map +1 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +39 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js +74 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/converters.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 { Converters as JsonConverters, JsonValue } from '@fgv/ts-json-base';\nimport { Converter, Converters, fail, succeed } from '@fgv/ts-utils';\nimport * as Constants from './constants';\nimport {\n EncryptedFileErrorMode,\n EncryptedFileFormat,\n EncryptionAlgorithm,\n IEncryptedFile,\n IKeyDerivationParams,\n INamedSecret,\n KeyDerivationFunction\n} from './model';\n\n// ============================================================================\n// Base Converters\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.EncryptionAlgorithm | encryption algorithm} values.\n * @public\n */\nexport const encryptionAlgorithm: Converter<EncryptionAlgorithm> =\n Converters.enumeratedValue<EncryptionAlgorithm>([Constants.DEFAULT_ALGORITHM]);\n\n/**\n * Converter for {@link CryptoUtils.EncryptedFileFormat | encrypted file format} version.\n * @public\n */\nexport const encryptedFileFormat: Converter<EncryptedFileFormat> = Converters.enumeratedValue([\n Constants.ENCRYPTED_FILE_FORMAT\n]);\n\n/**\n * Converter for {@link CryptoUtils.EncryptedFileErrorMode | encrypted file error mode}.\n * @public\n */\nexport const encryptedFileErrorMode: Converter<EncryptedFileErrorMode> =\n Converters.enumeratedValue<EncryptedFileErrorMode>(['fail', 'skip', 'warn']);\n\n/**\n * Converter for {@link CryptoUtils.KeyDerivationFunction | key derivation function} type.\n * @public\n */\nexport const keyDerivationFunction: Converter<KeyDerivationFunction> =\n Converters.enumeratedValue<KeyDerivationFunction>(['pbkdf2']);\n\n/**\n * Converter for {@link CryptoUtils.IKeyDerivationParams | key derivation parameters}.\n * @public\n */\nexport const keyDerivationParams: Converter<IKeyDerivationParams> = Converters.object<IKeyDerivationParams>({\n kdf: keyDerivationFunction,\n salt: Converters.string,\n iterations: Converters.number\n});\n\n/**\n * Converter for base64 strings (validates format).\n * @public\n */\nexport const base64String: Converter<string> = Converters.string.withConstraint((value) => {\n // Basic base64 validation - check for valid characters and padding\n const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;\n if (!base64Regex.test(value)) {\n return fail('Invalid base64 encoding');\n }\n return succeed(value);\n});\n\n// ============================================================================\n// Uint8Array Converter\n// ============================================================================\n\n/**\n * Converter which converts a base64 string to a Uint8Array.\n * @public\n */\nexport const uint8ArrayFromBase64: Converter<Uint8Array> = Converters.string.map((base64) => {\n try {\n // Use Buffer in Node.js environment, atob in browser\n if (typeof Buffer !== 'undefined') {\n return succeed(Uint8Array.from(Buffer.from(base64, 'base64')));\n }\n /* c8 ignore start - Browser-only fallback cannot be tested in Node.js environment */\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return succeed(bytes);\n } catch (e) {\n // This catch is for browser's atob() which throws on invalid base64\n // Node's Buffer.from() doesn't throw, it ignores invalid characters\n const message = e instanceof Error ? e.message : String(e);\n return fail(`Invalid base64: ${message}`);\n }\n /* c8 ignore stop */\n});\n\n// ============================================================================\n// Named Secret Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.INamedSecret | named secret} from JSON representation.\n * Expects key as base64 string in JSON, converts to Uint8Array.\n * @public\n */\nexport const namedSecret: Converter<INamedSecret> = Converters.object<INamedSecret>({\n name: Converters.string,\n key: uint8ArrayFromBase64\n});\n\n// ============================================================================\n// Encrypted File Converter Factory\n// ============================================================================\n\n/**\n * Base encrypted file structure without metadata typing.\n * Used internally by the converter factory.\n */\ninterface IBaseEncryptedFile {\n readonly format: EncryptedFileFormat;\n readonly secretName: string;\n readonly algorithm: EncryptionAlgorithm;\n readonly iv: string;\n readonly authTag: string;\n readonly encryptedData: string;\n readonly keyDerivation?: IKeyDerivationParams;\n readonly metadata?: JsonValue;\n}\n\n/**\n * Base converter for encrypted file structure (without typed metadata).\n */\nconst baseEncryptedFileConverter: Converter<IBaseEncryptedFile> = Converters.object<IBaseEncryptedFile>(\n {\n format: encryptedFileFormat,\n secretName: Converters.string,\n algorithm: encryptionAlgorithm,\n iv: base64String,\n authTag: base64String,\n encryptedData: base64String,\n keyDerivation: keyDerivationParams,\n metadata: JsonConverters.jsonValue\n },\n { optionalFields: ['keyDerivation', 'metadata'] }\n);\n\n/**\n * Creates a converter for {@link CryptoUtils.IEncryptedFile | encrypted files} with optional typed metadata.\n * @typeParam TMetadata - Type of optional unencrypted metadata\n * @param metadataConverter - Optional converter for validating metadata field\n * @returns A converter that validates and converts encrypted file structures\n * @public\n */\nexport function createEncryptedFileConverter<TMetadata = JsonValue>(\n metadataConverter?: Converter<TMetadata>\n): Converter<IEncryptedFile<TMetadata>> {\n return Converters.generic<IEncryptedFile<TMetadata>>((from: unknown) => {\n // First validate base structure\n const baseResult = baseEncryptedFileConverter.convert(from);\n if (baseResult.isFailure()) {\n return fail(baseResult.message);\n }\n\n const base = baseResult.value;\n\n // Validate metadata with specific converter if provided and metadata exists\n if (metadataConverter !== undefined && base.metadata !== undefined) {\n const metaResult = metadataConverter.convert(base.metadata);\n if (metaResult.isFailure()) {\n return fail(`Invalid metadata: ${metaResult.message}`);\n }\n return succeed({\n ...base,\n metadata: metaResult.value\n } as IEncryptedFile<TMetadata>);\n }\n\n // Return as-is (metadata is either undefined or untyped JsonValue)\n return succeed(base as IEncryptedFile<TMetadata>);\n });\n}\n\n/**\n * Default converter for encrypted files without typed metadata.\n * @public\n */\nexport const encryptedFile: Converter<IEncryptedFile> = createEncryptedFileConverter();\n"]}
|
|
1
|
+
{"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/converters.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 { Converters as JsonConverters, JsonValue } from '@fgv/ts-json-base';\nimport { Converter, Converters, fail, succeed } from '@fgv/ts-utils';\nimport * as Constants from './constants';\nimport {\n EncryptedFileErrorMode,\n EncryptedFileFormat,\n EncryptionAlgorithm,\n IArgon2idKeyDerivationParams,\n IEncryptedFile,\n IKeyDerivationParams,\n INamedSecret,\n IPbkdf2KeyDerivationParams,\n KeyDerivationFunction\n} from './model';\n\n// ============================================================================\n// Base Converters\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.EncryptionAlgorithm | encryption algorithm} values.\n * @public\n */\nexport const encryptionAlgorithm: Converter<EncryptionAlgorithm> =\n Converters.enumeratedValue<EncryptionAlgorithm>([Constants.DEFAULT_ALGORITHM]);\n\n/**\n * Converter for {@link CryptoUtils.EncryptedFileFormat | encrypted file format} version.\n * @public\n */\nexport const encryptedFileFormat: Converter<EncryptedFileFormat> = Converters.enumeratedValue([\n Constants.ENCRYPTED_FILE_FORMAT\n]);\n\n/**\n * Converter for {@link CryptoUtils.EncryptedFileErrorMode | encrypted file error mode}.\n * @public\n */\nexport const encryptedFileErrorMode: Converter<EncryptedFileErrorMode> =\n Converters.enumeratedValue<EncryptedFileErrorMode>(['fail', 'skip', 'warn']);\n\n/**\n * Converter for {@link CryptoUtils.KeyDerivationFunction | key derivation function} type.\n * @public\n */\nexport const keyDerivationFunction: Converter<KeyDerivationFunction> =\n Converters.enumeratedValue<KeyDerivationFunction>(['pbkdf2', 'argon2id']);\n\n/**\n * Converter for {@link CryptoUtils.IPbkdf2KeyDerivationParams | PBKDF2 key derivation parameters}.\n * @public\n */\nexport const pbkdf2KeyDerivationParams: Converter<IPbkdf2KeyDerivationParams> =\n Converters.object<IPbkdf2KeyDerivationParams>({\n kdf: Converters.enumeratedValue<'pbkdf2'>(['pbkdf2']),\n salt: Converters.string,\n iterations: Converters.number\n });\n\n/**\n * Converter for {@link CryptoUtils.IArgon2idKeyDerivationParams | Argon2id key derivation parameters}.\n * @public\n */\nexport const argon2idKeyDerivationParams: Converter<IArgon2idKeyDerivationParams> =\n Converters.object<IArgon2idKeyDerivationParams>({\n kdf: Converters.enumeratedValue<'argon2id'>(['argon2id']),\n salt: Converters.string,\n memoryKiB: Converters.number,\n iterations: Converters.number,\n parallelism: Converters.number\n });\n\n/**\n * Converter for {@link CryptoUtils.IKeyDerivationParams | key derivation parameters}.\n * Handles both PBKDF2 and Argon2id discriminated union arms.\n * @public\n */\nexport const keyDerivationParams: Converter<IKeyDerivationParams> = Converters.oneOf<IKeyDerivationParams>([\n pbkdf2KeyDerivationParams,\n argon2idKeyDerivationParams\n]);\n\n/**\n * Converter for base64 strings (validates format).\n * @public\n */\nexport const base64String: Converter<string> = Converters.string.withConstraint((value) => {\n // Basic base64 validation - check for valid characters and padding\n const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;\n if (!base64Regex.test(value)) {\n return fail('Invalid base64 encoding');\n }\n return succeed(value);\n});\n\n// ============================================================================\n// Uint8Array Converter\n// ============================================================================\n\n/**\n * Converter which converts a base64 string to a Uint8Array.\n * @public\n */\nexport const uint8ArrayFromBase64: Converter<Uint8Array> = Converters.string.map((base64) => {\n try {\n // Use Buffer in Node.js environment, atob in browser\n if (typeof Buffer !== 'undefined') {\n return succeed(Uint8Array.from(Buffer.from(base64, 'base64')));\n }\n /* c8 ignore start - Browser-only fallback cannot be tested in Node.js environment */\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return succeed(bytes);\n } catch (e) {\n // This catch is for browser's atob() which throws on invalid base64\n // Node's Buffer.from() doesn't throw, it ignores invalid characters\n const message = e instanceof Error ? e.message : String(e);\n return fail(`Invalid base64: ${message}`);\n }\n /* c8 ignore stop */\n});\n\n// ============================================================================\n// Named Secret Converter\n// ============================================================================\n\n/**\n * Converter for {@link CryptoUtils.INamedSecret | named secret} from JSON representation.\n * Expects key as base64 string in JSON, converts to Uint8Array.\n * @public\n */\nexport const namedSecret: Converter<INamedSecret> = Converters.object<INamedSecret>({\n name: Converters.string,\n key: uint8ArrayFromBase64\n});\n\n// ============================================================================\n// Encrypted File Converter Factory\n// ============================================================================\n\n/**\n * Base encrypted file structure without metadata typing.\n * Used internally by the converter factory.\n */\ninterface IBaseEncryptedFile {\n readonly format: EncryptedFileFormat;\n readonly secretName: string;\n readonly algorithm: EncryptionAlgorithm;\n readonly iv: string;\n readonly authTag: string;\n readonly encryptedData: string;\n readonly keyDerivation?: IKeyDerivationParams;\n readonly metadata?: JsonValue;\n}\n\n/**\n * Base converter for encrypted file structure (without typed metadata).\n */\nconst baseEncryptedFileConverter: Converter<IBaseEncryptedFile> = Converters.object<IBaseEncryptedFile>(\n {\n format: encryptedFileFormat,\n secretName: Converters.string,\n algorithm: encryptionAlgorithm,\n iv: base64String,\n authTag: base64String,\n encryptedData: base64String,\n keyDerivation: keyDerivationParams,\n metadata: JsonConverters.jsonValue\n },\n { optionalFields: ['keyDerivation', 'metadata'] }\n);\n\n/**\n * Creates a converter for {@link CryptoUtils.IEncryptedFile | encrypted files} with optional typed metadata.\n * @typeParam TMetadata - Type of optional unencrypted metadata\n * @param metadataConverter - Optional converter for validating metadata field\n * @returns A converter that validates and converts encrypted file structures\n * @public\n */\nexport function createEncryptedFileConverter<TMetadata = JsonValue>(\n metadataConverter?: Converter<TMetadata>\n): Converter<IEncryptedFile<TMetadata>> {\n return Converters.generic<IEncryptedFile<TMetadata>>((from: unknown) => {\n // First validate base structure\n const baseResult = baseEncryptedFileConverter.convert(from);\n if (baseResult.isFailure()) {\n return fail(baseResult.message);\n }\n\n const base = baseResult.value;\n\n // Validate metadata with specific converter if provided and metadata exists\n if (metadataConverter !== undefined && base.metadata !== undefined) {\n const metaResult = metadataConverter.convert(base.metadata);\n if (metaResult.isFailure()) {\n return fail(`Invalid metadata: ${metaResult.message}`);\n }\n return succeed({\n ...base,\n metadata: metaResult.value\n } as IEncryptedFile<TMetadata>);\n }\n\n // Return as-is (metadata is either undefined or untyped JsonValue)\n return succeed(base as IEncryptedFile<TMetadata>);\n });\n}\n\n/**\n * Default converter for encrypted files without typed metadata.\n * @public\n */\nexport const encryptedFile: Converter<IEncryptedFile> = createEncryptedFileConverter();\n"]}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
/**
|
|
3
|
+
* Output of {@link HpkeProvider.sealBase}.
|
|
4
|
+
*
|
|
5
|
+
* The `ciphertext` field includes the 16-byte AES-256-GCM authentication tag
|
|
6
|
+
* appended by Web Crypto's `encrypt()` operation: `length = plaintext.length + 16`.
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export interface IHpkeSealResult {
|
|
10
|
+
/**
|
|
11
|
+
* Encapsulated key — 32-byte raw X25519 ephemeral public key (`enc` in RFC 9180).
|
|
12
|
+
* Must be transmitted to the recipient alongside `ciphertext`.
|
|
13
|
+
*/
|
|
14
|
+
readonly enc: Uint8Array;
|
|
15
|
+
/**
|
|
16
|
+
* AES-256-GCM ciphertext with the 16-byte authentication tag appended.
|
|
17
|
+
* Length = `plaintext.length + 16`.
|
|
18
|
+
*/
|
|
19
|
+
readonly ciphertext: Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* HPKE base mode (RFC 9180) — `DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM`.
|
|
23
|
+
*
|
|
24
|
+
* Class-based provider that captures a `SubtleCrypto` instance at construction,
|
|
25
|
+
* matching the existing `NodeCryptoProvider` / `BrowserCryptoProvider` / `KeyStore`
|
|
26
|
+
* factory pattern used throughout `@fgv/ts-extras/crypto-utils`.
|
|
27
|
+
*
|
|
28
|
+
* **Node.js usage:**
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import * as crypto from 'crypto';
|
|
31
|
+
* const hpke = HpkeProvider.create(crypto.webcrypto.subtle).orThrow();
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* **Browser usage:**
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const hpke = HpkeProvider.create(globalThis.crypto.subtle).orThrow();
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* **Runtime requirements:** Node.js 20+ (X25519 in `crypto.webcrypto`);
|
|
40
|
+
* Chrome 113+, Safari 16.4+, Firefox 118+ (X25519 added to Web Crypto in 2023).
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
export declare class HpkeProvider {
|
|
44
|
+
private readonly _subtle;
|
|
45
|
+
private constructor();
|
|
46
|
+
/**
|
|
47
|
+
* Creates an `HpkeProvider` bound to the given `SubtleCrypto` instance.
|
|
48
|
+
*
|
|
49
|
+
* @param subtle - Web Crypto SubtleCrypto instance.
|
|
50
|
+
* Node.js: `(await import('crypto')).webcrypto.subtle`.
|
|
51
|
+
* Browser: `globalThis.crypto.subtle`.
|
|
52
|
+
* @returns `Success` with the provider, or `Failure` if construction fails.
|
|
53
|
+
*/
|
|
54
|
+
static create(subtle: SubtleCrypto): Result<HpkeProvider>;
|
|
55
|
+
/**
|
|
56
|
+
* HPKE base-mode seal (sender side). RFC 9180 §6.1.
|
|
57
|
+
*
|
|
58
|
+
* Generates a fresh ephemeral X25519 keypair, runs DHKEM Encap to produce a
|
|
59
|
+
* shared secret and `enc` (32-byte raw ephemeral public key), derives the AEAD
|
|
60
|
+
* key and nonce deterministically via the RFC 9180 key schedule, then encrypts
|
|
61
|
+
* `plaintext` with AES-256-GCM.
|
|
62
|
+
*
|
|
63
|
+
* @param recipientPublicKey - Recipient's X25519 public `CryptoKey`
|
|
64
|
+
* (`algorithm.name === 'X25519'`, `type === 'public'`, **`extractable: true`**).
|
|
65
|
+
* Must be extractable — DHKEM Encap calls `exportKey('raw', ...)` on this key to
|
|
66
|
+
* build the KEM shared-secret context. Keys imported with `extractable: false` will
|
|
67
|
+
* cause this method to return a `Failure`.
|
|
68
|
+
* @param info - Context-binding bytes. **Load-bearing — no default.**
|
|
69
|
+
* Binds this ciphertext to a specific application context, preventing replay
|
|
70
|
+
* across different contexts sharing the same recipient keypair.
|
|
71
|
+
* Use `new TextEncoder().encode('myapp/v1/use-case\x00' + contextId)` pattern.
|
|
72
|
+
* Never pass an empty array in production: empty `info` provides no context binding.
|
|
73
|
+
* @param aad - Additional authenticated data. Integrity-protected but not encrypted.
|
|
74
|
+
* `new Uint8Array(0)` is valid when no AAD is needed.
|
|
75
|
+
* @param plaintext - Bytes to encrypt. `new Uint8Array(0)` is valid.
|
|
76
|
+
* @returns `Success` with `{ enc, ciphertext }`, or `Failure` with error context.
|
|
77
|
+
*/
|
|
78
|
+
sealBase(recipientPublicKey: CryptoKey, info: Uint8Array, aad: Uint8Array, plaintext: Uint8Array): Promise<Result<IHpkeSealResult>>;
|
|
79
|
+
/**
|
|
80
|
+
* HPKE base-mode open (recipient side). RFC 9180 §6.1.
|
|
81
|
+
*
|
|
82
|
+
* Decapsulates `enc` using the recipient's X25519 private key, derives the same
|
|
83
|
+
* AEAD key and nonce from the shared secret and `info`, then authenticates and
|
|
84
|
+
* decrypts `ciphertext` with AES-256-GCM.
|
|
85
|
+
*
|
|
86
|
+
* Returns `Failure` on any of:
|
|
87
|
+
* - Wrong private key (different DH output → different key derivation)
|
|
88
|
+
* - Wrong `info` (different key schedule context → different AEAD key)
|
|
89
|
+
* - Wrong `aad` (AES-GCM authentication fails)
|
|
90
|
+
* - Tampered `ciphertext` or `enc` (authentication fails or DH fails)
|
|
91
|
+
* - `enc` not exactly 32 bytes
|
|
92
|
+
* - `ciphertext` shorter than 16 bytes (no room for authentication tag)
|
|
93
|
+
*
|
|
94
|
+
* @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`
|
|
95
|
+
* (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).
|
|
96
|
+
* **Must be extractable** (`extractable: true`) — the recipient's public key bytes
|
|
97
|
+
* are recovered from the JWK `x` field during Decap.
|
|
98
|
+
* @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.
|
|
99
|
+
* @param aad - Must exactly match `aad` from `sealBase`.
|
|
100
|
+
* @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.
|
|
101
|
+
* @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.
|
|
102
|
+
* @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.
|
|
103
|
+
*/
|
|
104
|
+
openBase(recipientPrivateKey: CryptoKey, info: Uint8Array, aad: Uint8Array, enc: Uint8Array, ciphertext: Uint8Array): Promise<Result<Uint8Array>>;
|
|
105
|
+
/**
|
|
106
|
+
* HKDF-SHA256 key derivation (RFC 5869). Extract-then-Expand using SHA-256.
|
|
107
|
+
*
|
|
108
|
+
* This is raw RFC 5869 HKDF — it does **not** use RFC 9180's labeled variants.
|
|
109
|
+
* The HPKE key schedule internally uses labeled HKDF; this method is the unlabeled
|
|
110
|
+
* version for callers that need standalone key derivation.
|
|
111
|
+
*
|
|
112
|
+
* @param secret - Input keying material (IKM). Any length.
|
|
113
|
+
* @param salt - Optional salt. Use `new Uint8Array(0)` if no salt is available
|
|
114
|
+
* (RFC 5869: 32 zero bytes are used internally when salt is empty).
|
|
115
|
+
* @param info - Context / application-binding bytes. Any length.
|
|
116
|
+
* @param length - Number of output bytes to derive. Maximum 8160 bytes (255 × 32).
|
|
117
|
+
* @returns `Success` with derived bytes, or `Failure` with error context.
|
|
118
|
+
*/
|
|
119
|
+
hkdf(secret: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Promise<Result<Uint8Array>>;
|
|
120
|
+
/**
|
|
121
|
+
* Encodes an {@link IHpkeSealResult} as a single contiguous byte array for wire transport.
|
|
122
|
+
*
|
|
123
|
+
* Format: `enc` (32 bytes, fixed) || `ciphertext` (variable length).
|
|
124
|
+
* The 32-byte `enc` length is fixed for X25519; the split point is unambiguous.
|
|
125
|
+
*
|
|
126
|
+
* @param result - The output of {@link HpkeProvider.sealBase}.
|
|
127
|
+
* @returns Concatenated bytes: `enc || ciphertext`.
|
|
128
|
+
*/
|
|
129
|
+
static encodeEnvelope(result: IHpkeSealResult): Uint8Array;
|
|
130
|
+
/**
|
|
131
|
+
* Decodes an envelope produced by {@link HpkeProvider.encodeEnvelope}.
|
|
132
|
+
*
|
|
133
|
+
* Validates that the buffer is at least 48 bytes (32-byte enc + 16-byte minimum
|
|
134
|
+
* ciphertext containing the AES-GCM auth tag; zero-length plaintext is the minimum
|
|
135
|
+
* meaningful case).
|
|
136
|
+
*
|
|
137
|
+
* @param envelope - Envelope bytes from `encodeEnvelope`.
|
|
138
|
+
* @returns `Success` with `{ enc, ciphertext }`, or `Failure` if malformed.
|
|
139
|
+
*/
|
|
140
|
+
static decodeEnvelope(envelope: Uint8Array): Result<IHpkeSealResult>;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=hpkeProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hpkeProvider.d.ts","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/hpkeProvider.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,MAAM,EAAoD,MAAM,eAAe,CAAC;AAoOzF;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO;IAIP;;;;;;;OAOG;WACW,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;IAIhE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACU,QAAQ,CACnB,kBAAkB,EAAE,SAAS,EAC7B,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAenC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACU,QAAQ,CACnB,mBAAmB,EAAE,SAAS,EAC9B,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAuB9B;;;;;;;;;;;;;OAaG;IACU,IAAI,CACf,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAQ9B;;;;;;;;OAQG;WACW,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,UAAU;IAIjE;;;;;;;;;OASG;WACW,cAAc,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC,eAAe,CAAC;CAY5E"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2026 Erik Fortune
|
|
3
|
+
//
|
|
4
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
// in the Software without restriction, including without limitation the rights
|
|
7
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
// furnished to do so, subject to the following conditions:
|
|
10
|
+
//
|
|
11
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
// copies or substantial portions of the Software.
|
|
13
|
+
//
|
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
// SOFTWARE.
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.HpkeProvider = void 0;
|
|
23
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
24
|
+
// ---- Internal constants ----
|
|
25
|
+
// "HPKE-v1" (7 bytes) — RFC 9180 domain separator prefix
|
|
26
|
+
const _HPKE_VERSION = new Uint8Array([0x48, 0x50, 0x4b, 0x45, 0x2d, 0x76, 0x31]);
|
|
27
|
+
// suite_id = "HPKE" || I2OSP(KEM_ID=0x0020, 2) || I2OSP(KDF_ID=0x0001, 2) || I2OSP(AEAD_ID=0x0002, 2)
|
|
28
|
+
// Used in key schedule LabeledExtract/LabeledExpand calls.
|
|
29
|
+
const _SUITE_ID = new Uint8Array([
|
|
30
|
+
0x48, 0x50, 0x4b, 0x45, 0x00, 0x20, 0x00, 0x01, 0x00, 0x02
|
|
31
|
+
]);
|
|
32
|
+
// kem_suite_id = "KEM" || I2OSP(KEM_ID=0x0020, 2)
|
|
33
|
+
// Used in DHKEM-internal LabeledExtract/LabeledExpand calls.
|
|
34
|
+
const _KEM_SUITE_ID = new Uint8Array([0x4b, 0x45, 0x4d, 0x00, 0x20]);
|
|
35
|
+
const _N_SECRET = 32; // Nsecret: KEM shared-secret output length
|
|
36
|
+
const _N_PK = 32; // Npk: X25519 public key (raw) length — also the enc length
|
|
37
|
+
const _N_K = 32; // Nk: AES-256-GCM key length
|
|
38
|
+
const _N_N = 12; // Nn: AES-256-GCM nonce length
|
|
39
|
+
const _N_T = 16; // Nt: AES-256-GCM authentication tag length
|
|
40
|
+
const _MODE_BASE = 0x00; // RFC 9180 mode_base
|
|
41
|
+
// ---- Internal helpers ----
|
|
42
|
+
// These are NOT exported. Per design D7, only the five public operations are public surface.
|
|
43
|
+
// Copies any Uint8Array into a fresh Uint8Array<ArrayBuffer>.
|
|
44
|
+
// Required to satisfy TypeScript's strict BufferSource typing for Web Crypto API calls —
|
|
45
|
+
// subtle.* rejects Uint8Array<ArrayBufferLike> but accepts Uint8Array<ArrayBuffer>.
|
|
46
|
+
// Pattern follows browserCryptoProvider.ts toBufferView.
|
|
47
|
+
function _toBufferView(arr) {
|
|
48
|
+
const buffer = new ArrayBuffer(arr.byteLength);
|
|
49
|
+
const view = new Uint8Array(buffer);
|
|
50
|
+
view.set(arr);
|
|
51
|
+
return view;
|
|
52
|
+
}
|
|
53
|
+
function _concat(...arrays) {
|
|
54
|
+
const total = arrays.reduce((n, a) => n + a.length, 0);
|
|
55
|
+
const buffer = new ArrayBuffer(total);
|
|
56
|
+
const out = new Uint8Array(buffer);
|
|
57
|
+
let offset = 0;
|
|
58
|
+
for (const a of arrays) {
|
|
59
|
+
out.set(a, offset);
|
|
60
|
+
offset += a.length;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function _i2osp(value, length) {
|
|
65
|
+
const buffer = new ArrayBuffer(length);
|
|
66
|
+
const out = new Uint8Array(buffer);
|
|
67
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
68
|
+
out[i] = value % 256;
|
|
69
|
+
value = Math.floor(value / 256);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
// Decodes a base64url-encoded string to Uint8Array<ArrayBuffer>.
|
|
74
|
+
// Uses `atob` (global in Node 18+ and all modern browsers; no import needed).
|
|
75
|
+
function _base64UrlDecode(s) {
|
|
76
|
+
const base64 = s.replace(/-/g, '+').replace(/_/g, '/');
|
|
77
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
78
|
+
const binary = atob(padded);
|
|
79
|
+
const buffer = new ArrayBuffer(binary.length);
|
|
80
|
+
const out = new Uint8Array(buffer);
|
|
81
|
+
for (let i = 0; i < binary.length; i++) {
|
|
82
|
+
out[i] = binary.charCodeAt(i);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
// HMAC-SHA256(key, data) — the underlying primitive for HKDF.
|
|
87
|
+
async function _hmacSha256(subtle, key, data) {
|
|
88
|
+
const hmacKey = await subtle.importKey('raw', _toBufferView(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
89
|
+
return new Uint8Array(await subtle.sign('HMAC', hmacKey, _toBufferView(data)));
|
|
90
|
+
}
|
|
91
|
+
// RFC 5869 HKDF-Extract(salt, IKM) = HMAC-SHA256(key=salt, msg=IKM).
|
|
92
|
+
// If salt is empty (length 0), uses 32 zero bytes per RFC 5869 §2.2.
|
|
93
|
+
async function _hkdfExtract(subtle, salt, ikm) {
|
|
94
|
+
const effectiveSalt = salt.length === 0 ? new Uint8Array(32) : salt;
|
|
95
|
+
return _hmacSha256(subtle, effectiveSalt, ikm);
|
|
96
|
+
}
|
|
97
|
+
// RFC 5869 HKDF-Expand(PRK, info, L) — iterative HMAC expansion.
|
|
98
|
+
async function _hkdfExpand(subtle, prk, info, length) {
|
|
99
|
+
if (length > 255 * 32) {
|
|
100
|
+
throw new Error(`HKDF-Expand: requested length ${length} exceeds maximum 8160 bytes (255 * HashLen)`);
|
|
101
|
+
}
|
|
102
|
+
const n = Math.ceil(length / 32);
|
|
103
|
+
const buffer = new ArrayBuffer(length);
|
|
104
|
+
const okm = new Uint8Array(buffer);
|
|
105
|
+
let prev = new Uint8Array(new ArrayBuffer(0));
|
|
106
|
+
let offset = 0;
|
|
107
|
+
for (let i = 1; i <= n; i++) {
|
|
108
|
+
prev = await _hmacSha256(subtle, prk, _concat(prev, info, new Uint8Array([i])));
|
|
109
|
+
const toCopy = Math.min(32, length - offset);
|
|
110
|
+
okm.set(prev.subarray(0, toCopy), offset);
|
|
111
|
+
offset += toCopy;
|
|
112
|
+
}
|
|
113
|
+
return okm;
|
|
114
|
+
}
|
|
115
|
+
// RFC 9180 §4 LabeledExtract — HKDF-Extract with HPKE-v1 domain label.
|
|
116
|
+
// labeled_ikm = "HPKE-v1" || suite_id || label || ikm
|
|
117
|
+
// Extract(salt, labeled_ikm)
|
|
118
|
+
async function _labeledExtract(subtle, suiteId, salt, label, ikm) {
|
|
119
|
+
const labeledIkm = _concat(_HPKE_VERSION, suiteId, new TextEncoder().encode(label), ikm);
|
|
120
|
+
return _hkdfExtract(subtle, salt, labeledIkm);
|
|
121
|
+
}
|
|
122
|
+
// RFC 9180 §4 LabeledExpand — HKDF-Expand with HPKE-v1 domain label.
|
|
123
|
+
// labeled_info = I2OSP(L, 2) || "HPKE-v1" || suite_id || label || info
|
|
124
|
+
// Expand(prk, labeled_info, L)
|
|
125
|
+
async function _labeledExpand(subtle, suiteId, prk, label, info, length) {
|
|
126
|
+
const labeledInfo = _concat(_i2osp(length, 2), _HPKE_VERSION, suiteId, new TextEncoder().encode(label), info);
|
|
127
|
+
return _hkdfExpand(subtle, prk, labeledInfo, length);
|
|
128
|
+
}
|
|
129
|
+
// RFC 9180 §4.1 DHKEM Encap — generates ephemeral keypair, DH with recipient pubkey,
|
|
130
|
+
// derives shared_secret via ExtractAndExpand.
|
|
131
|
+
// NOTE: uses label "eae_prk" (not "dh") per RFC 9180 §4.1 ExtractAndExpand.
|
|
132
|
+
async function _kemEncap(subtle, recipientPublicKey) {
|
|
133
|
+
const ephemeral = (await subtle.generateKey({ name: 'X25519' }, true, ['deriveBits']));
|
|
134
|
+
const enc = new Uint8Array(await subtle.exportKey('raw', ephemeral.publicKey));
|
|
135
|
+
const dh = new Uint8Array(await subtle.deriveBits({ name: 'X25519', public: recipientPublicKey }, ephemeral.privateKey, 256));
|
|
136
|
+
const pkRm = new Uint8Array(await subtle.exportKey('raw', recipientPublicKey));
|
|
137
|
+
const kemContext = _concat(enc, pkRm);
|
|
138
|
+
const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);
|
|
139
|
+
const sharedSecret = await _labeledExpand(subtle, _KEM_SUITE_ID, eaePrk, 'shared_secret', kemContext, _N_SECRET);
|
|
140
|
+
return { sharedSecret, enc };
|
|
141
|
+
}
|
|
142
|
+
// RFC 9180 §4.1 DHKEM Decap — deserializes enc, DH with recipient privkey,
|
|
143
|
+
// derives same shared_secret via ExtractAndExpand.
|
|
144
|
+
// Requires recipientPrivateKey to be extractable (JWK export needed for pkRm).
|
|
145
|
+
async function _kemDecap(subtle, enc, recipientPrivateKey) {
|
|
146
|
+
const pkE = await subtle.importKey('raw', _toBufferView(enc), { name: 'X25519' }, true, []);
|
|
147
|
+
const dh = new Uint8Array(await subtle.deriveBits({ name: 'X25519', public: pkE }, recipientPrivateKey, 256));
|
|
148
|
+
// Recover recipient's own public key from JWK x field (base64url-encoded raw X25519 public key).
|
|
149
|
+
const jwk = (await subtle.exportKey('jwk', recipientPrivateKey));
|
|
150
|
+
/* c8 ignore next 3 - defensive: X25519 JWK always has an x field; unreachable via public API */
|
|
151
|
+
if (!jwk.x) {
|
|
152
|
+
throw new Error('HPKE Decap: failed to extract public key bytes from recipient private key JWK');
|
|
153
|
+
}
|
|
154
|
+
const pkRm = _base64UrlDecode(jwk.x);
|
|
155
|
+
const kemContext = _concat(enc, pkRm);
|
|
156
|
+
const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);
|
|
157
|
+
return _labeledExpand(subtle, _KEM_SUITE_ID, eaePrk, 'shared_secret', kemContext, _N_SECRET);
|
|
158
|
+
}
|
|
159
|
+
// RFC 9180 §5 KeySchedule (base mode, psk = b"", psk_id = b"").
|
|
160
|
+
async function _keyScheduleBase(subtle, sharedSecret, info) {
|
|
161
|
+
const empty = new Uint8Array(0);
|
|
162
|
+
const pskIdHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'psk_id_hash', empty);
|
|
163
|
+
const infoHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'info_hash', info);
|
|
164
|
+
const ksContext = _concat(new Uint8Array([_MODE_BASE]), pskIdHash, infoHash);
|
|
165
|
+
const prk = await _labeledExtract(subtle, _SUITE_ID, sharedSecret, 'secret', empty);
|
|
166
|
+
const key = await _labeledExpand(subtle, _SUITE_ID, prk, 'key', ksContext, _N_K);
|
|
167
|
+
const baseNonce = await _labeledExpand(subtle, _SUITE_ID, prk, 'base_nonce', ksContext, _N_N);
|
|
168
|
+
return { key, baseNonce };
|
|
169
|
+
}
|
|
170
|
+
// ---- Public class ----
|
|
171
|
+
/**
|
|
172
|
+
* HPKE base mode (RFC 9180) — `DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM`.
|
|
173
|
+
*
|
|
174
|
+
* Class-based provider that captures a `SubtleCrypto` instance at construction,
|
|
175
|
+
* matching the existing `NodeCryptoProvider` / `BrowserCryptoProvider` / `KeyStore`
|
|
176
|
+
* factory pattern used throughout `@fgv/ts-extras/crypto-utils`.
|
|
177
|
+
*
|
|
178
|
+
* **Node.js usage:**
|
|
179
|
+
* ```typescript
|
|
180
|
+
* import * as crypto from 'crypto';
|
|
181
|
+
* const hpke = HpkeProvider.create(crypto.webcrypto.subtle).orThrow();
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* **Browser usage:**
|
|
185
|
+
* ```typescript
|
|
186
|
+
* const hpke = HpkeProvider.create(globalThis.crypto.subtle).orThrow();
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* **Runtime requirements:** Node.js 20+ (X25519 in `crypto.webcrypto`);
|
|
190
|
+
* Chrome 113+, Safari 16.4+, Firefox 118+ (X25519 added to Web Crypto in 2023).
|
|
191
|
+
* @public
|
|
192
|
+
*/
|
|
193
|
+
class HpkeProvider {
|
|
194
|
+
constructor(subtle) {
|
|
195
|
+
this._subtle = subtle;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Creates an `HpkeProvider` bound to the given `SubtleCrypto` instance.
|
|
199
|
+
*
|
|
200
|
+
* @param subtle - Web Crypto SubtleCrypto instance.
|
|
201
|
+
* Node.js: `(await import('crypto')).webcrypto.subtle`.
|
|
202
|
+
* Browser: `globalThis.crypto.subtle`.
|
|
203
|
+
* @returns `Success` with the provider, or `Failure` if construction fails.
|
|
204
|
+
*/
|
|
205
|
+
static create(subtle) {
|
|
206
|
+
return (0, ts_utils_1.captureResult)(() => new HpkeProvider(subtle));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* HPKE base-mode seal (sender side). RFC 9180 §6.1.
|
|
210
|
+
*
|
|
211
|
+
* Generates a fresh ephemeral X25519 keypair, runs DHKEM Encap to produce a
|
|
212
|
+
* shared secret and `enc` (32-byte raw ephemeral public key), derives the AEAD
|
|
213
|
+
* key and nonce deterministically via the RFC 9180 key schedule, then encrypts
|
|
214
|
+
* `plaintext` with AES-256-GCM.
|
|
215
|
+
*
|
|
216
|
+
* @param recipientPublicKey - Recipient's X25519 public `CryptoKey`
|
|
217
|
+
* (`algorithm.name === 'X25519'`, `type === 'public'`, **`extractable: true`**).
|
|
218
|
+
* Must be extractable — DHKEM Encap calls `exportKey('raw', ...)` on this key to
|
|
219
|
+
* build the KEM shared-secret context. Keys imported with `extractable: false` will
|
|
220
|
+
* cause this method to return a `Failure`.
|
|
221
|
+
* @param info - Context-binding bytes. **Load-bearing — no default.**
|
|
222
|
+
* Binds this ciphertext to a specific application context, preventing replay
|
|
223
|
+
* across different contexts sharing the same recipient keypair.
|
|
224
|
+
* Use `new TextEncoder().encode('myapp/v1/use-case\x00' + contextId)` pattern.
|
|
225
|
+
* Never pass an empty array in production: empty `info` provides no context binding.
|
|
226
|
+
* @param aad - Additional authenticated data. Integrity-protected but not encrypted.
|
|
227
|
+
* `new Uint8Array(0)` is valid when no AAD is needed.
|
|
228
|
+
* @param plaintext - Bytes to encrypt. `new Uint8Array(0)` is valid.
|
|
229
|
+
* @returns `Success` with `{ enc, ciphertext }`, or `Failure` with error context.
|
|
230
|
+
*/
|
|
231
|
+
async sealBase(recipientPublicKey, info, aad, plaintext) {
|
|
232
|
+
const result = await (0, ts_utils_1.captureAsyncResult)(async () => {
|
|
233
|
+
const { sharedSecret, enc } = await _kemEncap(this._subtle, recipientPublicKey);
|
|
234
|
+
const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);
|
|
235
|
+
const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt']);
|
|
236
|
+
const ct = await this._subtle.encrypt({ name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) }, aesKey, _toBufferView(plaintext));
|
|
237
|
+
return { enc, ciphertext: new Uint8Array(ct) };
|
|
238
|
+
});
|
|
239
|
+
return result.withErrorFormat((e) => `HPKE sealBase failed: ${e}`);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* HPKE base-mode open (recipient side). RFC 9180 §6.1.
|
|
243
|
+
*
|
|
244
|
+
* Decapsulates `enc` using the recipient's X25519 private key, derives the same
|
|
245
|
+
* AEAD key and nonce from the shared secret and `info`, then authenticates and
|
|
246
|
+
* decrypts `ciphertext` with AES-256-GCM.
|
|
247
|
+
*
|
|
248
|
+
* Returns `Failure` on any of:
|
|
249
|
+
* - Wrong private key (different DH output → different key derivation)
|
|
250
|
+
* - Wrong `info` (different key schedule context → different AEAD key)
|
|
251
|
+
* - Wrong `aad` (AES-GCM authentication fails)
|
|
252
|
+
* - Tampered `ciphertext` or `enc` (authentication fails or DH fails)
|
|
253
|
+
* - `enc` not exactly 32 bytes
|
|
254
|
+
* - `ciphertext` shorter than 16 bytes (no room for authentication tag)
|
|
255
|
+
*
|
|
256
|
+
* @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`
|
|
257
|
+
* (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).
|
|
258
|
+
* **Must be extractable** (`extractable: true`) — the recipient's public key bytes
|
|
259
|
+
* are recovered from the JWK `x` field during Decap.
|
|
260
|
+
* @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.
|
|
261
|
+
* @param aad - Must exactly match `aad` from `sealBase`.
|
|
262
|
+
* @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.
|
|
263
|
+
* @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.
|
|
264
|
+
* @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.
|
|
265
|
+
*/
|
|
266
|
+
async openBase(recipientPrivateKey, info, aad, enc, ciphertext) {
|
|
267
|
+
if (enc.length !== _N_PK) {
|
|
268
|
+
return (0, ts_utils_1.fail)(`HPKE openBase: enc must be ${_N_PK} bytes, got ${enc.length}`);
|
|
269
|
+
}
|
|
270
|
+
if (ciphertext.length < _N_T) {
|
|
271
|
+
return (0, ts_utils_1.fail)(`HPKE openBase: ciphertext too short (minimum ${_N_T} bytes for auth tag, got ${ciphertext.length})`);
|
|
272
|
+
}
|
|
273
|
+
const result = await (0, ts_utils_1.captureAsyncResult)(async () => {
|
|
274
|
+
const sharedSecret = await _kemDecap(this._subtle, enc, recipientPrivateKey);
|
|
275
|
+
const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);
|
|
276
|
+
const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['decrypt']);
|
|
277
|
+
const pt = await this._subtle.decrypt({ name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) }, aesKey, _toBufferView(ciphertext));
|
|
278
|
+
return new Uint8Array(pt);
|
|
279
|
+
});
|
|
280
|
+
return result.withErrorFormat((e) => `HPKE openBase failed: ${e}`);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* HKDF-SHA256 key derivation (RFC 5869). Extract-then-Expand using SHA-256.
|
|
284
|
+
*
|
|
285
|
+
* This is raw RFC 5869 HKDF — it does **not** use RFC 9180's labeled variants.
|
|
286
|
+
* The HPKE key schedule internally uses labeled HKDF; this method is the unlabeled
|
|
287
|
+
* version for callers that need standalone key derivation.
|
|
288
|
+
*
|
|
289
|
+
* @param secret - Input keying material (IKM). Any length.
|
|
290
|
+
* @param salt - Optional salt. Use `new Uint8Array(0)` if no salt is available
|
|
291
|
+
* (RFC 5869: 32 zero bytes are used internally when salt is empty).
|
|
292
|
+
* @param info - Context / application-binding bytes. Any length.
|
|
293
|
+
* @param length - Number of output bytes to derive. Maximum 8160 bytes (255 × 32).
|
|
294
|
+
* @returns `Success` with derived bytes, or `Failure` with error context.
|
|
295
|
+
*/
|
|
296
|
+
async hkdf(secret, salt, info, length) {
|
|
297
|
+
const result = await (0, ts_utils_1.captureAsyncResult)(async () => {
|
|
298
|
+
const prk = await _hkdfExtract(this._subtle, salt, secret);
|
|
299
|
+
return _hkdfExpand(this._subtle, prk, info, length);
|
|
300
|
+
});
|
|
301
|
+
return result.withErrorFormat((e) => `HKDF failed: ${e}`);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Encodes an {@link IHpkeSealResult} as a single contiguous byte array for wire transport.
|
|
305
|
+
*
|
|
306
|
+
* Format: `enc` (32 bytes, fixed) || `ciphertext` (variable length).
|
|
307
|
+
* The 32-byte `enc` length is fixed for X25519; the split point is unambiguous.
|
|
308
|
+
*
|
|
309
|
+
* @param result - The output of {@link HpkeProvider.sealBase}.
|
|
310
|
+
* @returns Concatenated bytes: `enc || ciphertext`.
|
|
311
|
+
*/
|
|
312
|
+
static encodeEnvelope(result) {
|
|
313
|
+
return _concat(result.enc, result.ciphertext);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Decodes an envelope produced by {@link HpkeProvider.encodeEnvelope}.
|
|
317
|
+
*
|
|
318
|
+
* Validates that the buffer is at least 48 bytes (32-byte enc + 16-byte minimum
|
|
319
|
+
* ciphertext containing the AES-GCM auth tag; zero-length plaintext is the minimum
|
|
320
|
+
* meaningful case).
|
|
321
|
+
*
|
|
322
|
+
* @param envelope - Envelope bytes from `encodeEnvelope`.
|
|
323
|
+
* @returns `Success` with `{ enc, ciphertext }`, or `Failure` if malformed.
|
|
324
|
+
*/
|
|
325
|
+
static decodeEnvelope(envelope) {
|
|
326
|
+
const minLen = _N_PK + _N_T;
|
|
327
|
+
if (envelope.length < minLen) {
|
|
328
|
+
return (0, ts_utils_1.fail)(`HPKE decodeEnvelope: envelope too short (minimum ${minLen} bytes, got ${envelope.length})`);
|
|
329
|
+
}
|
|
330
|
+
return (0, ts_utils_1.succeed)({
|
|
331
|
+
enc: envelope.slice(0, _N_PK),
|
|
332
|
+
ciphertext: envelope.slice(_N_PK)
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
exports.HpkeProvider = HpkeProvider;
|
|
337
|
+
//# sourceMappingURL=hpkeProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hpkeProvider.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/hpkeProvider.ts"],"names":[],"mappings":"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","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 { Result, captureAsyncResult, captureResult, fail, succeed } from '@fgv/ts-utils';\n\n// ---- Internal constants ----\n\n// \"HPKE-v1\" (7 bytes) — RFC 9180 domain separator prefix\nconst _HPKE_VERSION: Uint8Array<ArrayBuffer> = new Uint8Array([0x48, 0x50, 0x4b, 0x45, 0x2d, 0x76, 0x31]);\n\n// suite_id = \"HPKE\" || I2OSP(KEM_ID=0x0020, 2) || I2OSP(KDF_ID=0x0001, 2) || I2OSP(AEAD_ID=0x0002, 2)\n// Used in key schedule LabeledExtract/LabeledExpand calls.\nconst _SUITE_ID: Uint8Array<ArrayBuffer> = new Uint8Array([\n 0x48, 0x50, 0x4b, 0x45, 0x00, 0x20, 0x00, 0x01, 0x00, 0x02\n]);\n\n// kem_suite_id = \"KEM\" || I2OSP(KEM_ID=0x0020, 2)\n// Used in DHKEM-internal LabeledExtract/LabeledExpand calls.\nconst _KEM_SUITE_ID: Uint8Array<ArrayBuffer> = new Uint8Array([0x4b, 0x45, 0x4d, 0x00, 0x20]);\n\nconst _N_SECRET: number = 32; // Nsecret: KEM shared-secret output length\nconst _N_PK: number = 32; // Npk: X25519 public key (raw) length — also the enc length\nconst _N_K: number = 32; // Nk: AES-256-GCM key length\nconst _N_N: number = 12; // Nn: AES-256-GCM nonce length\nconst _N_T: number = 16; // Nt: AES-256-GCM authentication tag length\nconst _MODE_BASE: number = 0x00; // RFC 9180 mode_base\n\n// ---- Internal helpers ----\n// These are NOT exported. Per design D7, only the five public operations are public surface.\n\n// Copies any Uint8Array into a fresh Uint8Array<ArrayBuffer>.\n// Required to satisfy TypeScript's strict BufferSource typing for Web Crypto API calls —\n// subtle.* rejects Uint8Array<ArrayBufferLike> but accepts Uint8Array<ArrayBuffer>.\n// Pattern follows browserCryptoProvider.ts toBufferView.\nfunction _toBufferView(arr: Uint8Array): Uint8Array<ArrayBuffer> {\n const buffer = new ArrayBuffer(arr.byteLength);\n const view = new Uint8Array(buffer);\n view.set(arr);\n return view;\n}\n\nfunction _concat(...arrays: Uint8Array[]): Uint8Array<ArrayBuffer> {\n const total = arrays.reduce((n, a) => n + a.length, 0);\n const buffer = new ArrayBuffer(total);\n const out = new Uint8Array(buffer);\n let offset = 0;\n for (const a of arrays) {\n out.set(a, offset);\n offset += a.length;\n }\n return out;\n}\n\nfunction _i2osp(value: number, length: number): Uint8Array<ArrayBuffer> {\n const buffer = new ArrayBuffer(length);\n const out = new Uint8Array(buffer);\n for (let i = length - 1; i >= 0; i--) {\n out[i] = value % 256;\n value = Math.floor(value / 256);\n }\n return out;\n}\n\n// Decodes a base64url-encoded string to Uint8Array<ArrayBuffer>.\n// Uses `atob` (global in Node 18+ and all modern browsers; no import needed).\nfunction _base64UrlDecode(s: string): Uint8Array<ArrayBuffer> {\n const base64 = s.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n const binary = atob(padded);\n const buffer = new ArrayBuffer(binary.length);\n const out = new Uint8Array(buffer);\n for (let i = 0; i < binary.length; i++) {\n out[i] = binary.charCodeAt(i);\n }\n return out;\n}\n\n// HMAC-SHA256(key, data) — the underlying primitive for HKDF.\nasync function _hmacSha256(\n subtle: SubtleCrypto,\n key: Uint8Array,\n data: Uint8Array\n): Promise<Uint8Array<ArrayBuffer>> {\n const hmacKey = await subtle.importKey(\n 'raw',\n _toBufferView(key),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n return new Uint8Array(await subtle.sign('HMAC', hmacKey, _toBufferView(data)));\n}\n\n// RFC 5869 HKDF-Extract(salt, IKM) = HMAC-SHA256(key=salt, msg=IKM).\n// If salt is empty (length 0), uses 32 zero bytes per RFC 5869 §2.2.\nasync function _hkdfExtract(\n subtle: SubtleCrypto,\n salt: Uint8Array,\n ikm: Uint8Array\n): Promise<Uint8Array<ArrayBuffer>> {\n const effectiveSalt = salt.length === 0 ? new Uint8Array(32) : salt;\n return _hmacSha256(subtle, effectiveSalt, ikm);\n}\n\n// RFC 5869 HKDF-Expand(PRK, info, L) — iterative HMAC expansion.\nasync function _hkdfExpand(\n subtle: SubtleCrypto,\n prk: Uint8Array,\n info: Uint8Array,\n length: number\n): Promise<Uint8Array<ArrayBuffer>> {\n if (length > 255 * 32) {\n throw new Error(`HKDF-Expand: requested length ${length} exceeds maximum 8160 bytes (255 * HashLen)`);\n }\n const n = Math.ceil(length / 32);\n const buffer = new ArrayBuffer(length);\n const okm = new Uint8Array(buffer);\n let prev: Uint8Array<ArrayBuffer> = new Uint8Array(new ArrayBuffer(0));\n let offset = 0;\n for (let i = 1; i <= n; i++) {\n prev = await _hmacSha256(subtle, prk, _concat(prev, info, new Uint8Array([i])));\n const toCopy = Math.min(32, length - offset);\n okm.set(prev.subarray(0, toCopy), offset);\n offset += toCopy;\n }\n return okm;\n}\n\n// RFC 9180 §4 LabeledExtract — HKDF-Extract with HPKE-v1 domain label.\n// labeled_ikm = \"HPKE-v1\" || suite_id || label || ikm\n// Extract(salt, labeled_ikm)\nasync function _labeledExtract(\n subtle: SubtleCrypto,\n suiteId: Uint8Array,\n salt: Uint8Array,\n label: string,\n ikm: Uint8Array\n): Promise<Uint8Array<ArrayBuffer>> {\n const labeledIkm = _concat(_HPKE_VERSION, suiteId, new TextEncoder().encode(label), ikm);\n return _hkdfExtract(subtle, salt, labeledIkm);\n}\n\n// RFC 9180 §4 LabeledExpand — HKDF-Expand with HPKE-v1 domain label.\n// labeled_info = I2OSP(L, 2) || \"HPKE-v1\" || suite_id || label || info\n// Expand(prk, labeled_info, L)\nasync function _labeledExpand(\n subtle: SubtleCrypto,\n suiteId: Uint8Array,\n prk: Uint8Array,\n label: string,\n info: Uint8Array,\n length: number\n): Promise<Uint8Array<ArrayBuffer>> {\n const labeledInfo = _concat(\n _i2osp(length, 2),\n _HPKE_VERSION,\n suiteId,\n new TextEncoder().encode(label),\n info\n );\n return _hkdfExpand(subtle, prk, labeledInfo, length);\n}\n\n// RFC 9180 §4.1 DHKEM Encap — generates ephemeral keypair, DH with recipient pubkey,\n// derives shared_secret via ExtractAndExpand.\n// NOTE: uses label \"eae_prk\" (not \"dh\") per RFC 9180 §4.1 ExtractAndExpand.\nasync function _kemEncap(\n subtle: SubtleCrypto,\n recipientPublicKey: CryptoKey\n): Promise<{ sharedSecret: Uint8Array<ArrayBuffer>; enc: Uint8Array<ArrayBuffer> }> {\n const ephemeral = (await subtle.generateKey({ name: 'X25519' }, true, ['deriveBits'])) as CryptoKeyPair;\n const enc = new Uint8Array(await subtle.exportKey('raw', ephemeral.publicKey));\n const dh = new Uint8Array(\n await subtle.deriveBits({ name: 'X25519', public: recipientPublicKey }, ephemeral.privateKey, 256)\n );\n const pkRm = new Uint8Array(await subtle.exportKey('raw', recipientPublicKey));\n const kemContext = _concat(enc, pkRm);\n const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);\n const sharedSecret = await _labeledExpand(\n subtle,\n _KEM_SUITE_ID,\n eaePrk,\n 'shared_secret',\n kemContext,\n _N_SECRET\n );\n return { sharedSecret, enc };\n}\n\n// RFC 9180 §4.1 DHKEM Decap — deserializes enc, DH with recipient privkey,\n// derives same shared_secret via ExtractAndExpand.\n// Requires recipientPrivateKey to be extractable (JWK export needed for pkRm).\nasync function _kemDecap(\n subtle: SubtleCrypto,\n enc: Uint8Array,\n recipientPrivateKey: CryptoKey\n): Promise<Uint8Array<ArrayBuffer>> {\n const pkE = await subtle.importKey('raw', _toBufferView(enc), { name: 'X25519' }, true, []);\n const dh = new Uint8Array(\n await subtle.deriveBits({ name: 'X25519', public: pkE }, recipientPrivateKey, 256)\n );\n // Recover recipient's own public key from JWK x field (base64url-encoded raw X25519 public key).\n const jwk = (await subtle.exportKey('jwk', recipientPrivateKey)) as JsonWebKey;\n /* c8 ignore next 3 - defensive: X25519 JWK always has an x field; unreachable via public API */\n if (!jwk.x) {\n throw new Error('HPKE Decap: failed to extract public key bytes from recipient private key JWK');\n }\n const pkRm = _base64UrlDecode(jwk.x);\n const kemContext = _concat(enc, pkRm);\n const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);\n return _labeledExpand(subtle, _KEM_SUITE_ID, eaePrk, 'shared_secret', kemContext, _N_SECRET);\n}\n\n// RFC 9180 §5 KeySchedule (base mode, psk = b\"\", psk_id = b\"\").\nasync function _keyScheduleBase(\n subtle: SubtleCrypto,\n sharedSecret: Uint8Array,\n info: Uint8Array\n): Promise<{ key: Uint8Array<ArrayBuffer>; baseNonce: Uint8Array<ArrayBuffer> }> {\n const empty = new Uint8Array(0);\n const pskIdHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'psk_id_hash', empty);\n const infoHash = await _labeledExtract(subtle, _SUITE_ID, empty, 'info_hash', info);\n const ksContext = _concat(new Uint8Array([_MODE_BASE]), pskIdHash, infoHash);\n const prk = await _labeledExtract(subtle, _SUITE_ID, sharedSecret, 'secret', empty);\n const key = await _labeledExpand(subtle, _SUITE_ID, prk, 'key', ksContext, _N_K);\n const baseNonce = await _labeledExpand(subtle, _SUITE_ID, prk, 'base_nonce', ksContext, _N_N);\n return { key, baseNonce };\n}\n\n// ---- Public types ----\n\n/**\n * Output of {@link HpkeProvider.sealBase}.\n *\n * The `ciphertext` field includes the 16-byte AES-256-GCM authentication tag\n * appended by Web Crypto's `encrypt()` operation: `length = plaintext.length + 16`.\n * @public\n */\nexport interface IHpkeSealResult {\n /**\n * Encapsulated key — 32-byte raw X25519 ephemeral public key (`enc` in RFC 9180).\n * Must be transmitted to the recipient alongside `ciphertext`.\n */\n readonly enc: Uint8Array;\n\n /**\n * AES-256-GCM ciphertext with the 16-byte authentication tag appended.\n * Length = `plaintext.length + 16`.\n */\n readonly ciphertext: Uint8Array;\n}\n\n// ---- Public class ----\n\n/**\n * HPKE base mode (RFC 9180) — `DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM`.\n *\n * Class-based provider that captures a `SubtleCrypto` instance at construction,\n * matching the existing `NodeCryptoProvider` / `BrowserCryptoProvider` / `KeyStore`\n * factory pattern used throughout `@fgv/ts-extras/crypto-utils`.\n *\n * **Node.js usage:**\n * ```typescript\n * import * as crypto from 'crypto';\n * const hpke = HpkeProvider.create(crypto.webcrypto.subtle).orThrow();\n * ```\n *\n * **Browser usage:**\n * ```typescript\n * const hpke = HpkeProvider.create(globalThis.crypto.subtle).orThrow();\n * ```\n *\n * **Runtime requirements:** Node.js 20+ (X25519 in `crypto.webcrypto`);\n * Chrome 113+, Safari 16.4+, Firefox 118+ (X25519 added to Web Crypto in 2023).\n * @public\n */\nexport class HpkeProvider {\n private readonly _subtle: SubtleCrypto;\n\n private constructor(subtle: SubtleCrypto) {\n this._subtle = subtle;\n }\n\n /**\n * Creates an `HpkeProvider` bound to the given `SubtleCrypto` instance.\n *\n * @param subtle - Web Crypto SubtleCrypto instance.\n * Node.js: `(await import('crypto')).webcrypto.subtle`.\n * Browser: `globalThis.crypto.subtle`.\n * @returns `Success` with the provider, or `Failure` if construction fails.\n */\n public static create(subtle: SubtleCrypto): Result<HpkeProvider> {\n return captureResult(() => new HpkeProvider(subtle));\n }\n\n /**\n * HPKE base-mode seal (sender side). RFC 9180 §6.1.\n *\n * Generates a fresh ephemeral X25519 keypair, runs DHKEM Encap to produce a\n * shared secret and `enc` (32-byte raw ephemeral public key), derives the AEAD\n * key and nonce deterministically via the RFC 9180 key schedule, then encrypts\n * `plaintext` with AES-256-GCM.\n *\n * @param recipientPublicKey - Recipient's X25519 public `CryptoKey`\n * (`algorithm.name === 'X25519'`, `type === 'public'`, **`extractable: true`**).\n * Must be extractable — DHKEM Encap calls `exportKey('raw', ...)` on this key to\n * build the KEM shared-secret context. Keys imported with `extractable: false` will\n * cause this method to return a `Failure`.\n * @param info - Context-binding bytes. **Load-bearing — no default.**\n * Binds this ciphertext to a specific application context, preventing replay\n * across different contexts sharing the same recipient keypair.\n * Use `new TextEncoder().encode('myapp/v1/use-case\\x00' + contextId)` pattern.\n * Never pass an empty array in production: empty `info` provides no context binding.\n * @param aad - Additional authenticated data. Integrity-protected but not encrypted.\n * `new Uint8Array(0)` is valid when no AAD is needed.\n * @param plaintext - Bytes to encrypt. `new Uint8Array(0)` is valid.\n * @returns `Success` with `{ enc, ciphertext }`, or `Failure` with error context.\n */\n public async sealBase(\n recipientPublicKey: CryptoKey,\n info: Uint8Array,\n aad: Uint8Array,\n plaintext: Uint8Array\n ): Promise<Result<IHpkeSealResult>> {\n const result = await captureAsyncResult(async () => {\n const { sharedSecret, enc } = await _kemEncap(this._subtle, recipientPublicKey);\n const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);\n const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt']);\n const ct = await this._subtle.encrypt(\n { name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) },\n aesKey,\n _toBufferView(plaintext)\n );\n return { enc, ciphertext: new Uint8Array(ct) };\n });\n return result.withErrorFormat((e: string) => `HPKE sealBase failed: ${e}`);\n }\n\n /**\n * HPKE base-mode open (recipient side). RFC 9180 §6.1.\n *\n * Decapsulates `enc` using the recipient's X25519 private key, derives the same\n * AEAD key and nonce from the shared secret and `info`, then authenticates and\n * decrypts `ciphertext` with AES-256-GCM.\n *\n * Returns `Failure` on any of:\n * - Wrong private key (different DH output → different key derivation)\n * - Wrong `info` (different key schedule context → different AEAD key)\n * - Wrong `aad` (AES-GCM authentication fails)\n * - Tampered `ciphertext` or `enc` (authentication fails or DH fails)\n * - `enc` not exactly 32 bytes\n * - `ciphertext` shorter than 16 bytes (no room for authentication tag)\n *\n * @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`\n * (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).\n * **Must be extractable** (`extractable: true`) — the recipient's public key bytes\n * are recovered from the JWK `x` field during Decap.\n * @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.\n * @param aad - Must exactly match `aad` from `sealBase`.\n * @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.\n * @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.\n * @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.\n */\n public async openBase(\n recipientPrivateKey: CryptoKey,\n info: Uint8Array,\n aad: Uint8Array,\n enc: Uint8Array,\n ciphertext: Uint8Array\n ): Promise<Result<Uint8Array>> {\n if (enc.length !== _N_PK) {\n return fail(`HPKE openBase: enc must be ${_N_PK} bytes, got ${enc.length}`);\n }\n if (ciphertext.length < _N_T) {\n return fail(\n `HPKE openBase: ciphertext too short (minimum ${_N_T} bytes for auth tag, got ${ciphertext.length})`\n );\n }\n const result = await captureAsyncResult(async () => {\n const sharedSecret = await _kemDecap(this._subtle, enc, recipientPrivateKey);\n const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);\n const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['decrypt']);\n const pt = await this._subtle.decrypt(\n { name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) },\n aesKey,\n _toBufferView(ciphertext)\n );\n return new Uint8Array(pt);\n });\n return result.withErrorFormat((e: string) => `HPKE openBase failed: ${e}`);\n }\n\n /**\n * HKDF-SHA256 key derivation (RFC 5869). Extract-then-Expand using SHA-256.\n *\n * This is raw RFC 5869 HKDF — it does **not** use RFC 9180's labeled variants.\n * The HPKE key schedule internally uses labeled HKDF; this method is the unlabeled\n * version for callers that need standalone key derivation.\n *\n * @param secret - Input keying material (IKM). Any length.\n * @param salt - Optional salt. Use `new Uint8Array(0)` if no salt is available\n * (RFC 5869: 32 zero bytes are used internally when salt is empty).\n * @param info - Context / application-binding bytes. Any length.\n * @param length - Number of output bytes to derive. Maximum 8160 bytes (255 × 32).\n * @returns `Success` with derived bytes, or `Failure` with error context.\n */\n public async hkdf(\n secret: Uint8Array,\n salt: Uint8Array,\n info: Uint8Array,\n length: number\n ): Promise<Result<Uint8Array>> {\n const result = await captureAsyncResult(async () => {\n const prk = await _hkdfExtract(this._subtle, salt, secret);\n return _hkdfExpand(this._subtle, prk, info, length);\n });\n return result.withErrorFormat((e: string) => `HKDF failed: ${e}`);\n }\n\n /**\n * Encodes an {@link IHpkeSealResult} as a single contiguous byte array for wire transport.\n *\n * Format: `enc` (32 bytes, fixed) || `ciphertext` (variable length).\n * The 32-byte `enc` length is fixed for X25519; the split point is unambiguous.\n *\n * @param result - The output of {@link HpkeProvider.sealBase}.\n * @returns Concatenated bytes: `enc || ciphertext`.\n */\n public static encodeEnvelope(result: IHpkeSealResult): Uint8Array {\n return _concat(result.enc, result.ciphertext);\n }\n\n /**\n * Decodes an envelope produced by {@link HpkeProvider.encodeEnvelope}.\n *\n * Validates that the buffer is at least 48 bytes (32-byte enc + 16-byte minimum\n * ciphertext containing the AES-GCM auth tag; zero-length plaintext is the minimum\n * meaningful case).\n *\n * @param envelope - Envelope bytes from `encodeEnvelope`.\n * @returns `Success` with `{ enc, ciphertext }`, or `Failure` if malformed.\n */\n public static decodeEnvelope(envelope: Uint8Array): Result<IHpkeSealResult> {\n const minLen = _N_PK + _N_T;\n if (envelope.length < minLen) {\n return fail(\n `HPKE decodeEnvelope: envelope too short (minimum ${minLen} bytes, got ${envelope.length})`\n );\n }\n return succeed({\n enc: envelope.slice(0, _N_PK),\n ciphertext: envelope.slice(_N_PK)\n });\n }\n}\n"]}
|
|
@@ -13,4 +13,5 @@ export { DirectEncryptionProvider, IDirectEncryptionProviderParams } from './dir
|
|
|
13
13
|
export { IKeyPairAlgorithmParams, keyPairAlgorithmParams } from './keyPairAlgorithmParams';
|
|
14
14
|
export { createEncryptedFile, decryptFile, fromBase64, ICreateEncryptedFileParams, toBase64, tryDecryptFile } from './encryptedFile';
|
|
15
15
|
export { exportPublicKeyAsMultibaseSpki, importPublicKeyFromMultibaseSpki, multibaseBase64UrlDecode, multibaseBase64UrlEncode } from './spkiHelpers';
|
|
16
|
+
export { HpkeProvider, IHpkeSealResult } from './hpkeProvider';
|
|
16
17
|
//# sourceMappingURL=index.browser.d.ts.map
|
|
@@ -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"}
|
|
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"}
|