@fgv/ts-extras 5.1.0-40 → 5.1.0-41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packlets/crypto-utils/hpkeProvider.js +34 -12
- package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -1
- package/dist/packlets/crypto-utils/index.browser.js +1 -1
- package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
- package/dist/packlets/crypto-utils/index.js +1 -1
- package/dist/packlets/crypto-utils/index.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/converters.js +3 -4
- package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/keyStore.js +155 -16
- package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/model.js +8 -3
- package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
- package/dist/packlets/crypto-utils/model.js.map +1 -1
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js +66 -0
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
- package/dist/packlets/crypto-utils/spkiHelpers.js +31 -0
- package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -1
- package/dist/ts-extras.d.ts +289 -7
- package/lib/packlets/crypto-utils/hpkeProvider.d.ts +11 -3
- package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/hpkeProvider.js +34 -12
- package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.d.ts +1 -1
- package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.js +2 -1
- package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
- package/lib/packlets/crypto-utils/index.d.ts +1 -1
- package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.js +2 -1
- package/lib/packlets/crypto-utils/index.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/converters.js +2 -3
- package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +68 -3
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.js +154 -15
- package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/model.d.ts +75 -2
- package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/model.js +9 -4
- package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
- package/lib/packlets/crypto-utils/model.d.ts +89 -0
- package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/model.js.map +1 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +25 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js +66 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts +15 -0
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/spkiHelpers.js +32 -0
- package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spkiHelpers.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/spkiHelpers.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, fail, succeed } from '@fgv/ts-utils';\nimport { ICryptoProvider, KeyPairAlgorithm, MultibaseSpkiPublicKey } from './model';\n\n/**\n * Shared shape check for a base64url (no-padding) body: only base64url alphabet\n * characters (`A-Z`, `a-z`, `0-9`, `-`, `_`) and a length that is never `% 4 === 1`\n * (an impossible base64 remainder). Factored so the decoder and the\n * {@link isValidMultibaseSpkiPublicKey} guard agree on exactly one rule.\n */\nfunction isBase64UrlNoPadBody(body: string): boolean {\n return /^[A-Za-z0-9_-]*$/.test(body) && body.length % 4 !== 1;\n}\n\n/**\n * Encodes a `Uint8Array` as a base64url (no-padding) string (RFC 4648 §5).\n *\n * The body uses the base64url alphabet (`+` → `-`, `/` → `_`) and trailing `=`\n * padding is stripped. This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlEncode} when a multibase-`'m'`-prefixed value is required.\n *\n * @param data - The binary data to encode.\n * @returns The base64url-no-pad string.\n * @public\n */\nexport function base64UrlNoPadEncode(data: Uint8Array): string {\n let base64: string;\n if (typeof Buffer !== 'undefined') {\n base64 = Buffer.from(data).toString('base64');\n /* c8 ignore start - browser-only: btoa path not available in Node tests */\n } else {\n let binary = '';\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n base64 = btoa(binary);\n }\n /* c8 ignore stop */\n // Convert to base64url: + → -, / → _, strip = padding\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Decodes a base64url (no-padding) string (RFC 4648 §5) back to a `Uint8Array`.\n *\n * This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlDecode} to decode a multibase-`'m'`-prefixed value.\n *\n * @param encoded - The base64url-no-pad body to decode.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function base64UrlNoPadDecode(encoded: string): Result<Uint8Array> {\n if (!isBase64UrlNoPadBody(encoded)) {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n // Convert base64url back to standard base64 and restore padding\n const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n try {\n let bytes: Uint8Array;\n if (typeof Buffer !== 'undefined') {\n bytes = new Uint8Array(Buffer.from(padded, 'base64'));\n /* c8 ignore start - browser-only: atob path not available in Node tests */\n } else {\n const binary = atob(padded);\n bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n }\n /* c8 ignore stop */\n return succeed(bytes);\n /* c8 ignore next 3 - defensive: shape check above prevents invalid chars from reaching here */\n } catch {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n}\n\n/**\n * The structural *shape* of a {@link CryptoUtils.MultibaseSpkiPublicKey}: the\n * multibase `'m'` prefix followed by a non-empty base64url-no-pad body\n * (`A-Z`, `a-z`, `0-9`, `-`, `_`).\n *\n * This is a shape prefilter, not full validation: it does not decode the DER\n * SPKI or verify the key material or algorithm (that happens in\n * {@link CryptoUtils.importPublicKeyFromMultibaseSpki}). The authoritative guard\n * is {@link CryptoUtils.isValidMultibaseSpkiPublicKey}, which enforces this\n * pattern **and** additionally rejects a body whose length is an impossible\n * base64 remainder. Exposed for callers that need the pattern directly (e.g. a\n * JSON-schema `pattern` field); prefer the guard/converter for validation.\n *\n * @public\n */\nexport const MultibaseSpkiPublicKeyRegExp: RegExp = /^m[A-Za-z0-9_-]+$/;\n\n/**\n * Type guard for {@link CryptoUtils.MultibaseSpkiPublicKey}: a string matching\n * {@link CryptoUtils.MultibaseSpkiPublicKeyRegExp} (multibase `'m'` prefix + a\n * non-empty base64url-no-pad body) whose body also satisfies the base64url-no-pad\n * length rule shared with {@link CryptoUtils.base64UrlNoPadDecode}. This is a\n * structural *shape* check, not full validation — it does not decode the DER SPKI\n * or verify the key material/algorithm (a malformed-but-well-shaped string fails\n * later, with clear context, in {@link CryptoUtils.importPublicKeyFromMultibaseSpki}).\n *\n * @param value - The value to test.\n * @returns `true` if `value` is a well-formed multibase SPKI public key string.\n * @public\n */\nexport function isValidMultibaseSpkiPublicKey(value: unknown): value is MultibaseSpkiPublicKey {\n return (\n typeof value === 'string' &&\n MultibaseSpkiPublicKeyRegExp.test(value) &&\n isBase64UrlNoPadBody(value.slice(1))\n );\n}\n\n/**\n * Encodes a `Uint8Array` as a multibase base64url (no-padding) string.\n *\n * The multibase prefix `'m'` identifies the encoding as RFC 4648 base64url\n * without padding. The body uses base64url alphabet: `+` → `-`, `/` → `_`,\n * and trailing `=` padding is stripped.\n *\n * @param data - The binary data to encode.\n * @returns A multibase-prefixed base64url string (`'m' + base64url-no-pad`).\n * @public\n */\nexport function multibaseBase64UrlEncode(data: Uint8Array): string {\n return 'm' + base64UrlNoPadEncode(data);\n}\n\n/**\n * Decodes a multibase base64url (no-padding) string back to a `Uint8Array`.\n *\n * Validates that the first character is `'m'` (the multibase prefix for\n * RFC 4648 base64url without padding), then decodes the remaining body.\n *\n * @param encoded - A multibase-prefixed base64url string.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function multibaseBase64UrlDecode(encoded: string): Result<Uint8Array> {\n if (!encoded.startsWith('m')) {\n return fail(\n `multibaseBase64UrlDecode: invalid multibase prefix '${\n encoded[0] ?? '(empty)'\n }' — expected 'm' (base64url)`\n );\n }\n // Intentionally pin the exact original message (the delegate has a single failure\n // mode) to keep this established public function byte-for-byte behavior-preserving\n // after the extract-and-delegate refactor — do not \"fix\" this into composing the\n // delegate's message without updating the delegation-equivalence tests.\n return base64UrlNoPadDecode(encoded.slice(1)).withErrorFormat(\n () => `multibaseBase64UrlDecode: malformed base64url body`\n );\n}\n\n/**\n * Exports a public `CryptoKey` as a multibase base64url-encoded SPKI blob.\n *\n * The SPKI (SubjectPublicKeyInfo) format is the standard DER-encoded structure\n * for public keys defined in RFC 5280, RFC 5480, and RFC 8410. It is\n * algorithm-agnostic and suitable for storage and transmission.\n *\n * @param key - The `CryptoKey` to export. Must have `key.type === 'public'`.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the export operation.\n * @returns `Success` with the multibase SPKI string, or `Failure` with error context.\n * @public\n */\nexport async function exportPublicKeyAsMultibaseSpki(\n key: CryptoKey,\n provider: ICryptoProvider\n): Promise<Result<MultibaseSpkiPublicKey>> {\n return (await provider.exportPublicKeySpki(key))\n .withErrorFormat((e) => `exportPublicKeyAsMultibaseSpki: ${e}`)\n .onSuccess((buf) =>\n // The output is a freshly-built valid multibase SPKI string (`'m'` prefix +\n // base64url-no-pad body), so brand it at the construction site — no re-validation needed.\n succeed(multibaseBase64UrlEncode(buf) as MultibaseSpkiPublicKey)\n );\n}\n\n/**\n * Imports a public key from a multibase base64url-encoded SPKI blob.\n *\n * Decodes the multibase prefix, decodes the base64url body, then uses\n * the provider to import the key with the algorithm parameters from\n * {@link CryptoUtils.keyPairAlgorithmParams}.\n *\n * Accepts a plain `string` (not only a branded {@link CryptoUtils.MultibaseSpkiPublicKey}),\n * so callers holding an unbranded value read from storage or the wire can import\n * it directly; a malformed value fails with error context rather than throwing.\n *\n * @param encoded - A multibase SPKI string produced by {@link CryptoUtils.exportPublicKeyAsMultibaseSpki}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm} the key was generated for.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the import operation.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n * @public\n */\nexport async function importPublicKeyFromMultibaseSpki(\n encoded: string,\n algorithm: KeyPairAlgorithm,\n provider: ICryptoProvider\n): Promise<Result<CryptoKey>> {\n const decodeResult = multibaseBase64UrlDecode(encoded);\n if (decodeResult.isFailure()) {\n return fail(`importPublicKeyFromMultibaseSpki: ${decodeResult.message}`);\n }\n return (await provider.importPublicKeySpki(decodeResult.value, algorithm)).withErrorFormat(\n (e) => `importPublicKeyFromMultibaseSpki: ${e}`\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"spkiHelpers.js","sourceRoot":"","sources":["../../../src/packlets/crypto-utils/spkiHelpers.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, fail, succeed } from '@fgv/ts-utils';\nimport { ICryptoProvider, KeyPairAlgorithm, MultibaseSpkiPublicKey } from './model';\n\n/**\n * Shared shape check for a base64url (no-padding) body: only base64url alphabet\n * characters (`A-Z`, `a-z`, `0-9`, `-`, `_`) and a length that is never `% 4 === 1`\n * (an impossible base64 remainder). Factored so the decoder and the\n * {@link isValidMultibaseSpkiPublicKey} guard agree on exactly one rule.\n */\nfunction isBase64UrlNoPadBody(body: string): boolean {\n return /^[A-Za-z0-9_-]*$/.test(body) && body.length % 4 !== 1;\n}\n\n/**\n * Encodes a `Uint8Array` as a base64url (no-padding) string (RFC 4648 §5).\n *\n * The body uses the base64url alphabet (`+` → `-`, `/` → `_`) and trailing `=`\n * padding is stripped. This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlEncode} when a multibase-`'m'`-prefixed value is required.\n *\n * @param data - The binary data to encode.\n * @returns The base64url-no-pad string.\n * @public\n */\nexport function base64UrlNoPadEncode(data: Uint8Array): string {\n let base64: string;\n if (typeof Buffer !== 'undefined') {\n base64 = Buffer.from(data).toString('base64');\n /* c8 ignore start - browser-only: btoa path not available in Node tests */\n } else {\n let binary = '';\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n base64 = btoa(binary);\n }\n /* c8 ignore stop */\n // Convert to base64url: + → -, / → _, strip = padding\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Decodes a base64url (no-padding) string (RFC 4648 §5) back to a `Uint8Array`.\n *\n * This is the bare primitive with no multibase prefix; use\n * {@link CryptoUtils.multibaseBase64UrlDecode} to decode a multibase-`'m'`-prefixed value.\n *\n * @param encoded - The base64url-no-pad body to decode.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function base64UrlNoPadDecode(encoded: string): Result<Uint8Array> {\n if (!isBase64UrlNoPadBody(encoded)) {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n // Convert base64url back to standard base64 and restore padding\n const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n try {\n let bytes: Uint8Array;\n if (typeof Buffer !== 'undefined') {\n bytes = new Uint8Array(Buffer.from(padded, 'base64'));\n /* c8 ignore start - browser-only: atob path not available in Node tests */\n } else {\n const binary = atob(padded);\n bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n }\n /* c8 ignore stop */\n return succeed(bytes);\n /* c8 ignore next 3 - defensive: shape check above prevents invalid chars from reaching here */\n } catch {\n return fail(`base64UrlNoPadDecode: malformed base64url body`);\n }\n}\n\n/**\n * The structural *shape* of a {@link CryptoUtils.MultibaseSpkiPublicKey}: the\n * multibase `'m'` prefix followed by a non-empty base64url-no-pad body\n * (`A-Z`, `a-z`, `0-9`, `-`, `_`).\n *\n * This is a shape prefilter, not full validation: it does not decode the DER\n * SPKI or verify the key material or algorithm (that happens in\n * {@link CryptoUtils.importPublicKeyFromMultibaseSpki}). The authoritative guard\n * is {@link CryptoUtils.isValidMultibaseSpkiPublicKey}, which enforces this\n * pattern **and** additionally rejects a body whose length is an impossible\n * base64 remainder. Exposed for callers that need the pattern directly (e.g. a\n * JSON-schema `pattern` field); prefer the guard/converter for validation.\n *\n * @public\n */\nexport const MultibaseSpkiPublicKeyRegExp: RegExp = /^m[A-Za-z0-9_-]+$/;\n\n/**\n * Type guard for {@link CryptoUtils.MultibaseSpkiPublicKey}: a string matching\n * {@link CryptoUtils.MultibaseSpkiPublicKeyRegExp} (multibase `'m'` prefix + a\n * non-empty base64url-no-pad body) whose body also satisfies the base64url-no-pad\n * length rule shared with {@link CryptoUtils.base64UrlNoPadDecode}. This is a\n * structural *shape* check, not full validation — it does not decode the DER SPKI\n * or verify the key material/algorithm (a malformed-but-well-shaped string fails\n * later, with clear context, in {@link CryptoUtils.importPublicKeyFromMultibaseSpki}).\n *\n * @param value - The value to test.\n * @returns `true` if `value` is a well-formed multibase SPKI public key string.\n * @public\n */\nexport function isValidMultibaseSpkiPublicKey(value: unknown): value is MultibaseSpkiPublicKey {\n return (\n typeof value === 'string' &&\n MultibaseSpkiPublicKeyRegExp.test(value) &&\n isBase64UrlNoPadBody(value.slice(1))\n );\n}\n\n/**\n * Encodes a `Uint8Array` as a multibase base64url (no-padding) string.\n *\n * The multibase prefix `'m'` identifies the encoding as RFC 4648 base64url\n * without padding. The body uses base64url alphabet: `+` → `-`, `/` → `_`,\n * and trailing `=` padding is stripped.\n *\n * @param data - The binary data to encode.\n * @returns A multibase-prefixed base64url string (`'m' + base64url-no-pad`).\n * @public\n */\nexport function multibaseBase64UrlEncode(data: Uint8Array): string {\n return 'm' + base64UrlNoPadEncode(data);\n}\n\n/**\n * Decodes a multibase base64url (no-padding) string back to a `Uint8Array`.\n *\n * Validates that the first character is `'m'` (the multibase prefix for\n * RFC 4648 base64url without padding), then decodes the remaining body.\n *\n * @param encoded - A multibase-prefixed base64url string.\n * @returns `Success` with the decoded bytes, or `Failure` with error context.\n * @public\n */\nexport function multibaseBase64UrlDecode(encoded: string): Result<Uint8Array> {\n if (!encoded.startsWith('m')) {\n return fail(\n `multibaseBase64UrlDecode: invalid multibase prefix '${\n encoded[0] ?? '(empty)'\n }' — expected 'm' (base64url)`\n );\n }\n // Intentionally pin the exact original message (the delegate has a single failure\n // mode) to keep this established public function byte-for-byte behavior-preserving\n // after the extract-and-delegate refactor — do not \"fix\" this into composing the\n // delegate's message without updating the delegation-equivalence tests.\n return base64UrlNoPadDecode(encoded.slice(1)).withErrorFormat(\n () => `multibaseBase64UrlDecode: malformed base64url body`\n );\n}\n\n/**\n * Exports a public `CryptoKey` as a multibase base64url-encoded SPKI blob.\n *\n * The SPKI (SubjectPublicKeyInfo) format is the standard DER-encoded structure\n * for public keys defined in RFC 5280, RFC 5480, and RFC 8410. It is\n * algorithm-agnostic and suitable for storage and transmission.\n *\n * @param key - The `CryptoKey` to export. Must have `key.type === 'public'`.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the export operation.\n * @returns `Success` with the multibase SPKI string, or `Failure` with error context.\n * @public\n */\nexport async function exportPublicKeyAsMultibaseSpki(\n key: CryptoKey,\n provider: ICryptoProvider\n): Promise<Result<MultibaseSpkiPublicKey>> {\n return (await provider.exportPublicKeySpki(key))\n .withErrorFormat((e) => `exportPublicKeyAsMultibaseSpki: ${e}`)\n .onSuccess((buf) =>\n // The output is a freshly-built valid multibase SPKI string (`'m'` prefix +\n // base64url-no-pad body), so brand it at the construction site — no re-validation needed.\n succeed(multibaseBase64UrlEncode(buf) as MultibaseSpkiPublicKey)\n );\n}\n\n/**\n * Imports a public key from a multibase base64url-encoded SPKI blob.\n *\n * Decodes the multibase prefix, decodes the base64url body, then uses\n * the provider to import the key with the algorithm parameters from\n * {@link CryptoUtils.keyPairAlgorithmParams}.\n *\n * Accepts a plain `string` (not only a branded {@link CryptoUtils.MultibaseSpkiPublicKey}),\n * so callers holding an unbranded value read from storage or the wire can import\n * it directly; a malformed value fails with error context rather than throwing.\n *\n * @param encoded - A multibase SPKI string produced by {@link CryptoUtils.exportPublicKeyAsMultibaseSpki}.\n * @param algorithm - The {@link CryptoUtils.KeyPairAlgorithm} the key was generated for.\n * @param provider - The {@link CryptoUtils.ICryptoProvider} to use for the import operation.\n * @returns `Success` with the imported public `CryptoKey`, or `Failure` with error context.\n * @public\n */\nexport async function importPublicKeyFromMultibaseSpki(\n encoded: string,\n algorithm: KeyPairAlgorithm,\n provider: ICryptoProvider\n): Promise<Result<CryptoKey>> {\n const decodeResult = multibaseBase64UrlDecode(encoded);\n if (decodeResult.isFailure()) {\n return fail(`importPublicKeyFromMultibaseSpki: ${decodeResult.message}`);\n }\n return (await provider.importPublicKeySpki(decodeResult.value, algorithm)).withErrorFormat(\n (e) => `importPublicKeyFromMultibaseSpki: ${e}`\n );\n}\n\n// DER prefix for an X25519 SubjectPublicKeyInfo: SEQUENCE { SEQUENCE { OID id-X25519 }, BIT STRING }.\n// SEQUENCE(42) / SEQUENCE(5) / OID 1.3.101.110 (id-X25519) / BIT STRING(33, 0 unused bits).\nconst _X25519_SPKI_PREFIX: ReadonlyArray<number> = [\n 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00\n];\nconst _X25519_SPKI_LENGTH: number = _X25519_SPKI_PREFIX.length + 32;\n\n/**\n * Strips the fixed DER prefix from an X25519 SubjectPublicKeyInfo blob, returning the raw\n * 32-byte public key.\n *\n * An X25519 SPKI is always exactly 44 bytes: a fixed 12-byte prefix (SEQUENCE / AlgorithmIdentifier\n * with OID 1.3.101.110 / BIT STRING) followed by the 32-byte raw key. Use this to convert a\n * SPKI-held recipient public key into the raw form accepted by {@link HpkeProvider.openBase}'s\n * `recipientPublicKey` parameter.\n *\n * @param spki - The DER-encoded X25519 SubjectPublicKeyInfo bytes.\n * @returns `Success` with the raw 32-byte public key, or `Failure` if `spki` is not a\n * well-formed 44-byte X25519 SPKI blob.\n * @public\n */\nexport function spkiToRawX25519(spki: Uint8Array): Result<Uint8Array> {\n if (spki.length !== _X25519_SPKI_LENGTH) {\n return fail(`spkiToRawX25519: expected ${_X25519_SPKI_LENGTH} bytes, got ${spki.length}`);\n }\n for (let i = 0; i < _X25519_SPKI_PREFIX.length; i++) {\n if (spki[i] !== _X25519_SPKI_PREFIX[i]) {\n return fail(`spkiToRawX25519: byte ${i} does not match the expected X25519 SPKI prefix`);\n }\n }\n return succeed(spki.slice(_X25519_SPKI_PREFIX.length));\n}\n"]}
|
package/dist/ts-extras.d.ts
CHANGED
|
@@ -381,6 +381,12 @@ declare const allKeyPairAlgorithms: ReadonlyArray<KeyPairAlgorithm>;
|
|
|
381
381
|
*/
|
|
382
382
|
declare const allKeyStoreAsymmetricSecretTypes: ReadonlyArray<KeyStoreAsymmetricSecretType>;
|
|
383
383
|
|
|
384
|
+
/**
|
|
385
|
+
* All recognized key store format versions (readable by the current library).
|
|
386
|
+
* @public
|
|
387
|
+
*/
|
|
388
|
+
declare const allKeyStoreFormats: ReadonlyArray<KeyStoreFormat>;
|
|
389
|
+
|
|
384
390
|
/**
|
|
385
391
|
* All valid key store secret types.
|
|
386
392
|
* @public
|
|
@@ -730,6 +736,7 @@ declare namespace CryptoUtils {
|
|
|
730
736
|
MultibaseSpkiPublicKeyRegExp,
|
|
731
737
|
multibaseBase64UrlDecode,
|
|
732
738
|
multibaseBase64UrlEncode,
|
|
739
|
+
spkiToRawX25519,
|
|
733
740
|
HpkeProvider,
|
|
734
741
|
IHpkeSealResult,
|
|
735
742
|
isEncryptedFile,
|
|
@@ -738,6 +745,7 @@ declare namespace CryptoUtils {
|
|
|
738
745
|
EncryptedFileFormat,
|
|
739
746
|
INamedSecret,
|
|
740
747
|
IEncryptionResult,
|
|
748
|
+
IEncryptBytesResult,
|
|
741
749
|
KeyPairAlgorithm,
|
|
742
750
|
IWrapBytesOptions,
|
|
743
751
|
IWrappedBytes,
|
|
@@ -1435,15 +1443,23 @@ declare class HpkeProvider {
|
|
|
1435
1443
|
*
|
|
1436
1444
|
* @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`
|
|
1437
1445
|
* (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).
|
|
1438
|
-
* **Must be extractable** (`extractable: true`)
|
|
1439
|
-
* are recovered from the JWK `x` field
|
|
1446
|
+
* **Must be extractable** (`extractable: true`) only when `recipientPublicKey` is not
|
|
1447
|
+
* supplied — the recipient's public key bytes are then recovered from the JWK `x` field
|
|
1448
|
+
* during Decap. When `recipientPublicKey` is supplied, `recipientPrivateKey` may be
|
|
1449
|
+
* non-extractable.
|
|
1440
1450
|
* @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.
|
|
1441
1451
|
* @param aad - Must exactly match `aad` from `sealBase`.
|
|
1442
1452
|
* @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.
|
|
1443
1453
|
* @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.
|
|
1454
|
+
* @param recipientPublicKey - Optional raw 32-byte X25519 public key matching
|
|
1455
|
+
* `recipientPrivateKey` (`pkRm` in RFC 9180 §4.1). Public material — supplying it lets
|
|
1456
|
+
* Decap build `kem_context` without exporting `recipientPrivateKey` to JWK, so
|
|
1457
|
+
* `recipientPrivateKey` no longer needs to be extractable. A mismatched value only breaks
|
|
1458
|
+
* the caller's own decryption (AEAD authentication fails) — it cannot be used to attack
|
|
1459
|
+
* another party's ciphertext.
|
|
1444
1460
|
* @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.
|
|
1445
1461
|
*/
|
|
1446
|
-
openBase(recipientPrivateKey: CryptoKey, info: Uint8Array, aad: Uint8Array, enc: Uint8Array, ciphertext: Uint8Array): Promise<Result<Uint8Array>>;
|
|
1462
|
+
openBase(recipientPrivateKey: CryptoKey, info: Uint8Array, aad: Uint8Array, enc: Uint8Array, ciphertext: Uint8Array, recipientPublicKey?: Uint8Array): Promise<Result<Uint8Array>>;
|
|
1447
1463
|
/**
|
|
1448
1464
|
* HKDF-SHA256 key derivation (RFC 5869). Extract-then-Expand using SHA-256.
|
|
1449
1465
|
*
|
|
@@ -1508,6 +1524,28 @@ declare interface IAddKeyPairOptions {
|
|
|
1508
1524
|
* downgrading.
|
|
1509
1525
|
*/
|
|
1510
1526
|
readonly extractable?: boolean;
|
|
1527
|
+
/**
|
|
1528
|
+
* Opt in to private-key escrow. When `true`, an encrypted copy of the
|
|
1529
|
+
* private key (as a JWK) is carried inside the vault entry
|
|
1530
|
+
* (`escrowedPrivateKeyJwk`), enabling cross-device recovery from the vault
|
|
1531
|
+
* file plus the master password alone via
|
|
1532
|
+
* `getKeyPair(name, { rehydrate: true })`.
|
|
1533
|
+
*
|
|
1534
|
+
* Orthogonal to `extractable`: the escrow copy is always captured (the
|
|
1535
|
+
* transient keypair is generated extractable so it can be exported to JWK),
|
|
1536
|
+
* while the LIVE stored key's extractability still follows the normal rule
|
|
1537
|
+
* (`extractable` override, else the backend default). So escrow does not
|
|
1538
|
+
* change how extractable the day-to-day key is — only whether a recovery
|
|
1539
|
+
* copy is retained in the vault.
|
|
1540
|
+
*
|
|
1541
|
+
* @defaultValue false — no escrow copy is written (today's behavior).
|
|
1542
|
+
*
|
|
1543
|
+
* SECURITY: escrow makes the master password the sole gate protecting a
|
|
1544
|
+
* private key that would otherwise be unrecoverable from the vault. Use a
|
|
1545
|
+
* strong KDF (Argon2id-derived or high-iteration PBKDF2) for escrow-bearing
|
|
1546
|
+
* stores. See the {@link CryptoUtils.KeyStore.KeyStore} class docs.
|
|
1547
|
+
*/
|
|
1548
|
+
readonly escrow?: boolean;
|
|
1511
1549
|
}
|
|
1512
1550
|
|
|
1513
1551
|
/**
|
|
@@ -2681,6 +2719,68 @@ declare interface ICryptoProvider {
|
|
|
2681
2719
|
* @returns Success with decrypted UTF-8 string, or Failure with error
|
|
2682
2720
|
*/
|
|
2683
2721
|
decrypt(encryptedData: Uint8Array, key: Uint8Array, iv: Uint8Array, authTag: Uint8Array): Promise<Result<string>>;
|
|
2722
|
+
/**
|
|
2723
|
+
* Encrypts raw bytes using AES-256-GCM with a **caller-supplied nonce** and
|
|
2724
|
+
* optional additional authenticated data (AAD).
|
|
2725
|
+
*
|
|
2726
|
+
* This is the raw-byte sibling of {@link CryptoUtils.ICryptoProvider.encrypt | encrypt}.
|
|
2727
|
+
* Unlike `encrypt`, it takes and returns `Uint8Array` (no UTF-8 coding), the
|
|
2728
|
+
* caller owns the nonce (rather than the provider generating one), and it
|
|
2729
|
+
* binds optional `aad` into the GCM authentication. Use it when you need to
|
|
2730
|
+
* bind context (e.g. an actor id, key version, or row kind) into the
|
|
2731
|
+
* authentication so a wrapped secret cannot be replayed across
|
|
2732
|
+
* users/versions/kinds, or when you manage nonces yourself.
|
|
2733
|
+
*
|
|
2734
|
+
* @remarks
|
|
2735
|
+
* **⚠️ NONCE UNIQUENESS IS THE CALLER'S RESPONSIBILITY AND IS CRITICAL.**
|
|
2736
|
+
* Because the caller supplies the nonce, this primitive cannot guarantee
|
|
2737
|
+
* uniqueness. Reusing a `(key, nonce)` pair for two different messages is
|
|
2738
|
+
* **catastrophic** for AES-GCM: it breaks confidentiality (the XOR of the two
|
|
2739
|
+
* plaintexts leaks) AND authentication (the GCM authentication key can be
|
|
2740
|
+
* recovered, letting an attacker forge tags for arbitrary messages under that
|
|
2741
|
+
* key). The caller MUST use a unique nonce for every message encrypted under a
|
|
2742
|
+
* given key — draw it from {@link CryptoUtils.ICryptoProvider.generateRandomBytes | generateRandomBytes(12)}
|
|
2743
|
+
* (12 random bytes has negligible collision probability well within a single
|
|
2744
|
+
* key's message budget) or from a strictly-increasing counter. Never hardcode
|
|
2745
|
+
* a nonce and never reuse one.
|
|
2746
|
+
*
|
|
2747
|
+
* @param key - 32-byte AES-256 key. Wrong lengths fail with error context.
|
|
2748
|
+
* @param nonce - 12-byte (96-bit) GCM nonce. MUST be unique per message under
|
|
2749
|
+
* `key` (see the nonce-uniqueness warning above). Wrong lengths fail with
|
|
2750
|
+
* error context.
|
|
2751
|
+
* @param plaintext - The bytes to encrypt. Empty plaintext is permitted and
|
|
2752
|
+
* round-trips (GCM produces a valid tag over zero-length plaintext).
|
|
2753
|
+
* @param aad - Optional additional authenticated data bound into the GCM tag
|
|
2754
|
+
* but NOT encrypted. If provided at encrypt time, the identical bytes must be
|
|
2755
|
+
* supplied to `decryptBytes` or decryption fails authentication. Absent means
|
|
2756
|
+
* no AAD.
|
|
2757
|
+
* @returns `Success` with the {@link CryptoUtils.IEncryptBytesResult | ciphertext and 16-byte auth tag},
|
|
2758
|
+
* or `Failure` with error context.
|
|
2759
|
+
*/
|
|
2760
|
+
encryptBytes(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Promise<Result<IEncryptBytesResult>>;
|
|
2761
|
+
/**
|
|
2762
|
+
* Decrypts raw bytes produced by
|
|
2763
|
+
* {@link CryptoUtils.ICryptoProvider.encryptBytes | encryptBytes} using
|
|
2764
|
+
* AES-256-GCM. The inverse of `encryptBytes`: the `ciphertext` and `authTag`
|
|
2765
|
+
* from an `encryptBytes` result, together with the same `key`, `nonce`, and
|
|
2766
|
+
* `aad`, recover the original plaintext.
|
|
2767
|
+
*
|
|
2768
|
+
* Fails (never throws) on any authentication failure: a tampered ciphertext
|
|
2769
|
+
* or tag, the wrong key or nonce, or an `aad` that differs from the one used
|
|
2770
|
+
* at encrypt time. AES-GCM authentication is fail-closed — a mismatched `aad`
|
|
2771
|
+
* fails exactly as a tampered ciphertext does.
|
|
2772
|
+
*
|
|
2773
|
+
* @param key - 32-byte AES-256 key (the same key used to encrypt).
|
|
2774
|
+
* @param nonce - 12-byte (96-bit) GCM nonce (the same nonce used to encrypt).
|
|
2775
|
+
* @param ciphertext - The ciphertext from the `encryptBytes` result.
|
|
2776
|
+
* @param authTag - The 16-byte (128-bit) GCM authentication tag from the
|
|
2777
|
+
* `encryptBytes` result.
|
|
2778
|
+
* @param aad - The identical additional authenticated data supplied at encrypt
|
|
2779
|
+
* time (or absent if none was supplied). A mismatch fails authentication.
|
|
2780
|
+
* @returns `Success` with the decrypted plaintext bytes, or `Failure` with
|
|
2781
|
+
* error context (including all authentication failures).
|
|
2782
|
+
*/
|
|
2783
|
+
decryptBytes(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, authTag: Uint8Array, aad?: Uint8Array): Promise<Result<Uint8Array>>;
|
|
2684
2784
|
/**
|
|
2685
2785
|
* Generates a random 32-byte key suitable for AES-256.
|
|
2686
2786
|
* @returns Success with generated key, or Failure with error
|
|
@@ -2905,6 +3005,34 @@ declare interface IDirectEncryptionProviderParams {
|
|
|
2905
3005
|
readonly boundSecretName?: string;
|
|
2906
3006
|
}
|
|
2907
3007
|
|
|
3008
|
+
/**
|
|
3009
|
+
* Result of a raw-byte AES-256-GCM encryption via
|
|
3010
|
+
* {@link CryptoUtils.ICryptoProvider.encryptBytes | encryptBytes}. The
|
|
3011
|
+
* authentication tag is returned separately from the ciphertext, mirroring the
|
|
3012
|
+
* separated-tag convention of {@link CryptoUtils.IEncryptionResult}. The exact
|
|
3013
|
+
* byte layout is:
|
|
3014
|
+
* - `ciphertext`: the AES-GCM ciphertext, byte-for-byte the same length as the
|
|
3015
|
+
* input plaintext (GCM is a stream cipher — no padding). Empty plaintext
|
|
3016
|
+
* yields an empty `ciphertext`.
|
|
3017
|
+
* - `authTag`: the 16-byte (128-bit) GCM authentication tag, computed over the
|
|
3018
|
+
* ciphertext, the nonce, and any `aad`.
|
|
3019
|
+
*
|
|
3020
|
+
* The caller persists both fields alongside the (caller-owned) nonce and feeds
|
|
3021
|
+
* them back to {@link CryptoUtils.ICryptoProvider.decryptBytes | decryptBytes}.
|
|
3022
|
+
* @public
|
|
3023
|
+
*/
|
|
3024
|
+
declare interface IEncryptBytesResult {
|
|
3025
|
+
/**
|
|
3026
|
+
* The AES-256-GCM ciphertext. Same length as the input plaintext (no tag
|
|
3027
|
+
* appended — the tag is carried separately in the `authTag` field).
|
|
3028
|
+
*/
|
|
3029
|
+
readonly ciphertext: Uint8Array;
|
|
3030
|
+
/**
|
|
3031
|
+
* The 16-byte (128-bit) GCM authentication tag.
|
|
3032
|
+
*/
|
|
3033
|
+
readonly authTag: Uint8Array;
|
|
3034
|
+
}
|
|
3035
|
+
|
|
2908
3036
|
/**
|
|
2909
3037
|
* Generic encrypted file format.
|
|
2910
3038
|
* This is the JSON structure stored in encrypted files.
|
|
@@ -3278,6 +3406,26 @@ declare interface IGenerateJsonCompletionResult<T> {
|
|
|
3278
3406
|
readonly response: IAiCompletionResponse;
|
|
3279
3407
|
}
|
|
3280
3408
|
|
|
3409
|
+
/**
|
|
3410
|
+
* Options for retrieving an asymmetric keypair via {@link CryptoUtils.KeyStore.KeyStore.getKeyPair}.
|
|
3411
|
+
* @public
|
|
3412
|
+
*/
|
|
3413
|
+
declare interface IGetKeyPairOptions {
|
|
3414
|
+
/**
|
|
3415
|
+
* Opt in to escrow rehydration. When `true` AND no private-key blob exists
|
|
3416
|
+
* under the entry's storage `id` AND the entry carries an
|
|
3417
|
+
* `escrowedPrivateKeyJwk`, the escrowed JWK is imported (non-extractable
|
|
3418
|
+
* where the backend supports it) and stored under the entry's `id` before
|
|
3419
|
+
* being returned — filling a gap on a fresh device from the recovered vault.
|
|
3420
|
+
*
|
|
3421
|
+
* Fill-a-gap only: an existing storage blob is never overwritten. When a
|
|
3422
|
+
* blob is present it is loaded as usual and the escrow copy is ignored.
|
|
3423
|
+
*
|
|
3424
|
+
* @defaultValue false — today's behavior: load from storage or fail.
|
|
3425
|
+
*/
|
|
3426
|
+
readonly rehydrate?: boolean;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3281
3429
|
/**
|
|
3282
3430
|
* Provider-specific config for gpt-image-1.
|
|
3283
3431
|
* @public
|
|
@@ -3453,6 +3601,21 @@ declare interface IKeyStoreAsymmetricEntry {
|
|
|
3453
3601
|
* The public key as a JSON Web Key.
|
|
3454
3602
|
*/
|
|
3455
3603
|
readonly publicKeyJwk: JsonWebKey;
|
|
3604
|
+
/**
|
|
3605
|
+
* Optional escrowed copy of the private key, as a JSON Web Key. Present only
|
|
3606
|
+
* when the entry was created with `addKeyPair(name, { escrow: true })`.
|
|
3607
|
+
*
|
|
3608
|
+
* This is an opt-in cross-device recovery affordance: the private key is
|
|
3609
|
+
* carried inside the vault's AES-GCM ciphertext (same custody class as
|
|
3610
|
+
* `publicKeyJwk`), so a recovered vault plus the master password can
|
|
3611
|
+
* reconstitute the signing/decryption identity on a fresh device via
|
|
3612
|
+
* `getKeyPair(name, { rehydrate: true })`.
|
|
3613
|
+
*
|
|
3614
|
+
* SECURITY: when present, the master password becomes the sole gate
|
|
3615
|
+
* protecting this private key. Use a strong KDF for escrow-bearing stores.
|
|
3616
|
+
* See the {@link CryptoUtils.KeyStore.KeyStore} class docs.
|
|
3617
|
+
*/
|
|
3618
|
+
readonly escrowedPrivateKeyJwk?: JsonWebKey;
|
|
3456
3619
|
/**
|
|
3457
3620
|
* Optional description for this entry.
|
|
3458
3621
|
*/
|
|
@@ -3491,6 +3654,12 @@ declare interface IKeyStoreAsymmetricEntryJson {
|
|
|
3491
3654
|
* The public key as a JSON Web Key.
|
|
3492
3655
|
*/
|
|
3493
3656
|
readonly publicKeyJwk: JsonWebKey;
|
|
3657
|
+
/**
|
|
3658
|
+
* Optional escrowed copy of the private key, as a JSON Web Key. Present only
|
|
3659
|
+
* on `'keystore-v2'` vaults whose entry was created with escrow enabled. A
|
|
3660
|
+
* v1 vault omits the field; a v2 reader treats its absence as "no escrow".
|
|
3661
|
+
*/
|
|
3662
|
+
readonly escrowedPrivateKeyJwk?: JsonWebKey;
|
|
3494
3663
|
/**
|
|
3495
3664
|
* Optional description.
|
|
3496
3665
|
*/
|
|
@@ -4604,6 +4773,7 @@ declare namespace KeyStore {
|
|
|
4604
4773
|
allKeyPairAlgorithms,
|
|
4605
4774
|
KeyPairAlgorithm,
|
|
4606
4775
|
KeyStoreFormat,
|
|
4776
|
+
allKeyStoreFormats,
|
|
4607
4777
|
KEYSTORE_FORMAT,
|
|
4608
4778
|
DEFAULT_KEYSTORE_ITERATIONS,
|
|
4609
4779
|
MIN_SALT_LENGTH,
|
|
@@ -4635,6 +4805,7 @@ declare namespace KeyStore {
|
|
|
4635
4805
|
IAddSecretFromPasswordResult,
|
|
4636
4806
|
IAddSecretFromPasswordArgon2idOptions,
|
|
4637
4807
|
IAddKeyPairOptions,
|
|
4808
|
+
IGetKeyPairOptions,
|
|
4638
4809
|
IAddKeyPairResult,
|
|
4639
4810
|
IRemoveSecretResult,
|
|
4640
4811
|
IPrivateKeyStorage
|
|
@@ -4670,6 +4841,20 @@ declare namespace KeyStore {
|
|
|
4670
4841
|
* const encryptionConfig = keystore2.getEncryptionConfig().orThrow();
|
|
4671
4842
|
* ```
|
|
4672
4843
|
*
|
|
4844
|
+
* @remarks
|
|
4845
|
+
* SECURITY — private-key escrow. By default an asymmetric keypair's private
|
|
4846
|
+
* key lives only in the per-device {@link CryptoUtils.KeyStore.IPrivateKeyStorage}
|
|
4847
|
+
* backend; the vault carries only the public JWK, so a vault recovered on a new
|
|
4848
|
+
* device reconstitutes with no private keys (a lost device is a lost identity).
|
|
4849
|
+
* `addKeyPair(name, { escrow: true })` opts into carrying an encrypted private-key
|
|
4850
|
+
* copy inside the vault ciphertext, so the vault file plus the master password can
|
|
4851
|
+
* recover the identity on a fresh device via `getKeyPair(name, { rehydrate: true })`.
|
|
4852
|
+
*
|
|
4853
|
+
* When escrow is used, **the master password becomes the sole gate** protecting a
|
|
4854
|
+
* private signing/decryption key that was previously unrecoverable from the vault.
|
|
4855
|
+
* Configure escrow-bearing stores with a strong KDF — an Argon2id-derived master
|
|
4856
|
+
* key or a high-iteration PBKDF2 count — and treat the vault file accordingly.
|
|
4857
|
+
*
|
|
4673
4858
|
* @public
|
|
4674
4859
|
*/
|
|
4675
4860
|
declare class KeyStore_2 implements IEncryptionProvider {
|
|
@@ -4776,6 +4961,12 @@ declare class KeyStore_2 implements IEncryptionProvider {
|
|
|
4776
4961
|
* Gets a secret by name. Returns the {@link CryptoUtils.KeyStore.IKeyStoreEntry | discriminated union}
|
|
4777
4962
|
* — callers must check `entry.type` before accessing `key`/`id` since asymmetric
|
|
4778
4963
|
* entries carry no raw key material.
|
|
4964
|
+
*
|
|
4965
|
+
* SECURITY: for an escrow-enabled asymmetric-keypair entry the returned object
|
|
4966
|
+
* carries `escrowedPrivateKeyJwk` in cleartext (same custody class as
|
|
4967
|
+
* `publicKeyJwk`, gated by the same unlock) — do not log or serialize a
|
|
4968
|
+
* `getSecret()` result casually.
|
|
4969
|
+
*
|
|
4779
4970
|
* @param name - Name of the secret
|
|
4780
4971
|
* @returns Success with secret entry, Failure if not found or locked
|
|
4781
4972
|
* @public
|
|
@@ -4962,12 +5153,23 @@ declare class KeyStore_2 implements IEncryptionProvider {
|
|
|
4962
5153
|
* the keystore never caches private `CryptoKey` references between calls.
|
|
4963
5154
|
* The public key is re-imported from the vault's JWK so callers always
|
|
4964
5155
|
* receive a `CryptoKey` rather than the JWK form.
|
|
5156
|
+
*
|
|
5157
|
+
* With `options.rehydrate: true`, if the storage backend holds no blob for
|
|
5158
|
+
* the entry's `id` and the entry carries an `escrowedPrivateKeyJwk` (see
|
|
5159
|
+
* `addKeyPair(name, { escrow: true })`), the escrowed JWK is imported and
|
|
5160
|
+
* stored under the entry's `id` before being returned — recovering the
|
|
5161
|
+
* private key on a fresh device from the vault plus master password. This is
|
|
5162
|
+
* fill-a-gap only: an existing storage blob is never overwritten.
|
|
5163
|
+
*
|
|
4965
5164
|
* @param name - Name of the entry
|
|
5165
|
+
* @param options - Optional {@link CryptoUtils.KeyStore.IGetKeyPairOptions}
|
|
5166
|
+
* (currently the `rehydrate` escrow-recovery flag).
|
|
4966
5167
|
* @returns Success with `{ publicKey, privateKey }`, Failure if not found,
|
|
4967
|
-
* locked, wrong type, no provider, or storage load
|
|
5168
|
+
* locked, wrong type, no provider, or storage load (and any escrow
|
|
5169
|
+
* rehydration) failed.
|
|
4968
5170
|
* @public
|
|
4969
5171
|
*/
|
|
4970
|
-
getKeyPair(name: string): Promise<Result<{
|
|
5172
|
+
getKeyPair(name: string, options?: IGetKeyPairOptions): Promise<Result<{
|
|
4971
5173
|
publicKey: CryptoKey;
|
|
4972
5174
|
privateKey: CryptoKey;
|
|
4973
5175
|
}>>;
|
|
@@ -5053,6 +5255,40 @@ declare class KeyStore_2 implements IEncryptionProvider {
|
|
|
5053
5255
|
* @returns A warning string if storage cleanup failed, otherwise undefined.
|
|
5054
5256
|
*/
|
|
5055
5257
|
private _releaseEntryResources;
|
|
5258
|
+
/**
|
|
5259
|
+
* Exports the transient extractable private `CryptoKey` to a JWK for escrow,
|
|
5260
|
+
* via WebCrypto's `exportKey('jwk', ...)` (cross-runtime through
|
|
5261
|
+
* `globalThis.crypto.subtle`). The result is carried in the vault entry — the
|
|
5262
|
+
* same custody class as the public JWK — and is never returned from a public
|
|
5263
|
+
* method nor logged.
|
|
5264
|
+
*/
|
|
5265
|
+
private _exportPrivateKeyJwk;
|
|
5266
|
+
/**
|
|
5267
|
+
* Re-imports an escrowed private-key JWK as a `CryptoKey` for `algorithm`
|
|
5268
|
+
* with the requested extractability. The WebCrypto JWK-import descriptor is
|
|
5269
|
+
* shared between the public and private halves for every supported algorithm,
|
|
5270
|
+
* so `IKeyPairAlgorithmParams.importPublicKey` is reused; the private/public
|
|
5271
|
+
* distinction is carried by the requested usages (see
|
|
5272
|
+
* {@link KeyStore._privateKeyUsagesFor}). Cross-runtime through
|
|
5273
|
+
* `globalThis.crypto.subtle`.
|
|
5274
|
+
*/
|
|
5275
|
+
private _importEscrowedPrivateKey;
|
|
5276
|
+
/**
|
|
5277
|
+
* Loads the private key for `entry` from storage, or — when
|
|
5278
|
+
* `options.rehydrate` is set, storage holds no blob, and the entry carries an
|
|
5279
|
+
* escrowed JWK — imports the escrowed key, persists it under the entry's `id`
|
|
5280
|
+
* (fill-a-gap; never overwrites an existing blob), and returns it.
|
|
5281
|
+
*/
|
|
5282
|
+
private _loadOrRehydratePrivateKey;
|
|
5283
|
+
/**
|
|
5284
|
+
* Computes the key usages to request when importing an escrowed private JWK.
|
|
5285
|
+
* Mirrors `EncryptedFilePrivateKeyStorage`: intersect the algorithm's private
|
|
5286
|
+
* usages (its keypair usages minus the public-only ones) with the JWK's
|
|
5287
|
+
* recorded `key_ops` so we request exactly the operations the stored key
|
|
5288
|
+
* supports; fall back to the algorithm's private usages when `key_ops` is
|
|
5289
|
+
* absent.
|
|
5290
|
+
*/
|
|
5291
|
+
private static _privateKeyUsagesFor;
|
|
5056
5292
|
/**
|
|
5057
5293
|
* Constant-time byte comparison. Returns false immediately for length
|
|
5058
5294
|
* mismatch (length is not secret); for equal-length inputs, walks the full
|
|
@@ -5069,7 +5305,7 @@ declare class KeyStore_2 implements IEncryptionProvider {
|
|
|
5069
5305
|
}
|
|
5070
5306
|
|
|
5071
5307
|
/**
|
|
5072
|
-
* Current format version constant.
|
|
5308
|
+
* Current format version constant. New vaults are written as `'keystore-v2'`.
|
|
5073
5309
|
* @public
|
|
5074
5310
|
*/
|
|
5075
5311
|
declare const KEYSTORE_FORMAT: KeyStoreFormat;
|
|
@@ -5107,9 +5343,15 @@ declare const keystoreFile: Converter<IKeyStoreFile>;
|
|
|
5107
5343
|
|
|
5108
5344
|
/**
|
|
5109
5345
|
* Format version for key store files.
|
|
5346
|
+
*
|
|
5347
|
+
* - `'keystore-v1'`: the original format (no private-key escrow).
|
|
5348
|
+
* - `'keystore-v2'`: adds the optional `escrowedPrivateKeyJwk` field on
|
|
5349
|
+
* asymmetric-keypair entries. A strict superset of v1 — the field is
|
|
5350
|
+
* optional, so a v2 reader opens a v1 vault with no special-casing, and a
|
|
5351
|
+
* v1 vault opened and re-saved is silently upgraded to v2.
|
|
5110
5352
|
* @public
|
|
5111
5353
|
*/
|
|
5112
|
-
declare type KeyStoreFormat = 'keystore-v1';
|
|
5354
|
+
declare type KeyStoreFormat = 'keystore-v1' | 'keystore-v2';
|
|
5113
5355
|
|
|
5114
5356
|
/**
|
|
5115
5357
|
* Converter for {@link CryptoUtils.KeyStore.KeyStoreFormat | key store format} version.
|
|
@@ -5447,6 +5689,30 @@ declare class NodeCryptoProvider implements ICryptoProvider {
|
|
|
5447
5689
|
* @returns `Success` with decrypted UTF-8 string, or `Failure` with an error.
|
|
5448
5690
|
*/
|
|
5449
5691
|
decrypt(encryptedData: Uint8Array, key: Uint8Array, iv: Uint8Array, authTag: Uint8Array): Promise<Result<string>>;
|
|
5692
|
+
/**
|
|
5693
|
+
* Encrypts raw bytes using AES-256-GCM with a caller-supplied nonce and
|
|
5694
|
+
* optional AAD. See {@link CryptoUtils.ICryptoProvider.encryptBytes | ICryptoProvider.encryptBytes}
|
|
5695
|
+
* — in particular the caller's responsibility to use a unique `nonce` per
|
|
5696
|
+
* message under a given `key`.
|
|
5697
|
+
* @param key - 32-byte AES-256 key.
|
|
5698
|
+
* @param nonce - 12-byte GCM nonce (must be unique per message under `key`).
|
|
5699
|
+
* @param plaintext - The bytes to encrypt (empty permitted).
|
|
5700
|
+
* @param aad - Optional additional authenticated data bound into the tag.
|
|
5701
|
+
* @returns `Success` with the ciphertext and 16-byte auth tag, or `Failure` with an error.
|
|
5702
|
+
*/
|
|
5703
|
+
encryptBytes(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Promise<Result<IEncryptBytesResult>>;
|
|
5704
|
+
/**
|
|
5705
|
+
* Decrypts raw bytes produced by {@link NodeCryptoProvider.encryptBytes} using
|
|
5706
|
+
* AES-256-GCM. See {@link CryptoUtils.ICryptoProvider.decryptBytes | ICryptoProvider.decryptBytes}.
|
|
5707
|
+
* Fails (never throws) on any authentication failure, including a mismatched `aad`.
|
|
5708
|
+
* @param key - 32-byte AES-256 key.
|
|
5709
|
+
* @param nonce - 12-byte GCM nonce (the same one used to encrypt).
|
|
5710
|
+
* @param ciphertext - The ciphertext from the `encryptBytes` result.
|
|
5711
|
+
* @param authTag - The 16-byte GCM auth tag from the `encryptBytes` result.
|
|
5712
|
+
* @param aad - The identical AAD supplied at encrypt time (or absent).
|
|
5713
|
+
* @returns `Success` with the decrypted plaintext bytes, or `Failure` with an error.
|
|
5714
|
+
*/
|
|
5715
|
+
decryptBytes(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, authTag: Uint8Array, aad?: Uint8Array): Promise<Result<Uint8Array>>;
|
|
5450
5716
|
/**
|
|
5451
5717
|
* Generates a random 32-byte key suitable for AES-256.
|
|
5452
5718
|
* @returns `Success` with generated key, or `Failure` with an error.
|
|
@@ -5956,6 +6222,22 @@ declare type SecretProvider = (secretName: string) => Promise<Result<Uint8Array>
|
|
|
5956
6222
|
*/
|
|
5957
6223
|
declare const SMART_JSON_PROMPT_HINT: string;
|
|
5958
6224
|
|
|
6225
|
+
/**
|
|
6226
|
+
* Strips the fixed DER prefix from an X25519 SubjectPublicKeyInfo blob, returning the raw
|
|
6227
|
+
* 32-byte public key.
|
|
6228
|
+
*
|
|
6229
|
+
* An X25519 SPKI is always exactly 44 bytes: a fixed 12-byte prefix (SEQUENCE / AlgorithmIdentifier
|
|
6230
|
+
* with OID 1.3.101.110 / BIT STRING) followed by the 32-byte raw key. Use this to convert a
|
|
6231
|
+
* SPKI-held recipient public key into the raw form accepted by {@link HpkeProvider.openBase}'s
|
|
6232
|
+
* `recipientPublicKey` parameter.
|
|
6233
|
+
*
|
|
6234
|
+
* @param spki - The DER-encoded X25519 SubjectPublicKeyInfo bytes.
|
|
6235
|
+
* @returns `Success` with the raw 32-byte public key, or `Failure` if `spki` is not a
|
|
6236
|
+
* well-formed 44-byte X25519 SPKI blob.
|
|
6237
|
+
* @public
|
|
6238
|
+
*/
|
|
6239
|
+
declare function spkiToRawX25519(spki: Uint8Array): Result<Uint8Array>;
|
|
6240
|
+
|
|
5959
6241
|
/**
|
|
5960
6242
|
* Whether a provider declares any embedding capability at all.
|
|
5961
6243
|
*
|
|
@@ -93,15 +93,23 @@ export declare class HpkeProvider {
|
|
|
93
93
|
*
|
|
94
94
|
* @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`
|
|
95
95
|
* (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).
|
|
96
|
-
* **Must be extractable** (`extractable: true`)
|
|
97
|
-
* are recovered from the JWK `x` field
|
|
96
|
+
* **Must be extractable** (`extractable: true`) only when `recipientPublicKey` is not
|
|
97
|
+
* supplied — the recipient's public key bytes are then recovered from the JWK `x` field
|
|
98
|
+
* during Decap. When `recipientPublicKey` is supplied, `recipientPrivateKey` may be
|
|
99
|
+
* non-extractable.
|
|
98
100
|
* @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.
|
|
99
101
|
* @param aad - Must exactly match `aad` from `sealBase`.
|
|
100
102
|
* @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.
|
|
101
103
|
* @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.
|
|
104
|
+
* @param recipientPublicKey - Optional raw 32-byte X25519 public key matching
|
|
105
|
+
* `recipientPrivateKey` (`pkRm` in RFC 9180 §4.1). Public material — supplying it lets
|
|
106
|
+
* Decap build `kem_context` without exporting `recipientPrivateKey` to JWK, so
|
|
107
|
+
* `recipientPrivateKey` no longer needs to be extractable. A mismatched value only breaks
|
|
108
|
+
* the caller's own decryption (AEAD authentication fails) — it cannot be used to attack
|
|
109
|
+
* another party's ciphertext.
|
|
102
110
|
* @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.
|
|
103
111
|
*/
|
|
104
|
-
openBase(recipientPrivateKey: CryptoKey, info: Uint8Array, aad: Uint8Array, enc: Uint8Array, ciphertext: Uint8Array): Promise<Result<Uint8Array>>;
|
|
112
|
+
openBase(recipientPrivateKey: CryptoKey, info: Uint8Array, aad: Uint8Array, enc: Uint8Array, ciphertext: Uint8Array, recipientPublicKey?: Uint8Array): Promise<Result<Uint8Array>>;
|
|
105
113
|
/**
|
|
106
114
|
* HKDF-SHA256 key derivation (RFC 5869). Extract-then-Expand using SHA-256.
|
|
107
115
|
*
|
|
@@ -1 +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;
|
|
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;AA+OzF;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACU,QAAQ,CACnB,mBAAmB,EAAE,SAAS,EAC9B,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,UAAU,EACtB,kBAAkB,CAAC,EAAE,UAAU,GAC9B,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IA4B9B;;;;;;;;;;;;;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"}
|
|
@@ -141,17 +141,28 @@ async function _kemEncap(subtle, recipientPublicKey) {
|
|
|
141
141
|
}
|
|
142
142
|
// RFC 9180 §4.1 DHKEM Decap — deserializes enc, DH with recipient privkey,
|
|
143
143
|
// derives same shared_secret via ExtractAndExpand.
|
|
144
|
-
//
|
|
145
|
-
|
|
144
|
+
// The DH step (deriveBits) works on a non-extractable recipientPrivateKey. Recovering the
|
|
145
|
+
// recipient's own public key bytes (pkRm) for kem_context normally requires a JWK export, which
|
|
146
|
+
// in turn requires recipientPrivateKey to be extractable — UNLESS the caller supplies pkRm
|
|
147
|
+
// directly (raw 32-byte X25519 public key), in which case no JWK export happens and
|
|
148
|
+
// recipientPrivateKey may be non-extractable.
|
|
149
|
+
async function _kemDecap(subtle, enc, recipientPrivateKey, recipientPublicKey) {
|
|
146
150
|
const pkE = await subtle.importKey('raw', _toBufferView(enc), { name: 'X25519' }, true, []);
|
|
147
151
|
const dh = new Uint8Array(await subtle.deriveBits({ name: 'X25519', public: pkE }, recipientPrivateKey, 256));
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
let pkRm;
|
|
153
|
+
if (recipientPublicKey !== undefined) {
|
|
154
|
+
// Caller-supplied; length already validated in openBase.
|
|
155
|
+
pkRm = recipientPublicKey;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Recover recipient's own public key from JWK x field (base64url-encoded raw X25519 public key).
|
|
159
|
+
const jwk = (await subtle.exportKey('jwk', recipientPrivateKey));
|
|
160
|
+
/* c8 ignore next 3 - defensive: X25519 JWK always has an x field; unreachable via public API */
|
|
161
|
+
if (!jwk.x) {
|
|
162
|
+
throw new Error('HPKE Decap: failed to extract public key bytes from recipient private key JWK');
|
|
163
|
+
}
|
|
164
|
+
pkRm = _base64UrlDecode(jwk.x);
|
|
153
165
|
}
|
|
154
|
-
const pkRm = _base64UrlDecode(jwk.x);
|
|
155
166
|
const kemContext = _concat(enc, pkRm);
|
|
156
167
|
const eaePrk = await _labeledExtract(subtle, _KEM_SUITE_ID, new Uint8Array(0), 'eae_prk', dh);
|
|
157
168
|
return _labeledExpand(subtle, _KEM_SUITE_ID, eaePrk, 'shared_secret', kemContext, _N_SECRET);
|
|
@@ -255,23 +266,34 @@ class HpkeProvider {
|
|
|
255
266
|
*
|
|
256
267
|
* @param recipientPrivateKey - Recipient's X25519 private `CryptoKey`
|
|
257
268
|
* (`algorithm.name === 'X25519'`, `type === 'private'`, `usages` includes `'deriveBits'`).
|
|
258
|
-
* **Must be extractable** (`extractable: true`)
|
|
259
|
-
* are recovered from the JWK `x` field
|
|
269
|
+
* **Must be extractable** (`extractable: true`) only when `recipientPublicKey` is not
|
|
270
|
+
* supplied — the recipient's public key bytes are then recovered from the JWK `x` field
|
|
271
|
+
* during Decap. When `recipientPublicKey` is supplied, `recipientPrivateKey` may be
|
|
272
|
+
* non-extractable.
|
|
260
273
|
* @param info - Context-binding bytes. Must exactly match `info` from `sealBase`.
|
|
261
274
|
* @param aad - Must exactly match `aad` from `sealBase`.
|
|
262
275
|
* @param enc - The encapsulated key from `sealBase` — exactly 32 bytes.
|
|
263
276
|
* @param ciphertext - The ciphertext from `sealBase` — `plaintext.length + 16` bytes.
|
|
277
|
+
* @param recipientPublicKey - Optional raw 32-byte X25519 public key matching
|
|
278
|
+
* `recipientPrivateKey` (`pkRm` in RFC 9180 §4.1). Public material — supplying it lets
|
|
279
|
+
* Decap build `kem_context` without exporting `recipientPrivateKey` to JWK, so
|
|
280
|
+
* `recipientPrivateKey` no longer needs to be extractable. A mismatched value only breaks
|
|
281
|
+
* the caller's own decryption (AEAD authentication fails) — it cannot be used to attack
|
|
282
|
+
* another party's ciphertext.
|
|
264
283
|
* @returns `Success` with decrypted plaintext bytes, or `Failure` with error context.
|
|
265
284
|
*/
|
|
266
|
-
async openBase(recipientPrivateKey, info, aad, enc, ciphertext) {
|
|
285
|
+
async openBase(recipientPrivateKey, info, aad, enc, ciphertext, recipientPublicKey) {
|
|
267
286
|
if (enc.length !== _N_PK) {
|
|
268
287
|
return (0, ts_utils_1.fail)(`HPKE openBase: enc must be ${_N_PK} bytes, got ${enc.length}`);
|
|
269
288
|
}
|
|
270
289
|
if (ciphertext.length < _N_T) {
|
|
271
290
|
return (0, ts_utils_1.fail)(`HPKE openBase: ciphertext too short (minimum ${_N_T} bytes for auth tag, got ${ciphertext.length})`);
|
|
272
291
|
}
|
|
292
|
+
if (recipientPublicKey !== undefined && recipientPublicKey.length !== _N_PK) {
|
|
293
|
+
return (0, ts_utils_1.fail)(`HPKE openBase: recipientPublicKey must be ${_N_PK} bytes, got ${recipientPublicKey.length}`);
|
|
294
|
+
}
|
|
273
295
|
const result = await (0, ts_utils_1.captureAsyncResult)(async () => {
|
|
274
|
-
const sharedSecret = await _kemDecap(this._subtle, enc, recipientPrivateKey);
|
|
296
|
+
const sharedSecret = await _kemDecap(this._subtle, enc, recipientPrivateKey, recipientPublicKey);
|
|
275
297
|
const { key, baseNonce } = await _keyScheduleBase(this._subtle, sharedSecret, info);
|
|
276
298
|
const aesKey = await this._subtle.importKey('raw', key, { name: 'AES-GCM' }, false, ['decrypt']);
|
|
277
299
|
const pt = await this._subtle.decrypt({ name: 'AES-GCM', iv: baseNonce, additionalData: _toBufferView(aad) }, aesKey, _toBufferView(ciphertext));
|