mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
crypto: support ML-KEM in Web Cryptography
PR-URL: https://github.com/nodejs/node/pull/59569 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
19d2cee62c
commit
589ef79bf8
17
deps/ncrypto/ncrypto.cc
vendored
17
deps/ncrypto/ncrypto.cc
vendored
|
|
@ -2162,21 +2162,34 @@ DataPointer EVPKeyPointer::rawPublicKey() const {
|
|||
#if OPENSSL_WITH_PQC
|
||||
DataPointer EVPKeyPointer::rawSeed() const {
|
||||
if (!pkey_) return {};
|
||||
|
||||
// Determine seed length and parameter name based on key type
|
||||
size_t seed_len;
|
||||
const char* param_name;
|
||||
|
||||
switch (id()) {
|
||||
case EVP_PKEY_ML_DSA_44:
|
||||
case EVP_PKEY_ML_DSA_65:
|
||||
case EVP_PKEY_ML_DSA_87:
|
||||
seed_len = 32; // ML-DSA uses 32-byte seeds
|
||||
param_name = OSSL_PKEY_PARAM_ML_DSA_SEED;
|
||||
break;
|
||||
case EVP_PKEY_ML_KEM_512:
|
||||
case EVP_PKEY_ML_KEM_768:
|
||||
case EVP_PKEY_ML_KEM_1024:
|
||||
seed_len = 64; // ML-KEM uses 64-byte seeds
|
||||
param_name = OSSL_PKEY_PARAM_ML_KEM_SEED;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
size_t seed_len = 32;
|
||||
if (auto data = DataPointer::Alloc(seed_len)) {
|
||||
const Buffer<unsigned char> buf = data;
|
||||
size_t len = data.size();
|
||||
|
||||
if (EVP_PKEY_get_octet_string_param(
|
||||
get(), OSSL_PKEY_PARAM_ML_DSA_SEED, buf.data, len, &seed_len) != 1)
|
||||
get(), param_name, buf.data, len, &seed_len) != 1)
|
||||
return {};
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
<!-- YAML
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -107,6 +110,9 @@ Algorithms:
|
|||
* `'ML-DSA-44'`[^openssl35]
|
||||
* `'ML-DSA-65'`[^openssl35]
|
||||
* `'ML-DSA-87'`[^openssl35]
|
||||
* `'ML-KEM-1024'`[^openssl35]
|
||||
* `'ML-KEM-512'`[^openssl35]
|
||||
* `'ML-KEM-768'`[^openssl35]
|
||||
* `'SHA3-256'`
|
||||
* `'SHA3-384'`
|
||||
* `'SHA3-512'`
|
||||
|
|
@ -119,6 +125,10 @@ Key Formats:
|
|||
|
||||
Methods:
|
||||
|
||||
* [`subtle.decapsulateBits()`][]
|
||||
* [`subtle.decapsulateKey()`][]
|
||||
* [`subtle.encapsulateBits()`][]
|
||||
* [`subtle.encapsulateKey()`][]
|
||||
* [`subtle.getPublicKey()`][]
|
||||
* [`SubtleCrypto.supports()`][]
|
||||
|
||||
|
|
@ -480,40 +490,83 @@ const decrypted = new TextDecoder().decode(await crypto.subtle.decrypt(
|
|||
|
||||
## Algorithm matrix
|
||||
|
||||
The table details the algorithms supported by the Node.js Web Crypto API
|
||||
The tables details the algorithms supported by the Node.js Web Crypto API
|
||||
implementation and the APIs supported for each:
|
||||
|
||||
| Algorithm | `generateKey` | `exportKey` | `importKey` | `encrypt/decrypt` | `wrapKey/unwrapKey` | `deriveBits/deriveKey` | `sign/verify` | `digest` | `getPublicKey` |
|
||||
| ------------------------------------ | ------------- | ----------- | ----------- | ----------------- | ------------------- | ---------------------- | ------------- | -------- | -------------- |
|
||||
| `'AES-CBC'` | ✔ | ✔ | ✔ | ✔ | ✔ | | | | |
|
||||
| `'AES-CTR'` | ✔ | ✔ | ✔ | ✔ | ✔ | | | | |
|
||||
| `'AES-GCM'` | ✔ | ✔ | ✔ | ✔ | ✔ | | | | |
|
||||
| `'AES-KW'` | ✔ | ✔ | ✔ | | ✔ | | | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ | ✔ | | | | |
|
||||
| `'cSHAKE128'`[^modern-algos] | | | | | | | | ✔ | |
|
||||
| `'cSHAKE256'`[^modern-algos] | | | | | | | | ✔ | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | | | ✔ | | | ✔ |
|
||||
| `'ECDSA'` | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'Ed25519'` | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'HKDF'` | | | ✔ | | | ✔ | | | |
|
||||
| `'HMAC'` | ✔ | ✔ | ✔ | | | | ✔ | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'PBKDF2'` | | | ✔ | | | ✔ | | | |
|
||||
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | ✔ | ✔ | | | | ✔ |
|
||||
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | ✔ | | ✔ |
|
||||
| `'SHA-1'` | | | | | | | | ✔ | |
|
||||
| `'SHA-256'` | | | | | | | | ✔ | |
|
||||
| `'SHA-384'` | | | | | | | | ✔ | |
|
||||
| `'SHA-512'` | | | | | | | | ✔ | |
|
||||
| `'SHA3-256'`[^modern-algos] | | | | | | | | ✔ | |
|
||||
| `'SHA3-384'`[^modern-algos] | | | | | | | | ✔ | |
|
||||
| `'SHA3-512'`[^modern-algos] | | | | | | | | ✔ | |
|
||||
| `'X25519'` | ✔ | ✔ | ✔ | | | ✔ | | | ✔ |
|
||||
| `'X448'`[^secure-curves] | ✔ | ✔ | ✔ | | | ✔ | | | ✔ |
|
||||
### Key Management APIs
|
||||
|
||||
| Algorithm | [`subtle.generateKey()`][] | [`subtle.exportKey()`][] | [`subtle.importKey()`][] | [`subtle.getPublicKey()`][] |
|
||||
| ------------------------------------ | -------------------------- | ------------------------ | ------------------------ | --------------------------- |
|
||||
| `'AES-CBC'` | ✔ | ✔ | ✔ | |
|
||||
| `'AES-CTR'` | ✔ | ✔ | ✔ | |
|
||||
| `'AES-GCM'` | ✔ | ✔ | ✔ | |
|
||||
| `'AES-KW'` | ✔ | ✔ | ✔ | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | ✔ | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'HKDF'` | | | ✔ | |
|
||||
| `'HMAC'` | ✔ | ✔ | ✔ | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'PBKDF2'` | | | ✔ | |
|
||||
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'RSA-PSS'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'X25519'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'X448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ |
|
||||
|
||||
### Crypto Operation APIs
|
||||
|
||||
**Column Legend:**
|
||||
|
||||
* **Encryption**: [`subtle.encrypt()`][] / [`subtle.decrypt()`][]
|
||||
* **Signatures and MAC**: [`subtle.sign()`][] / [`subtle.verify()`][]
|
||||
* **Key or Bits Derivation**: [`subtle.deriveBits()`][] / [`subtle.deriveKey()`][]
|
||||
* **Key Wrapping**: [`subtle.wrapKey()`][] / [`subtle.unwrapKey()`][]
|
||||
* **Key Encapsulation**: [`subtle.encapsulateBits()`][] / [`subtle.decapsulateBits()`][] /
|
||||
[`subtle.encapsulateKey()`][] / [`subtle.decapsulateKey()`][]
|
||||
* **Digest**: [`subtle.digest()`][]
|
||||
|
||||
| Algorithm | Encryption | Signatures and MAC | Key or Bits Derivation | Key Wrapping | Key Encapsulation | Digest |
|
||||
| ------------------------------------ | ---------- | ------------------ | ---------------------- | ------------ | ----------------- | ------ |
|
||||
| `'AES-CBC'` | ✔ | | | ✔ | | |
|
||||
| `'AES-CTR'` | ✔ | | | ✔ | | |
|
||||
| `'AES-GCM'` | ✔ | | | ✔ | | |
|
||||
| `'AES-KW'` | | | | ✔ | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | | |
|
||||
| `'cSHAKE128'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'cSHAKE256'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'ECDH'` | | | ✔ | | | |
|
||||
| `'ECDSA'` | | ✔ | | | | |
|
||||
| `'Ed25519'` | | ✔ | | | | |
|
||||
| `'Ed448'`[^secure-curves] | | ✔ | | | | |
|
||||
| `'HKDF'` | | | ✔ | | | |
|
||||
| `'HMAC'` | | ✔ | | | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-DSA-65'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-DSA-87'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-KEM-512'`[^modern-algos] | | | | | ✔ | |
|
||||
| `'ML-KEM-768'`[^modern-algos] | | | | | ✔ | |
|
||||
| `'ML-KEM-1024'`[^modern-algos] | | | | | ✔ | |
|
||||
| `'PBKDF2'` | | | ✔ | | | |
|
||||
| `'RSA-OAEP'` | ✔ | | | ✔ | | |
|
||||
| `'RSA-PSS'` | | ✔ | | | | |
|
||||
| `'RSASSA-PKCS1-v1_5'` | | ✔ | | | | |
|
||||
| `'SHA-1'` | | | | | | ✔ |
|
||||
| `'SHA-256'` | | | | | | ✔ |
|
||||
| `'SHA-384'` | | | | | | ✔ |
|
||||
| `'SHA-512'` | | | | | | ✔ |
|
||||
| `'SHA3-256'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'SHA3-384'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'SHA3-512'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'X25519'` | | | ✔ | | | |
|
||||
| `'X448'`[^secure-curves] | | | ✔ | | | |
|
||||
|
||||
## Class: `Crypto`
|
||||
|
||||
|
|
@ -623,40 +676,56 @@ key may be used.
|
|||
|
||||
The possible usages are:
|
||||
|
||||
* `'encrypt'` - The key may be used to encrypt data.
|
||||
* `'decrypt'` - The key may be used to decrypt data.
|
||||
* `'sign'` - The key may be used to generate digital signatures.
|
||||
* `'verify'` - The key may be used to verify digital signatures.
|
||||
* `'deriveKey'` - The key may be used to derive a new key.
|
||||
* `'deriveBits'` - The key may be used to derive bits.
|
||||
* `'wrapKey'` - The key may be used to wrap another key.
|
||||
* `'unwrapKey'` - The key may be used to unwrap another key.
|
||||
* `'encrypt'` - Enable using the key with [`subtle.encrypt()`][]
|
||||
* `'decrypt'` - Enable using the key with [`subtle.decrypt()`][]
|
||||
* `'sign'` - Enable using the key with [`subtle.sign()`][]
|
||||
* `'verify'` - Enable using the key with [`subtle.verify()`][]
|
||||
* `'deriveKey'` - Enable using the key with [`subtle.deriveKey()`][]
|
||||
* `'deriveBits'` - Enable using the key with [`subtle.deriveBits()`][]
|
||||
* `'encapsulateBits'` - Enable using the key with [`subtle.encapsulateBits()`][]
|
||||
* `'decapsulateBits'` - Enable using the key with [`subtle.decapsulateBits()`][]
|
||||
* `'encapsulateKey'` - Enable using the key with [`subtle.encapsulateKey()`][]
|
||||
* `'decapsulateKey'` - Enable using the key with [`subtle.decapsulateKey()`][]
|
||||
* `'wrapKey'` - Enable using the key with [`subtle.wrapKey()`][]
|
||||
* `'unwrapKey'` - Enable using the key with [`subtle.unwrapKey()`][]
|
||||
|
||||
Valid key usages depend on the key algorithm (identified by
|
||||
`cryptokey.algorithm.name`).
|
||||
|
||||
| Supported Key Algorithm | `'encrypt'` | `'decrypt'` | `'sign'` | `'verify'` | `'deriveKey'` | `'deriveBits'` | `'wrapKey'` | `'unwrapKey'` |
|
||||
| ------------------------------------ | ----------- | ----------- | -------- | ---------- | ------------- | -------------- | ----------- | ------------- |
|
||||
| `'AES-CBC'` | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'AES-CTR'` | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'AES-GCM'` | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'AES-KW'` | | | | | | | ✔ | ✔ |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'ECDH'` | | | | | ✔ | ✔ | | |
|
||||
| `'ECDSA'` | | | ✔ | ✔ | | | | |
|
||||
| `'Ed25519'` | | | ✔ | ✔ | | | | |
|
||||
| `'Ed448'`[^secure-curves] | | | ✔ | ✔ | | | | |
|
||||
| `'HDKF'` | | | | | ✔ | ✔ | | |
|
||||
| `'HMAC'` | | | ✔ | ✔ | | | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | | | ✔ | ✔ | | | | |
|
||||
| `'ML-DSA-65'`[^modern-algos] | | | ✔ | ✔ | | | | |
|
||||
| `'ML-DSA-87'`[^modern-algos] | | | ✔ | ✔ | | | | |
|
||||
| `'PBKDF2'` | | | | | ✔ | ✔ | | |
|
||||
| `'RSA-OAEP'` | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'RSA-PSS'` | | | ✔ | ✔ | | | | |
|
||||
| `'RSASSA-PKCS1-v1_5'` | | | ✔ | ✔ | | | | |
|
||||
| `'X25519'` | | | | | ✔ | ✔ | | |
|
||||
| `'X448'`[^secure-curves] | | | | | ✔ | ✔ | | |
|
||||
**Column Legend:**
|
||||
|
||||
* **Encryption**: [`subtle.encrypt()`][] / [`subtle.decrypt()`][]
|
||||
* **Signatures and MAC**: [`subtle.sign()`][] / [`subtle.verify()`][]
|
||||
* **Key or Bits Derivation**: [`subtle.deriveBits()`][] / [`subtle.deriveKey()`][]
|
||||
* **Key Wrapping**: [`subtle.wrapKey()`][] / [`subtle.unwrapKey()`][]
|
||||
* **Key Encapsulation**: [`subtle.encapsulateBits()`][] / [`subtle.decapsulateBits()`][] /
|
||||
[`subtle.encapsulateKey()`][] / [`subtle.decapsulateKey()`][]
|
||||
|
||||
| Supported Key Algorithm | Encryption | Signatures and MAC | Key or Bits Derivation | Key Wrapping | Key Encapsulation |
|
||||
| ------------------------------------ | ---------- | ------------------ | ---------------------- | ------------ | ----------------- |
|
||||
| `'AES-CBC'` | ✔ | | | ✔ | |
|
||||
| `'AES-CTR'` | ✔ | | | ✔ | |
|
||||
| `'AES-GCM'` | ✔ | | | ✔ | |
|
||||
| `'AES-KW'` | | | | ✔ | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | |
|
||||
| `'ECDH'` | | | ✔ | | |
|
||||
| `'ECDSA'` | | ✔ | | | |
|
||||
| `'Ed25519'` | | ✔ | | | |
|
||||
| `'Ed448'`[^secure-curves] | | ✔ | | | |
|
||||
| `'HDKF'` | | | ✔ | | |
|
||||
| `'HMAC'` | | ✔ | | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | | ✔ | | | |
|
||||
| `'ML-DSA-65'`[^modern-algos] | | ✔ | | | |
|
||||
| `'ML-DSA-87'`[^modern-algos] | | ✔ | | | |
|
||||
| `'ML-KEM-512'`[^modern-algos] | | | | | ✔ |
|
||||
| `'ML-KEM-768'`[^modern-algos] | | | | | ✔ |
|
||||
| `'ML-KEM-1024'`[^modern-algos] | | | | | ✔ |
|
||||
| `'PBKDF2'` | | | ✔ | | |
|
||||
| `'RSA-OAEP'` | ✔ | | | ✔ | |
|
||||
| `'RSA-PSS'` | | ✔ | | | |
|
||||
| `'RSASSA-PKCS1-v1_5'` | | ✔ | | | |
|
||||
| `'X25519'` | | | ✔ | | |
|
||||
| `'X448'`[^secure-curves] | | | ✔ | | |
|
||||
|
||||
## Class: `CryptoKeyPair`
|
||||
|
||||
|
|
@ -710,6 +779,47 @@ Allows feature detection in Web Crypto API,
|
|||
which can be used to detect whether a given algorithm identifier
|
||||
(including its parameters) is supported for the given operation.
|
||||
|
||||
### `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
* `decapsulationAlgorithm` {string|Algorithm}
|
||||
* `decapsulationKey` {CryptoKey}
|
||||
* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||
* Returns: {Promise} Fulfills with {ArrayBuffer} upon success.
|
||||
|
||||
The algorithms currently supported include:
|
||||
|
||||
* `'ML-KEM-512'`[^modern-algos]
|
||||
* `'ML-KEM-768'`[^modern-algos]
|
||||
* `'ML-KEM-1024'`[^modern-algos]
|
||||
|
||||
### `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
* `decapsulationAlgorithm` {string|Algorithm}
|
||||
* `decapsulationKey` {CryptoKey}
|
||||
* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
|
||||
* `extractable` {boolean}
|
||||
* `usages` {string\[]} See [Key usages][].
|
||||
* Returns: {Promise} Fulfills with {CryptoKey} upon success.
|
||||
|
||||
The algorithms currently supported include:
|
||||
|
||||
* `'ML-KEM-512'`[^modern-algos]
|
||||
* `'ML-KEM-768'`[^modern-algos]
|
||||
* `'ML-KEM-1024'`[^modern-algos]
|
||||
|
||||
### `subtle.decrypt(algorithm, key, data)`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -861,6 +971,45 @@ If `algorithm` is provided as a {string}, it must be one of:
|
|||
If `algorithm` is provided as an {Object}, it must have a `name` property
|
||||
whose value is one of the above.
|
||||
|
||||
### `subtle.encapsulateBits(encapsulationAlgorithm, encapsulationKey)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
* `encapsulationAlgorithm` {string|Algorithm}
|
||||
* `encapsulationKey` {CryptoKey}
|
||||
* Returns: {Promise} Fulfills with {EncapsulatedBits} upon success.
|
||||
|
||||
The algorithms currently supported include:
|
||||
|
||||
* `'ML-KEM-512'`[^modern-algos]
|
||||
* `'ML-KEM-768'`[^modern-algos]
|
||||
* `'ML-KEM-1024'`[^modern-algos]
|
||||
|
||||
### `subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
* `encapsulationAlgorithm` {string|Algorithm}
|
||||
* `encapsulationKey` {CryptoKey}
|
||||
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
|
||||
* `extractable` {boolean}
|
||||
* `usages` {string\[]} See [Key usages][].
|
||||
* Returns: {Promise} Fulfills with {EncapsulatedKey} upon success.
|
||||
|
||||
The algorithms currently supported include:
|
||||
|
||||
* `'ML-KEM-512'`[^modern-algos]
|
||||
* `'ML-KEM-768'`[^modern-algos]
|
||||
* `'ML-KEM-1024'`[^modern-algos]
|
||||
|
||||
### `subtle.encrypt(algorithm, key, data)`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -894,6 +1043,9 @@ The algorithms currently supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -943,6 +1095,9 @@ specification.
|
|||
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
|
||||
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
|
||||
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
|
||||
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
|
||||
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |
|
||||
| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | |
|
||||
|
|
@ -966,6 +1121,9 @@ Derives the public key from a given private key.
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -998,6 +1156,9 @@ include:
|
|||
* `'ML-DSA-44'`[^modern-algos]
|
||||
* `'ML-DSA-65'`[^modern-algos]
|
||||
* `'ML-DSA-87'`[^modern-algos]
|
||||
* `'ML-KEM-512'`[^modern-algos]
|
||||
* `'ML-KEM-768'`[^modern-algos]
|
||||
* `'ML-KEM-1024'`[^modern-algos]
|
||||
* `'RSA-OAEP'`
|
||||
* `'RSA-PSS'`
|
||||
* `'RSASSA-PKCS1-v1_5'`
|
||||
|
|
@ -1018,6 +1179,9 @@ The {CryptoKey} (secret key) generating algorithms supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -1074,6 +1238,9 @@ The algorithms currently supported include:
|
|||
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
|
||||
| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
|
||||
| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ |
|
||||
| `'PBKDF2'` | | | | ✔ | ✔ | | |
|
||||
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
|
||||
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |
|
||||
|
|
@ -1181,6 +1348,9 @@ The unwrapped key algorithms supported include:
|
|||
* `'ML-DSA-44'`[^modern-algos]
|
||||
* `'ML-DSA-65'`[^modern-algos]
|
||||
* `'ML-DSA-87'`[^modern-algos]
|
||||
* `'ML-KEM-512'`[^modern-algos]
|
||||
* `'ML-KEM-768'`[^modern-algos]
|
||||
* `'ML-KEM-1024'`[^modern-algos]v
|
||||
* `'RSA-OAEP'`
|
||||
* `'RSA-PSS'`
|
||||
* `'RSASSA-PKCS1-v1_5'`
|
||||
|
|
@ -1707,6 +1877,50 @@ the message.
|
|||
The Node.js Web Crypto API implementation only supports zero-length context
|
||||
which is equivalent to not providing context at all.
|
||||
|
||||
### Class: `EncapsulatedBits`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
#### `encapsulatedBits.ciphertext`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {ArrayBuffer}
|
||||
|
||||
#### `encapsulatedBits.sharedKey`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {ArrayBuffer}
|
||||
|
||||
### Class: `EncapsulatedKey`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
#### `encapsulatedKey.ciphertext`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {ArrayBuffer}
|
||||
|
||||
#### `encapsulatedKey.sharedKey`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {CryptoKey}
|
||||
|
||||
### Class: `HkdfParams`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -2187,4 +2401,20 @@ The length (in bytes) of the random salt to use.
|
|||
[Secure Curves in the Web Cryptography API]: #secure-curves-in-the-web-cryptography-api
|
||||
[Web Crypto API]: https://www.w3.org/TR/WebCryptoAPI/
|
||||
[`SubtleCrypto.supports()`]: #static-method-subtlecryptosupportsoperation-algorithm-lengthoradditionalalgorithm
|
||||
[`subtle.decapsulateBits()`]: #subtledecapsulatebitsdecapsulationalgorithm-decapsulationkey-ciphertext
|
||||
[`subtle.decapsulateKey()`]: #subtledecapsulatekeydecapsulationalgorithm-decapsulationkey-ciphertext-sharedkeyalgorithm-extractable-usages
|
||||
[`subtle.decrypt()`]: #subtledecryptalgorithm-key-data
|
||||
[`subtle.deriveBits()`]: #subtlederivebitsalgorithm-basekey-length
|
||||
[`subtle.deriveKey()`]: #subtlederivekeyalgorithm-basekey-derivedkeyalgorithm-extractable-keyusages
|
||||
[`subtle.digest()`]: #subtledigestalgorithm-data
|
||||
[`subtle.encapsulateBits()`]: #subtleencapsulatebitsencapsulationalgorithm-encapsulationkey
|
||||
[`subtle.encapsulateKey()`]: #subtleencapsulatekeyencapsulationalgorithm-encapsulationkey-sharedkeyalgorithm-extractable-usages
|
||||
[`subtle.encrypt()`]: #subtleencryptalgorithm-key-data
|
||||
[`subtle.exportKey()`]: #subtleexportkeyformat-key
|
||||
[`subtle.generateKey()`]: #subtlegeneratekeyalgorithm-extractable-keyusages
|
||||
[`subtle.getPublicKey()`]: #subtlegetpublickeykey-keyusages
|
||||
[`subtle.importKey()`]: #subtleimportkeyformat-keydata-algorithm-extractable-keyusages
|
||||
[`subtle.sign()`]: #subtlesignalgorithm-key-data
|
||||
[`subtle.unwrapKey()`]: #subtleunwrapkeyformat-wrappedkey-unwrappingkey-unwrapalgo-unwrappedkeyalgo-extractable-keyusages
|
||||
[`subtle.verify()`]: #subtleverifyalgorithm-key-signature-data
|
||||
[`subtle.wrapKey()`]: #subtlewrapkeyformat-key-wrappingkey-wrapalgo
|
||||
|
|
|
|||
|
|
@ -308,6 +308,14 @@ const {
|
|||
result = require('internal/crypto/ml_dsa')
|
||||
.mlDsaImportKey('KeyObject', this, algorithm, extractable, keyUsages);
|
||||
break;
|
||||
case 'ML-KEM-512':
|
||||
// Fall through
|
||||
case 'ML-KEM-768':
|
||||
// Fall through
|
||||
case 'ML-KEM-1024':
|
||||
result = require('internal/crypto/ml_kem')
|
||||
.mlKemImportKey('KeyObject', this, algorithm, extractable, keyUsages);
|
||||
break;
|
||||
default:
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
}
|
||||
|
|
@ -568,7 +576,7 @@ function getKeyObjectHandleFromJwk(key, ctx) {
|
|||
const handle = new KeyObjectHandle();
|
||||
|
||||
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
|
||||
if (!handle.initMlDsaRaw(key.alg, keyData, keyType)) {
|
||||
if (!handle.initPqcRaw(key.alg, keyData, keyType)) {
|
||||
throw new ERR_CRYPTO_INVALID_JWK();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) {
|
|||
function createMlDsaRawKey(name, keyData, isPublic) {
|
||||
const handle = new KeyObjectHandle();
|
||||
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
|
||||
if (!handle.initMlDsaRaw(name, keyData, keyType)) {
|
||||
if (!handle.initPqcRaw(name, keyData, keyType)) {
|
||||
throw lazyDOMException('Invalid keyData', 'DataError');
|
||||
}
|
||||
|
||||
|
|
@ -119,19 +119,16 @@ function mlDsaExportKey(key, format) {
|
|||
switch (format) {
|
||||
case kWebCryptoKeyFormatRaw: {
|
||||
if (key[kKeyType] === 'private') {
|
||||
const { priv } = key[kKeyObject][kHandle].exportJwk({}, false);
|
||||
return Buffer.alloc(32, priv, 'base64url').buffer;
|
||||
return key[kKeyObject][kHandle].rawSeed().buffer;
|
||||
}
|
||||
|
||||
const { pub } = key[kKeyObject][kHandle].exportJwk({}, false);
|
||||
return Buffer.alloc(Buffer.byteLength(pub, 'base64url'), pub, 'base64url').buffer;
|
||||
return key[kKeyObject][kHandle].rawPublicKey().buffer;
|
||||
}
|
||||
case kWebCryptoKeyFormatSPKI: {
|
||||
return key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI).buffer;
|
||||
}
|
||||
case kWebCryptoKeyFormatPKCS8: {
|
||||
const { priv } = key[kKeyObject][kHandle].exportJwk({}, false);
|
||||
const seed = Buffer.alloc(32, priv, 'base64url');
|
||||
const seed = key[kKeyObject][kHandle].rawSeed();
|
||||
const buffer = new Uint8Array(54);
|
||||
buffer.set([
|
||||
0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0B, 0x06,
|
||||
|
|
|
|||
287
lib/internal/crypto/ml_kem.js
Normal file
287
lib/internal/crypto/ml_kem.js
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
PromiseWithResolvers,
|
||||
SafeSet,
|
||||
Uint8Array,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
kCryptoJobAsync,
|
||||
KEMDecapsulateJob,
|
||||
KEMEncapsulateJob,
|
||||
KeyObjectHandle,
|
||||
kKeyFormatDER,
|
||||
kKeyTypePrivate,
|
||||
kKeyTypePublic,
|
||||
kWebCryptoKeyFormatPKCS8,
|
||||
kWebCryptoKeyFormatRaw,
|
||||
kWebCryptoKeyFormatSPKI,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const {
|
||||
getUsagesUnion,
|
||||
hasAnyNotIn,
|
||||
kHandle,
|
||||
kKeyObject,
|
||||
} = require('internal/crypto/util');
|
||||
|
||||
const {
|
||||
lazyDOMException,
|
||||
promisify,
|
||||
} = require('internal/util');
|
||||
|
||||
const {
|
||||
generateKeyPair: _generateKeyPair,
|
||||
} = require('internal/crypto/keygen');
|
||||
|
||||
const {
|
||||
InternalCryptoKey,
|
||||
PrivateKeyObject,
|
||||
PublicKeyObject,
|
||||
createPrivateKey,
|
||||
createPublicKey,
|
||||
kAlgorithm,
|
||||
kKeyType,
|
||||
} = require('internal/crypto/keys');
|
||||
|
||||
const generateKeyPair = promisify(_generateKeyPair);
|
||||
|
||||
async function mlKemGenerateKey(algorithm, extractable, keyUsages) {
|
||||
const { name } = algorithm;
|
||||
|
||||
const usageSet = new SafeSet(keyUsages);
|
||||
if (hasAnyNotIn(usageSet, ['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits'])) {
|
||||
throw lazyDOMException(
|
||||
`Unsupported key usage for an ${name} key`,
|
||||
'SyntaxError');
|
||||
}
|
||||
|
||||
const keyPair = await generateKeyPair(name.toLowerCase()).catch((err) => {
|
||||
throw lazyDOMException(
|
||||
'The operation failed for an operation-specific reason',
|
||||
{ name: 'OperationError', cause: err });
|
||||
});
|
||||
|
||||
const publicUsages = getUsagesUnion(usageSet, 'encapsulateBits', 'encapsulateKey');
|
||||
const privateUsages = getUsagesUnion(usageSet, 'decapsulateBits', 'decapsulateKey');
|
||||
|
||||
const keyAlgorithm = { name };
|
||||
|
||||
const publicKey =
|
||||
new InternalCryptoKey(
|
||||
keyPair.publicKey,
|
||||
keyAlgorithm,
|
||||
publicUsages,
|
||||
true);
|
||||
|
||||
const privateKey =
|
||||
new InternalCryptoKey(
|
||||
keyPair.privateKey,
|
||||
keyAlgorithm,
|
||||
privateUsages,
|
||||
extractable);
|
||||
|
||||
return { __proto__: null, privateKey, publicKey };
|
||||
}
|
||||
|
||||
function mlKemExportKey(key, format) {
|
||||
try {
|
||||
switch (format) {
|
||||
case kWebCryptoKeyFormatRaw: {
|
||||
if (key[kKeyType] === 'private') {
|
||||
return key[kKeyObject][kHandle].rawSeed().buffer;
|
||||
}
|
||||
|
||||
return key[kKeyObject][kHandle].rawPublicKey().buffer;
|
||||
}
|
||||
case kWebCryptoKeyFormatSPKI: {
|
||||
return key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI).buffer;
|
||||
}
|
||||
case kWebCryptoKeyFormatPKCS8: {
|
||||
const seed = key[kKeyObject][kHandle].rawSeed();
|
||||
const buffer = new Uint8Array(86);
|
||||
buffer.set([
|
||||
0x30, 0x54, 0x02, 0x01, 0x00, 0x30, 0x0B, 0x06,
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
|
||||
0x04, 0x00, 0x04, 0x42, 0x80, 0x40,
|
||||
], 0);
|
||||
switch (key[kAlgorithm].name) {
|
||||
case 'ML-KEM-512':
|
||||
buffer.set([0x01], 17);
|
||||
break;
|
||||
case 'ML-KEM-768':
|
||||
buffer.set([0x02], 17);
|
||||
break;
|
||||
case 'ML-KEM-1024':
|
||||
buffer.set([0x03], 17);
|
||||
break;
|
||||
}
|
||||
buffer.set(seed, 22);
|
||||
return buffer.buffer;
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
} catch (err) {
|
||||
throw lazyDOMException(
|
||||
'The operation failed for an operation-specific reason',
|
||||
{ name: 'OperationError', cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
function verifyAcceptableMlKemKeyUse(name, isPublic, usages) {
|
||||
const checkSet = isPublic ? ['encapsulateKey', 'encapsulateBits'] : ['decapsulateKey', 'decapsulateBits'];
|
||||
if (hasAnyNotIn(usages, checkSet)) {
|
||||
throw lazyDOMException(
|
||||
`Unsupported key usage for a ${name} key`,
|
||||
'SyntaxError');
|
||||
}
|
||||
}
|
||||
|
||||
function createMlKemRawKey(name, keyData, isPublic) {
|
||||
const handle = new KeyObjectHandle();
|
||||
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
|
||||
if (!handle.initPqcRaw(name, keyData, keyType)) {
|
||||
throw lazyDOMException('Invalid keyData', 'DataError');
|
||||
}
|
||||
|
||||
return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
|
||||
}
|
||||
|
||||
function mlKemImportKey(
|
||||
format,
|
||||
keyData,
|
||||
algorithm,
|
||||
extractable,
|
||||
keyUsages) {
|
||||
|
||||
const { name } = algorithm;
|
||||
let keyObject;
|
||||
const usagesSet = new SafeSet(keyUsages);
|
||||
switch (format) {
|
||||
case 'KeyObject': {
|
||||
verifyAcceptableMlKemKeyUse(name, keyData.type === 'public', usagesSet);
|
||||
keyObject = keyData;
|
||||
break;
|
||||
}
|
||||
case 'spki': {
|
||||
verifyAcceptableMlKemKeyUse(name, true, usagesSet);
|
||||
try {
|
||||
keyObject = createPublicKey({
|
||||
key: keyData,
|
||||
format: 'der',
|
||||
type: 'spki',
|
||||
});
|
||||
} catch (err) {
|
||||
throw lazyDOMException(
|
||||
'Invalid keyData', { name: 'DataError', cause: err });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pkcs8': {
|
||||
verifyAcceptableMlKemKeyUse(name, false, usagesSet);
|
||||
try {
|
||||
keyObject = createPrivateKey({
|
||||
key: keyData,
|
||||
format: 'der',
|
||||
type: 'pkcs8',
|
||||
});
|
||||
} catch (err) {
|
||||
throw lazyDOMException(
|
||||
'Invalid keyData', { name: 'DataError', cause: err });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'raw-public':
|
||||
case 'raw-seed': {
|
||||
const isPublic = format === 'raw-public';
|
||||
verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet);
|
||||
|
||||
try {
|
||||
keyObject = createMlKemRawKey(name, keyData, isPublic);
|
||||
} catch (err) {
|
||||
throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err });
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
|
||||
throw lazyDOMException('Invalid key type', 'DataError');
|
||||
}
|
||||
|
||||
return new InternalCryptoKey(
|
||||
keyObject,
|
||||
{ name },
|
||||
keyUsages,
|
||||
extractable);
|
||||
}
|
||||
|
||||
function mlKemEncapsulate(encapsulationKey) {
|
||||
if (encapsulationKey[kKeyType] !== 'public') {
|
||||
throw lazyDOMException(`Key must be a public key`, 'InvalidAccessError');
|
||||
}
|
||||
|
||||
const { promise, resolve, reject } = PromiseWithResolvers();
|
||||
|
||||
const job = new KEMEncapsulateJob(
|
||||
kCryptoJobAsync,
|
||||
encapsulationKey[kKeyObject][kHandle],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined);
|
||||
|
||||
job.ondone = (error, result) => {
|
||||
if (error) {
|
||||
reject(lazyDOMException(
|
||||
'The operation failed for an operation-specific reason',
|
||||
{ name: 'OperationError', cause: error }));
|
||||
} else {
|
||||
const { 0: sharedKey, 1: ciphertext } = result;
|
||||
resolve({ sharedKey: sharedKey.buffer, ciphertext: ciphertext.buffer });
|
||||
}
|
||||
};
|
||||
job.run();
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function mlKemDecapsulate(decapsulationKey, ciphertext) {
|
||||
if (decapsulationKey[kKeyType] !== 'private') {
|
||||
throw lazyDOMException(`Key must be a private key`, 'InvalidAccessError');
|
||||
}
|
||||
|
||||
const { promise, resolve, reject } = PromiseWithResolvers();
|
||||
|
||||
const job = new KEMDecapsulateJob(
|
||||
kCryptoJobAsync,
|
||||
decapsulationKey[kKeyObject][kHandle],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
ciphertext);
|
||||
|
||||
job.ondone = (error, result) => {
|
||||
if (error) {
|
||||
reject(lazyDOMException(
|
||||
'The operation failed for an operation-specific reason',
|
||||
{ name: 'OperationError', cause: error }));
|
||||
} else {
|
||||
resolve(result.buffer);
|
||||
}
|
||||
};
|
||||
job.run();
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mlKemExportKey,
|
||||
mlKemImportKey,
|
||||
mlKemEncapsulate,
|
||||
mlKemDecapsulate,
|
||||
mlKemGenerateKey,
|
||||
};
|
||||
|
|
@ -36,6 +36,9 @@ const {
|
|||
EVP_PKEY_ML_DSA_44,
|
||||
EVP_PKEY_ML_DSA_65,
|
||||
EVP_PKEY_ML_DSA_87,
|
||||
EVP_PKEY_ML_KEM_512,
|
||||
EVP_PKEY_ML_KEM_768,
|
||||
EVP_PKEY_ML_KEM_1024,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const { getOptionValue } = require('internal/options');
|
||||
|
|
@ -276,6 +279,27 @@ const kAlgorithmDefinitions = {
|
|||
'sign': 'ContextParams',
|
||||
'verify': 'ContextParams',
|
||||
},
|
||||
'ML-KEM-512': {
|
||||
'generateKey': null,
|
||||
'exportKey': null,
|
||||
'importKey': null,
|
||||
'encapsulate': null,
|
||||
'decapsulate': null,
|
||||
},
|
||||
'ML-KEM-768': {
|
||||
'generateKey': null,
|
||||
'exportKey': null,
|
||||
'importKey': null,
|
||||
'encapsulate': null,
|
||||
'decapsulate': null,
|
||||
},
|
||||
'ML-KEM-1024': {
|
||||
'generateKey': null,
|
||||
'exportKey': null,
|
||||
'importKey': null,
|
||||
'encapsulate': null,
|
||||
'decapsulate': null,
|
||||
},
|
||||
'PBKDF2': {
|
||||
'importKey': null,
|
||||
'deriveBits': 'Pbkdf2Params',
|
||||
|
|
@ -336,6 +360,9 @@ const conditionalAlgorithms = {
|
|||
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
|
||||
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
|
||||
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
|
||||
'ML-KEM-512': !!EVP_PKEY_ML_KEM_512,
|
||||
'ML-KEM-768': !!EVP_PKEY_ML_KEM_768,
|
||||
'ML-KEM-1024': !!EVP_PKEY_ML_KEM_1024,
|
||||
'SHA3-256': !process.features.openssl_is_boringssl ||
|
||||
ArrayPrototypeIncludes(getHashes(), 'sha3-256'),
|
||||
'SHA3-384': !process.features.openssl_is_boringssl ||
|
||||
|
|
@ -354,6 +381,9 @@ const experimentalAlgorithms = [
|
|||
'ML-DSA-44',
|
||||
'ML-DSA-65',
|
||||
'ML-DSA-87',
|
||||
'ML-KEM-512',
|
||||
'ML-KEM-768',
|
||||
'ML-KEM-1024',
|
||||
'SHA3-256',
|
||||
'SHA3-384',
|
||||
'SHA3-512',
|
||||
|
|
|
|||
|
|
@ -174,6 +174,15 @@ async function generateKey(
|
|||
result = await require('internal/crypto/ml_dsa')
|
||||
.mlDsaGenerateKey(algorithm, extractable, keyUsages);
|
||||
break;
|
||||
case 'ML-KEM-512':
|
||||
// Fall through
|
||||
case 'ML-KEM-768':
|
||||
// Fall through
|
||||
case 'ML-KEM-1024':
|
||||
resultType = 'CryptoKeyPair';
|
||||
result = await require('internal/crypto/ml_kem')
|
||||
.mlKemGenerateKey(algorithm, extractable, keyUsages);
|
||||
break;
|
||||
default:
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
}
|
||||
|
|
@ -366,10 +375,16 @@ async function exportKeySpki(key) {
|
|||
// Fall through
|
||||
case 'ML-DSA-65':
|
||||
// Fall through
|
||||
case 'ML-DSA-87': {
|
||||
case 'ML-DSA-87':
|
||||
return require('internal/crypto/ml_dsa')
|
||||
.mlDsaExportKey(key, kWebCryptoKeyFormatSPKI);
|
||||
}
|
||||
case 'ML-KEM-512':
|
||||
// Fall through
|
||||
case 'ML-KEM-768':
|
||||
// Fall through
|
||||
case 'ML-KEM-1024':
|
||||
return require('internal/crypto/ml_kem')
|
||||
.mlKemExportKey(key, kWebCryptoKeyFormatSPKI);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -402,10 +417,16 @@ async function exportKeyPkcs8(key) {
|
|||
// Fall through
|
||||
case 'ML-DSA-65':
|
||||
// Fall through
|
||||
case 'ML-DSA-87': {
|
||||
case 'ML-DSA-87':
|
||||
return require('internal/crypto/ml_dsa')
|
||||
.mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8);
|
||||
}
|
||||
case 'ML-KEM-512':
|
||||
// Fall through
|
||||
case 'ML-KEM-768':
|
||||
// Fall through
|
||||
case 'ML-KEM-1024':
|
||||
return require('internal/crypto/ml_kem')
|
||||
.mlKemExportKey(key, kWebCryptoKeyFormatPKCS8);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -439,6 +460,18 @@ async function exportKeyRawPublic(key, format) {
|
|||
return require('internal/crypto/ml_dsa')
|
||||
.mlDsaExportKey(key, kWebCryptoKeyFormatRaw);
|
||||
}
|
||||
case 'ML-KEM-512':
|
||||
// Fall through
|
||||
case 'ML-KEM-768':
|
||||
// Fall through
|
||||
case 'ML-KEM-1024': {
|
||||
// ML-KEM keys don't recognize "raw"
|
||||
if (format !== 'raw-public') {
|
||||
return undefined;
|
||||
}
|
||||
return require('internal/crypto/ml_kem')
|
||||
.mlKemExportKey(key, kWebCryptoKeyFormatRaw);
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -450,10 +483,16 @@ async function exportKeyRawSeed(key) {
|
|||
// Fall through
|
||||
case 'ML-DSA-65':
|
||||
// Fall through
|
||||
case 'ML-DSA-87': {
|
||||
case 'ML-DSA-87':
|
||||
return require('internal/crypto/ml_dsa')
|
||||
.mlDsaExportKey(key, kWebCryptoKeyFormatRaw);
|
||||
}
|
||||
case 'ML-KEM-512':
|
||||
// Fall through
|
||||
case 'ML-KEM-768':
|
||||
// Fall through
|
||||
case 'ML-KEM-1024':
|
||||
return require('internal/crypto/ml_kem')
|
||||
.mlKemExportKey(key, kWebCryptoKeyFormatRaw);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -746,6 +785,14 @@ async function importKey(
|
|||
result = require('internal/crypto/ml_dsa')
|
||||
.mlDsaImportKey(format, keyData, algorithm, extractable, keyUsages);
|
||||
break;
|
||||
case 'ML-KEM-512':
|
||||
// Fall through
|
||||
case 'ML-KEM-768':
|
||||
// Fall through
|
||||
case 'ML-KEM-1024':
|
||||
result = require('internal/crypto/ml_kem')
|
||||
.mlKemImportKey(format, keyData, algorithm, extractable, keyUsages);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
|
|
@ -1099,6 +1146,229 @@ async function getPublicKey(key, keyUsages) {
|
|||
return keyObject.toCryptoKey(key[kAlgorithm], true, keyUsages);
|
||||
}
|
||||
|
||||
async function encapsulateBits(encapsulationAlgorithm, encapsulationKey) {
|
||||
emitExperimentalWarning('The encapsulateBits Web Crypto API method');
|
||||
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
|
||||
|
||||
webidl ??= require('internal/crypto/webidl');
|
||||
const prefix = "Failed to execute 'encapsulateBits' on 'SubtleCrypto'";
|
||||
webidl.requiredArguments(arguments.length, 2, { prefix });
|
||||
encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(encapsulationAlgorithm, {
|
||||
prefix,
|
||||
context: '1st argument',
|
||||
});
|
||||
encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, {
|
||||
prefix,
|
||||
context: '2nd argument',
|
||||
});
|
||||
|
||||
const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate');
|
||||
|
||||
if (normalizedEncapsulationAlgorithm.name !== encapsulationKey[kAlgorithm].name) {
|
||||
throw lazyDOMException(
|
||||
'key algorithm mismatch',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
if (!ArrayPrototypeIncludes(encapsulationKey[kKeyUsages], 'encapsulateBits')) {
|
||||
throw lazyDOMException(
|
||||
'encapsulationKey does not have encapsulateBits usage',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
switch (encapsulationKey[kAlgorithm].name) {
|
||||
case 'ML-KEM-512':
|
||||
case 'ML-KEM-768':
|
||||
case 'ML-KEM-1024':
|
||||
return require('internal/crypto/ml_kem')
|
||||
.mlKemEncapsulate(encapsulationKey);
|
||||
}
|
||||
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
}
|
||||
|
||||
async function encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages) {
|
||||
emitExperimentalWarning('The encapsulateKey Web Crypto API method');
|
||||
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
|
||||
|
||||
webidl ??= require('internal/crypto/webidl');
|
||||
const prefix = "Failed to execute 'encapsulateKey' on 'SubtleCrypto'";
|
||||
webidl.requiredArguments(arguments.length, 5, { prefix });
|
||||
encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(encapsulationAlgorithm, {
|
||||
prefix,
|
||||
context: '1st argument',
|
||||
});
|
||||
encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, {
|
||||
prefix,
|
||||
context: '2nd argument',
|
||||
});
|
||||
sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(sharedKeyAlgorithm, {
|
||||
prefix,
|
||||
context: '3rd argument',
|
||||
});
|
||||
extractable = webidl.converters.boolean(extractable, {
|
||||
prefix,
|
||||
context: '4th argument',
|
||||
});
|
||||
usages = webidl.converters['sequence<KeyUsage>'](usages, {
|
||||
prefix,
|
||||
context: '5th argument',
|
||||
});
|
||||
|
||||
const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate');
|
||||
const normalizedSharedKeyAlgorithm = normalizeAlgorithm(sharedKeyAlgorithm, 'importKey');
|
||||
|
||||
if (normalizedEncapsulationAlgorithm.name !== encapsulationKey[kAlgorithm].name) {
|
||||
throw lazyDOMException(
|
||||
'key algorithm mismatch',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
if (!ArrayPrototypeIncludes(encapsulationKey[kKeyUsages], 'encapsulateKey')) {
|
||||
throw lazyDOMException(
|
||||
'encapsulationKey does not have encapsulateKey usage',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
let encapsulateBits;
|
||||
switch (encapsulationKey[kAlgorithm].name) {
|
||||
case 'ML-KEM-512':
|
||||
case 'ML-KEM-768':
|
||||
case 'ML-KEM-1024':
|
||||
encapsulateBits = await require('internal/crypto/ml_kem')
|
||||
.mlKemEncapsulate(encapsulationKey);
|
||||
break;
|
||||
default:
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
}
|
||||
|
||||
const sharedKey = await ReflectApply(
|
||||
importKey,
|
||||
this,
|
||||
['raw-secret', encapsulateBits.sharedKey, normalizedSharedKeyAlgorithm, extractable, usages],
|
||||
);
|
||||
|
||||
const encapsulatedKey = {
|
||||
ciphertext: encapsulateBits.ciphertext,
|
||||
sharedKey,
|
||||
};
|
||||
|
||||
return encapsulatedKey;
|
||||
}
|
||||
|
||||
async function decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext) {
|
||||
emitExperimentalWarning('The decapsulateBits Web Crypto API method');
|
||||
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
|
||||
|
||||
webidl ??= require('internal/crypto/webidl');
|
||||
const prefix = "Failed to execute 'decapsulateBits' on 'SubtleCrypto'";
|
||||
webidl.requiredArguments(arguments.length, 3, { prefix });
|
||||
decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(decapsulationAlgorithm, {
|
||||
prefix,
|
||||
context: '1st argument',
|
||||
});
|
||||
decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, {
|
||||
prefix,
|
||||
context: '2nd argument',
|
||||
});
|
||||
ciphertext = webidl.converters.BufferSource(ciphertext, {
|
||||
prefix,
|
||||
context: '3rd argument',
|
||||
});
|
||||
|
||||
const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate');
|
||||
|
||||
if (normalizedDecapsulationAlgorithm.name !== decapsulationKey[kAlgorithm].name) {
|
||||
throw lazyDOMException(
|
||||
'key algorithm mismatch',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
if (!ArrayPrototypeIncludes(decapsulationKey[kKeyUsages], 'decapsulateBits')) {
|
||||
throw lazyDOMException(
|
||||
'decapsulationKey does not have decapsulateBits usage',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
switch (decapsulationKey[kAlgorithm].name) {
|
||||
case 'ML-KEM-512':
|
||||
case 'ML-KEM-768':
|
||||
case 'ML-KEM-1024':
|
||||
return require('internal/crypto/ml_kem')
|
||||
.mlKemDecapsulate(decapsulationKey, ciphertext);
|
||||
}
|
||||
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
}
|
||||
|
||||
async function decapsulateKey(
|
||||
decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages,
|
||||
) {
|
||||
emitExperimentalWarning('The decapsulateKey Web Crypto API method');
|
||||
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
|
||||
|
||||
webidl ??= require('internal/crypto/webidl');
|
||||
const prefix = "Failed to execute 'decapsulateKey' on 'SubtleCrypto'";
|
||||
webidl.requiredArguments(arguments.length, 6, { prefix });
|
||||
decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(decapsulationAlgorithm, {
|
||||
prefix,
|
||||
context: '1st argument',
|
||||
});
|
||||
decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, {
|
||||
prefix,
|
||||
context: '2nd argument',
|
||||
});
|
||||
ciphertext = webidl.converters.BufferSource(ciphertext, {
|
||||
prefix,
|
||||
context: '3rd argument',
|
||||
});
|
||||
sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(sharedKeyAlgorithm, {
|
||||
prefix,
|
||||
context: '4th argument',
|
||||
});
|
||||
extractable = webidl.converters.boolean(extractable, {
|
||||
prefix,
|
||||
context: '5th argument',
|
||||
});
|
||||
usages = webidl.converters['sequence<KeyUsage>'](usages, {
|
||||
prefix,
|
||||
context: '6th argument',
|
||||
});
|
||||
|
||||
const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate');
|
||||
const normalizedSharedKeyAlgorithm = normalizeAlgorithm(sharedKeyAlgorithm, 'importKey');
|
||||
|
||||
if (normalizedDecapsulationAlgorithm.name !== decapsulationKey[kAlgorithm].name) {
|
||||
throw lazyDOMException(
|
||||
'key algorithm mismatch',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
if (!ArrayPrototypeIncludes(decapsulationKey[kKeyUsages], 'decapsulateKey')) {
|
||||
throw lazyDOMException(
|
||||
'decapsulationKey does not have decapsulateKey usage',
|
||||
'InvalidAccessError');
|
||||
}
|
||||
|
||||
let decapsulatedBits;
|
||||
switch (decapsulationKey[kAlgorithm].name) {
|
||||
case 'ML-KEM-512':
|
||||
case 'ML-KEM-768':
|
||||
case 'ML-KEM-1024':
|
||||
decapsulatedBits = await require('internal/crypto/ml_kem')
|
||||
.mlKemDecapsulate(decapsulationKey, ciphertext);
|
||||
break;
|
||||
default:
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
}
|
||||
|
||||
return ReflectApply(
|
||||
importKey,
|
||||
this,
|
||||
['raw-secret', decapsulatedBits, normalizedSharedKeyAlgorithm, extractable, usages],
|
||||
);
|
||||
}
|
||||
|
||||
// The SubtleCrypto and Crypto classes are defined as part of the
|
||||
// Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/
|
||||
|
||||
|
|
@ -1125,19 +1395,23 @@ class SubtleCrypto {
|
|||
});
|
||||
|
||||
switch (operation) {
|
||||
case 'encrypt':
|
||||
case 'decapsulateBits':
|
||||
case 'decapsulateKey':
|
||||
case 'decrypt':
|
||||
case 'sign':
|
||||
case 'verify':
|
||||
case 'digest':
|
||||
case 'generateKey':
|
||||
case 'deriveKey':
|
||||
case 'deriveBits':
|
||||
case 'importKey':
|
||||
case 'deriveKey':
|
||||
case 'digest':
|
||||
case 'encapsulateBits':
|
||||
case 'encapsulateKey':
|
||||
case 'encrypt':
|
||||
case 'exportKey':
|
||||
case 'wrapKey':
|
||||
case 'unwrapKey':
|
||||
case 'generateKey':
|
||||
case 'getPublicKey':
|
||||
case 'importKey':
|
||||
case 'sign':
|
||||
case 'unwrapKey':
|
||||
case 'verify':
|
||||
case 'wrapKey':
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
|
|
@ -1208,6 +1482,42 @@ class SubtleCrypto {
|
|||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (operation === 'encapsulateKey' || operation === 'decapsulateKey') {
|
||||
additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
|
||||
prefix,
|
||||
context: '3rd argument',
|
||||
});
|
||||
|
||||
let normalizedAdditionalAlgorithm;
|
||||
try {
|
||||
normalizedAdditionalAlgorithm = normalizeAlgorithm(additionalAlgorithm, 'importKey');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (normalizedAdditionalAlgorithm.name) {
|
||||
case 'AES-OCB':
|
||||
case 'AES-KW':
|
||||
case 'AES-GCM':
|
||||
case 'AES-CTR':
|
||||
case 'AES-CBC':
|
||||
case 'ChaCha20-Poly1305':
|
||||
case 'HKDF':
|
||||
case 'PBKDF2':
|
||||
case 'Argon2i':
|
||||
case 'Argon2d':
|
||||
case 'Argon2id':
|
||||
break;
|
||||
case 'HMAC':
|
||||
case 'KMAC128':
|
||||
case 'KMAC256':
|
||||
if (normalizedAdditionalAlgorithm.length === undefined || normalizedAdditionalAlgorithm.length === 256) {
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return check(operation, algorithm, length);
|
||||
|
|
@ -1215,6 +1525,14 @@ class SubtleCrypto {
|
|||
}
|
||||
|
||||
function check(op, alg, length) {
|
||||
if (op === 'encapsulateBits' || op === 'encapsulateKey') {
|
||||
op = 'encapsulate';
|
||||
}
|
||||
|
||||
if (op === 'decapsulateBits' || op === 'decapsulateKey') {
|
||||
op = 'decapsulate';
|
||||
}
|
||||
|
||||
let normalizedAlgorithm;
|
||||
try {
|
||||
normalizedAlgorithm = normalizeAlgorithm(alg, op);
|
||||
|
|
@ -1231,15 +1549,17 @@ function check(op, alg, length) {
|
|||
}
|
||||
|
||||
switch (op) {
|
||||
case 'encrypt':
|
||||
case 'decapsulate':
|
||||
case 'decrypt':
|
||||
case 'sign':
|
||||
case 'verify':
|
||||
case 'digest':
|
||||
case 'importKey':
|
||||
case 'encapsulate':
|
||||
case 'encrypt':
|
||||
case 'exportKey':
|
||||
case 'wrapKey':
|
||||
case 'importKey':
|
||||
case 'sign':
|
||||
case 'unwrapKey':
|
||||
case 'verify':
|
||||
case 'wrapKey':
|
||||
return true;
|
||||
case 'deriveBits': {
|
||||
if (normalizedAlgorithm.name === 'HKDF') {
|
||||
|
|
@ -1428,6 +1748,34 @@ ObjectDefineProperties(
|
|||
writable: true,
|
||||
value: getPublicKey,
|
||||
},
|
||||
encapsulateBits: {
|
||||
__proto__: null,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: encapsulateBits,
|
||||
},
|
||||
encapsulateKey: {
|
||||
__proto__: null,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: encapsulateKey,
|
||||
},
|
||||
decapsulateBits: {
|
||||
__proto__: null,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: decapsulateBits,
|
||||
},
|
||||
decapsulateKey: {
|
||||
__proto__: null,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: decapsulateKey,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -362,6 +362,10 @@ converters.KeyUsage = createEnumConverter('KeyUsage', [
|
|||
'deriveBits',
|
||||
'wrapKey',
|
||||
'unwrapKey',
|
||||
'encapsulateBits',
|
||||
'decapsulateBits',
|
||||
'encapsulateKey',
|
||||
'decapsulateKey',
|
||||
]);
|
||||
|
||||
converters['sequence<KeyUsage>'] = createSequenceConverter(converters.KeyUsage);
|
||||
|
|
|
|||
|
|
@ -287,6 +287,12 @@ int GetNidFromName(const char* name) {
|
|||
nid = EVP_PKEY_ML_DSA_65;
|
||||
} else if (strcmp(name, "ML-DSA-87") == 0) {
|
||||
nid = EVP_PKEY_ML_DSA_87;
|
||||
} else if (strcmp(name, "ML-KEM-512") == 0) {
|
||||
nid = EVP_PKEY_ML_KEM_512;
|
||||
} else if (strcmp(name, "ML-KEM-768") == 0) {
|
||||
nid = EVP_PKEY_ML_KEM_768;
|
||||
} else if (strcmp(name, "ML-KEM-1024") == 0) {
|
||||
nid = EVP_PKEY_ML_KEM_1024;
|
||||
#endif
|
||||
} else {
|
||||
nid = NID_undef;
|
||||
|
|
@ -621,7 +627,9 @@ Local<Function> KeyObjectHandle::Initialize(Environment* env) {
|
|||
SetProtoMethod(isolate, templ, "initECRaw", InitECRaw);
|
||||
SetProtoMethod(isolate, templ, "initEDRaw", InitEDRaw);
|
||||
#if OPENSSL_WITH_PQC
|
||||
SetProtoMethod(isolate, templ, "initMlDsaRaw", InitMlDsaRaw);
|
||||
SetProtoMethod(isolate, templ, "initPqcRaw", InitPqcRaw);
|
||||
SetProtoMethodNoSideEffect(isolate, templ, "rawPublicKey", RawPublicKey);
|
||||
SetProtoMethodNoSideEffect(isolate, templ, "rawSeed", RawSeed);
|
||||
#endif
|
||||
SetProtoMethod(isolate, templ, "initJwk", InitJWK);
|
||||
SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail);
|
||||
|
|
@ -644,7 +652,9 @@ void KeyObjectHandle::RegisterExternalReferences(
|
|||
registry->Register(InitECRaw);
|
||||
registry->Register(InitEDRaw);
|
||||
#if OPENSSL_WITH_PQC
|
||||
registry->Register(InitMlDsaRaw);
|
||||
registry->Register(InitPqcRaw);
|
||||
registry->Register(RawPublicKey);
|
||||
registry->Register(RawSeed);
|
||||
#endif
|
||||
registry->Register(InitJWK);
|
||||
registry->Register(GetKeyDetail);
|
||||
|
|
@ -839,7 +849,7 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) {
|
|||
}
|
||||
|
||||
#if OPENSSL_WITH_PQC
|
||||
void KeyObjectHandle::InitMlDsaRaw(const FunctionCallbackInfo<Value>& args) {
|
||||
void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
KeyObjectHandle* key;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
|
||||
|
|
@ -862,7 +872,10 @@ void KeyObjectHandle::InitMlDsaRaw(const FunctionCallbackInfo<Value>& args) {
|
|||
switch (id) {
|
||||
case EVP_PKEY_ML_DSA_44:
|
||||
case EVP_PKEY_ML_DSA_65:
|
||||
case EVP_PKEY_ML_DSA_87: {
|
||||
case EVP_PKEY_ML_DSA_87:
|
||||
case EVP_PKEY_ML_KEM_512:
|
||||
case EVP_PKEY_ML_KEM_768:
|
||||
case EVP_PKEY_ML_KEM_1024: {
|
||||
auto pkey = fn(id,
|
||||
ncrypto::Buffer<const unsigned char>{
|
||||
.data = key_data.data(),
|
||||
|
|
@ -1083,6 +1096,50 @@ MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
|
|||
return WritePrivateKey(env(), data_.GetAsymmetricKey(), config);
|
||||
}
|
||||
|
||||
#if OPENSSL_WITH_PQC
|
||||
void KeyObjectHandle::RawPublicKey(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
KeyObjectHandle* key;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
|
||||
|
||||
const KeyObjectData& data = key->Data();
|
||||
CHECK_NE(data.GetKeyType(), kKeyTypeSecret);
|
||||
|
||||
Mutex::ScopedLock lock(data.mutex());
|
||||
auto raw_data = data.GetAsymmetricKey().rawPublicKey();
|
||||
if (!raw_data) {
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
|
||||
"Failed to get raw public key");
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(
|
||||
Buffer::Copy(
|
||||
env, reinterpret_cast<const char*>(raw_data.get()), raw_data.size())
|
||||
.FromMaybe(Local<Value>()));
|
||||
}
|
||||
|
||||
void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
KeyObjectHandle* key;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
|
||||
|
||||
const KeyObjectData& data = key->Data();
|
||||
CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);
|
||||
|
||||
Mutex::ScopedLock lock(data.mutex());
|
||||
auto raw_data = data.GetAsymmetricKey().rawSeed();
|
||||
if (!raw_data) {
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed");
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(
|
||||
Buffer::Copy(
|
||||
env, reinterpret_cast<const char*>(raw_data.get()), raw_data.size())
|
||||
.FromMaybe(Local<Value>()));
|
||||
}
|
||||
#endif
|
||||
|
||||
void KeyObjectHandle::ExportJWK(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
|
|
|||
|
|
@ -152,9 +152,6 @@ class KeyObjectHandle : public BaseObject {
|
|||
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void InitECRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
#if OPENSSL_WITH_PQC
|
||||
static void InitMlDsaRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
#endif
|
||||
static void InitJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetKeyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Equals(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
|
@ -173,6 +170,12 @@ class KeyObjectHandle : public BaseObject {
|
|||
|
||||
static void Export(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
#if OPENSSL_WITH_PQC
|
||||
static void InitPqcRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void RawPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void RawSeed(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
#endif
|
||||
|
||||
v8::MaybeLocal<v8::Value> ExportSecretKey() const;
|
||||
v8::MaybeLocal<v8::Value> ExportPublicKey(
|
||||
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const;
|
||||
|
|
|
|||
49
test/fixtures/crypto/ml-kem.js
vendored
Normal file
49
test/fixtures/crypto/ml-kem.js
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
'use strict';
|
||||
|
||||
const fixtures = require('../../common/fixtures');
|
||||
|
||||
function getKeyFileName(type, suffix) {
|
||||
return `${type.replaceAll('-', '_')}_${suffix}.pem`;
|
||||
}
|
||||
|
||||
module.exports = function() {
|
||||
const pkcs8 = {
|
||||
'ML-KEM-512': fixtures.readKey(getKeyFileName('ml-kem-512', 'private_seed_only'), 'ascii'),
|
||||
'ML-KEM-768': fixtures.readKey(getKeyFileName('ml-kem-768', 'private_seed_only'), 'ascii'),
|
||||
'ML-KEM-1024': fixtures.readKey(getKeyFileName('ml-kem-1024', 'private_seed_only'), 'ascii'),
|
||||
}
|
||||
|
||||
const spki = {
|
||||
'ML-KEM-512': fixtures.readKey(getKeyFileName('ml-kem-512', 'public'), 'ascii'),
|
||||
'ML-KEM-768': fixtures.readKey(getKeyFileName('ml-kem-768', 'public'), 'ascii'),
|
||||
'ML-KEM-1024': fixtures.readKey(getKeyFileName('ml-kem-1024', 'public'), 'ascii'),
|
||||
}
|
||||
|
||||
/* eslint-disable @stylistic/js/max-len */
|
||||
const results = {
|
||||
'ML-KEM-512': {
|
||||
ciphertext: Buffer.from('3b7ac92f6140c9fa0348f112ee8211ee668ce657b8bb6352a076880dd7ff4ca7c0babf635f1d36cab5106b8287504d572fab1d0fa3e086310564bd853bdd96cff460eeaa49be316eb95e47c477eeeec7276422abb44ce349016d80eb28f3519a64f1e1f7df63730a1ae83f6cd88af463fae7552103f1eff1097d2b80fa7539b10355136753d725dafd9311fca3d3b5353e2af9ac7f514c420dd7cf8a7e95eec82a37b39f29806db0c6469e884787054c8e48d1416e28fc5c809e42e0abe4547ba9f183f4ab3ce27da63d858b0a2c0970dce72674b5f2c62d6016fc9557b788685cb0b1c7ed3dbe5263463618edb729823f6afb349c3efd229bd03d94393880e48e9d5c80905702fd8bbaf19050dd67029f77df4d37a053fce22a41bde0a464f3940acada39d96a7e20dfef6f02d8317e40cbbe584fde7eeccd02db0b6e1b16c42eb2895afaafebeba0f628907af2a7de9da22afecf77ae1e3abd9593ed545736c766aafe7b223e3b8ab96084a8e14b81bdeff5b5d6c34a107f5108d99d76940535b0a0d3ffcd7e1085ef3e1c1fb54667af235d12f1b8a153faa5fc033676c81a4f39835c29a5ccc1ebb170c8f1717a9f0660ad353e5c18277e3c5e1c999431e51601f3d0a30b027b529489c83ab8da3e00faefc83c5df551c32446280db37940e347f283ac504b2f6a4ca3de69598dd5b73cbf64a2efac948f4793576ba59aafebff69cbfa53a9833d8a2af9b43bbb31c5c18248ddd3c869f7c4d701bd8050345c33bf87ae52cd97246930d84736723b777ab25d08233501a116ded57c28007944c7f3bfb469f0e7d54e643deb543014b6ce9ca4dce6f56a11af37ec0846bf8023efc5127af080e2ecf1c568626cd19842386f3f722d11f153c0167951692dab5468e5f8bc978fc21cca1fdbb6e1d2b27440bb132014da61536ec9c0175d4a7f6ab07bb57004b929a9efb452c718fbf754afb5433df64ae6b95850a1cdbe1e9d743b216f9e1465e488c3fe813a342138134ff73323210eea5950019b35940f8ab340a42054e4143dad43ebc4e9a363e610b5bef73f2de70642e3aa4a631f07b4', 'hex'),
|
||||
sharedKey: Buffer.from('f6d03353f51c73a55611f307f15eaa2fc8527213667370df2ccab580a0b50da3', 'hex'),
|
||||
},
|
||||
'ML-KEM-768': {
|
||||
ciphertext: Buffer.from('90a9c1f4d9a9ab9b90acd011a9b881c18e692e2019d452c11d81300259d3f2d1df10ea4d352574a4e7f84ba78779631d927d8172dea8118e3a9b16efc28d92f15e75a2a223f68155ad39a8f94e95eeda8e5fe14257f7e3158bb8f12927d48e616318569b8cedaa9d71e5a6c883e6344e2ae5cf55ef0cc484b02ecb1dffcb8146971d6bf043a6b772b2a8aeaf6c2549c7695d389e3e6e106daa4b40a4cb429aff76f04e2524b635d4f4e7e820381da770c9b7b53e37cfb84f3744ca8a37a2e150104b3fe82f2c93d5fbbf77b55ccf2c9ea209cd3a950c3e986ee109d10cb492b1e6e1d39672d4c56ce986fb29c8687c5f8efa8be5cb96d0e11162976f6bd04404cad93598187764b38acde20674ea797e1df86869de406cfb863c7d95c94aa419c32f78caae4c86114906edf4bcc41331fac7ff92c2c695452952b7abf7f726f46d22653ce9e77eedf08696ebcd29b6f9a129f8c66ebf6d41c5066f652d79f1cc0b1d3098e20e34917308b1c3a62a10040bf5d6b761fe78be0502f05f5f11a3c443458d185820e132edbe7effb8247d4e397c832d073b822a611b26830b3d3225ee9c56d00348ed234b71bda05c39f1743573b7252081acc36cdb5966349da9faebb1cc269f9584c6cedc412bdb53e70e27cd1dada840fb5e972a0004edbf58b4828e434b7f1fc87814bbe77a8b36bf42fc8d47a5a09dfc1f1e551e47114fdb688dd3c7dd85ae582f169720e7df0b96896e96df39881e83d7e58f5b55c7ecc2fdd0e20e384942db0bbd10edf28c76a84d29607b394e774b7f528874722c57088fe6ffe41428f6c3618578031632e99b7bb9145f035eadb3e621c39b2a4eae17d4fdfb12eaef29e38d915474866911e6b0a963e25b16cf5b0baab3dd07f46dd5e8bd07e5f8b20c346200b3eba57d01d7e365c9309c73ea8c0a1aecb154bf810e08612220bd03733f7750ff291c07b8c7b8dfc3cb889dcbe268832e80e9637d32a38dfbcccc8600f3f1356718708b7d593a90988f584755e16738b2ca0465a2c5920e7be7f180db854a7b59fedbd5252e435e04f5d800b603e04a844a073ead6c43ffd7aa85633fe54be40e371fdcc19e9596292e3745b5bd5a1bc92948489a558c9089c17ff9c05c93dffbc28ac8f4fbd53a54f2017e68db6609d7a7a20f7ab858ce6a204d1f43aeb4599e66d9e0dea307d253b7882da6a6d18dd87a285c6c1ed31c88193e1a103ee175380a89ca8f0fb91fad3e383c88bcf42c966c36c676a7cad83844360ee4ca4c391569aebf2a0df09274116ee03fdfeb6ef4307928e7aae14b311d450fa2b073afac336a85ee3b1f734534feb75b8eec929175fdaa2a7b96dea45b14f897f15b8085dc126ea49df13edb4cbbbd10aeeb688de0e2809faf3e912a4aadce23df8655326079701f035b6acbb8b22fb2b25649b77249eb9c829405aaedcf470f07f7b3ba5c633c202265018177d2991a5d39a23652fb9fa5cc9f9b6394c788c2a529d706f14ef1c7592066aae6b2369c70622bdf345c5692a8f4', 'hex'),
|
||||
sharedKey: Buffer.from('e9888adf3af812be74eb1b52a49d37ec1ca0e06c04d47d8ceb05f64f0c979b5f', 'hex'),
|
||||
},
|
||||
'ML-KEM-1024': {
|
||||
ciphertext: Buffer.from('3ea6d152dd8883f588a33bc88ccbcf7db6e2ee0278b368eb55c81f5e11409509ce0be7932bf60e7b8c463f6f7763fdc11094480dbb94d5cdbb8eb08445f5404f4b37eb1b7a57533269e32c5dc1c673d4b45ee61e7cac186e4b699615ab92591225a2e4ff419aa3e57488a92f75af6945134f3d77e84dcc38bbc996bbb524e1c2afab4e3bc4812de74309ee4a0011299b5097c7bf468024e34552b3be56a63dec078e0083474a31347f7ebabcbcb261c5c2a8e2b14c61c622016e117b6dd8fb7008d64cee91085c4f3a7a90fc6a76af1214e265ca75bb218d13f9e7142fcb5cbc35f3afdb855eb14ab53738ed6e670473e5480f949b59db466affe2b95c002bdc31e901c9ada3bb969e71e1ca95d816d9adaa7fb9e696b7549ede59defce525356b3bc38afe854e5df16771f7a40bcf0c0eebbb051760ab102a18ecc537d44e4cce2a1827d2c863c3b4341dedc7cdb4881bfa5a228ddf21c615d5c29e9404a08aaa61481fff2665c0b057264a65ab355bfc0c407f36546aa69e0e71563bb9ffea45fdc40a6c91a5b1c58901b8b72ca85f39ae159638bf7cf2cd3c9cf344333ac3caa8ba5b900f3c3fac4cfffcd6767f1f347f0de4d0414d18e70e06beb7293e14e3ae49b01a7a75c6c3bb2458b37c68dc583e0742e80fdb2136c01b47173c33759d13753bd9a75d853422006ee05429a644c62e932343f7bab875b635db94af63f9b64a1b44eacf5b8fcc663f4f54078cfe80438156696a5985548e28ea512ac12c267c9b38c73139a91f5ad3034a7edd76ce5e794cdd2775aaeb90c6b991764874a0729aeb66033db5c22c7de93da7e3f068a16b98a0f0a33968f9778ebde1c0d60fb756c150d17220829b41bf393fd96c58336d6ec73844debee343acad5fdd43fd69bacb11200413dd98d3fc72416a20270081dad8f72ab909c2bb2b5aa4f0fb40d0fd738fde65dcd1acb580813f721048114d32ba1b90b514b54a5be56bd914eecf36265bfc94939e2fd86b71344ffc086258667b7c07c915020e5ac93f1e308377361b3cde1e9f1d9b879e43f7f74d4a565db2f9e6a920f2137568651f68d112be4cb13890e9ca88370df569199624bb0658d9b36729d3fd7a55a7a9c52f8c6c261e7cbb54436c8ab9f3a816ac9456e9ce426012defcb7f23a2ac1178e9cf006ba36bb71d091582b8aac3922dd5bdc5badb6682b1e907bf405d5d8ae3e0418e8b2764266437261b80489186cd88d12f8587782b6edd88c5f063be492d5c3c72cd699d36558415c3fc53ef43ded7916ba91018381e4f4ac504098b5ea3935d6eaf458e3032ee1c263d603ab1b6198738874b8c8c8a463053e8b11b381d48117526031f8f5d97501edbae61b8a461f53004323fe55329513720255f5f65c2f450bcb389ff6af8719388e6c2e6d835b59187de474c27ddd8720e51d8a04c565e1537dc2b95f591e0984303e4c8ffc57233cf4943478835b7b1e84407561e8608719a2c26a81c5d8be381ef4e127d3cbae07c4a4b254dee58f17a18f5d09cdf8d3f9bec22fa0b862993d0254e288000699b2cf86e55c4def365c1ac4481e1db3e3e77c9facc0322689f7064d35fbad128cedd83431b97a9463c0168f8eba1c1262f2aaf7ba01f2a2d25f236b0c10646e66b619bcc1d5b2e2f25ffe59ec87ba15878049cb3c2fa707e608229cf2a558e2e770bef5ce3e716c133cc31b60efac524ed8191f4e8eafe9d7f4760e1fa136dc67d9a34a383387b69d60eb81da221cbb13466e9cd89bbfcb4baf1eed89cd9675a3deefa1fa63bcd70ebe138aa07fcebb3a271683508994fa2fd593e6a98b124372b324610ef1f0dc0ec03dca70f15c9cabae4909be7b0e3f844d7023a58194a1745b6ec3e43d05dce405fab650447fc786e53ae57d65e516985946e6d69a090a0f2351f926aa8e5ecb034843135cf15ae2e3f7336158e0ae5afcd7a8f3f787f3cf2fada0253137b69b93df6e43125e388e76327bde676b0e1827e02c31a5c01f63d8110986d8f5e3d1d6038699cb83d9ab3e05460a59095a011f454bf15a8e5718fe57afe2753443712af3b8d6b967a6fb90e5a5f83b63a67b96df1472fac3930dba1a317882728c14e51ff9437c1a878755ed6a6ccfc5e0631c71062a37390b5de369d8363ec768499f64854d4ddcaecdae560cecb0a4798440c71c9303549a908d10cab296d53aa1978b205277cae0', 'hex'),
|
||||
sharedKey: Buffer.from('345cee699a756befaf05c60a35591c6df4f91a97a004356dd823fa4053276405', 'hex'),
|
||||
},
|
||||
};
|
||||
/* eslint-enable @stylistic/js/max-len */
|
||||
|
||||
const algorithms = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024'];
|
||||
|
||||
const vectors = algorithms.map((algorithm) => ({
|
||||
publicKeyPem: spki[algorithm],
|
||||
privateKeyPem: pkcs8[algorithm],
|
||||
name: algorithm,
|
||||
results: results[algorithm],
|
||||
}));
|
||||
|
||||
return vectors;
|
||||
}
|
||||
|
|
@ -31,18 +31,27 @@ export const vectors = {
|
|||
[pqc, 'ML-DSA-44'],
|
||||
[pqc, 'ML-DSA-65'],
|
||||
[pqc, 'ML-DSA-87'],
|
||||
[pqc, 'ML-KEM-512'],
|
||||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
[chacha, 'ChaCha20-Poly1305'],
|
||||
],
|
||||
'importKey': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
[pqc, 'ML-DSA-65'],
|
||||
[pqc, 'ML-DSA-87'],
|
||||
[pqc, 'ML-KEM-512'],
|
||||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
[chacha, 'ChaCha20-Poly1305'],
|
||||
],
|
||||
'exportKey': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
[pqc, 'ML-DSA-65'],
|
||||
[pqc, 'ML-DSA-87'],
|
||||
[pqc, 'ML-KEM-512'],
|
||||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
[chacha, 'ChaCha20-Poly1305'],
|
||||
],
|
||||
'getPublicKey': [
|
||||
|
|
@ -56,6 +65,11 @@ export const vectors = {
|
|||
[true, 'ECDH'],
|
||||
[true, 'ECDSA'],
|
||||
[pqc, 'ML-DSA-44'],
|
||||
[pqc, 'ML-DSA-65'],
|
||||
[pqc, 'ML-DSA-87'],
|
||||
[pqc, 'ML-KEM-512'],
|
||||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
[false, 'AES-CTR'],
|
||||
[false, 'AES-CBC'],
|
||||
[false, 'AES-GCM'],
|
||||
|
|
@ -68,5 +82,39 @@ export const vectors = {
|
|||
[chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }],
|
||||
[false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 64 }],
|
||||
[false, 'ChaCha20-Poly1305'],
|
||||
]
|
||||
],
|
||||
'encapsulateBits': [
|
||||
[pqc, 'ML-KEM-512'],
|
||||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
],
|
||||
'encapsulateKey': [
|
||||
[pqc, 'ML-KEM-512', 'AES-KW'],
|
||||
[pqc, 'ML-KEM-512', 'AES-GCM'],
|
||||
[pqc, 'ML-KEM-512', 'AES-CTR'],
|
||||
[pqc, 'ML-KEM-512', 'AES-CBC'],
|
||||
[pqc, 'ML-KEM-512', 'ChaCha20-Poly1305'],
|
||||
[pqc, 'ML-KEM-512', 'HKDF'],
|
||||
[pqc, 'ML-KEM-512', 'PBKDF2'],
|
||||
[pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256' }],
|
||||
[pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 256 }],
|
||||
[false, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 128 }],
|
||||
],
|
||||
'decapsulateBits': [
|
||||
[pqc, 'ML-KEM-512'],
|
||||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
],
|
||||
'decapsulateKey': [
|
||||
[pqc, 'ML-KEM-512', 'AES-KW'],
|
||||
[pqc, 'ML-KEM-512', 'AES-GCM'],
|
||||
[pqc, 'ML-KEM-512', 'AES-CTR'],
|
||||
[pqc, 'ML-KEM-512', 'AES-CBC'],
|
||||
[pqc, 'ML-KEM-512', 'ChaCha20-Poly1305'],
|
||||
[pqc, 'ML-KEM-512', 'HKDF'],
|
||||
[pqc, 'ML-KEM-512', 'PBKDF2'],
|
||||
[pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256' }],
|
||||
[pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 256 }],
|
||||
[false, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 128 }],
|
||||
],
|
||||
};
|
||||
|
|
|
|||
264
test/parallel/test-webcrypto-encap-decap-ml-kem.js
Normal file
264
test/parallel/test-webcrypto-encap-decap-ml-kem.js
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
|
||||
if (!hasOpenSSL(3, 5))
|
||||
common.skip('requires OpenSSL >= 3.5');
|
||||
|
||||
const assert = require('assert');
|
||||
const crypto = require('crypto');
|
||||
const { KeyObject } = crypto;
|
||||
const { subtle } = globalThis.crypto;
|
||||
|
||||
const vectors = require('../fixtures/crypto/ml-kem')();
|
||||
|
||||
async function testEncapsulateKey({ name, publicKeyPem, privateKeyPem, results }) {
|
||||
const [
|
||||
publicKey,
|
||||
noEncapsulatePublicKey,
|
||||
privateKey,
|
||||
] = await Promise.all([
|
||||
crypto.createPublicKey(publicKeyPem)
|
||||
.toCryptoKey(name, false, ['encapsulateKey']),
|
||||
crypto.createPublicKey(publicKeyPem)
|
||||
.toCryptoKey(name, false, ['encapsulateBits']),
|
||||
crypto.createPrivateKey(privateKeyPem)
|
||||
.toCryptoKey(name, false, ['decapsulateKey']),
|
||||
]);
|
||||
|
||||
// Test successful encapsulation
|
||||
const encapsulated = await subtle.encapsulateKey(
|
||||
{ name },
|
||||
publicKey,
|
||||
'HKDF',
|
||||
false,
|
||||
['deriveBits']
|
||||
);
|
||||
|
||||
assert(encapsulated.sharedKey instanceof CryptoKey);
|
||||
assert(encapsulated.ciphertext instanceof ArrayBuffer);
|
||||
assert.strictEqual(encapsulated.sharedKey.type, 'secret');
|
||||
assert.strictEqual(encapsulated.sharedKey.algorithm.name, 'HKDF');
|
||||
assert.strictEqual(encapsulated.sharedKey.extractable, false);
|
||||
assert.deepStrictEqual(encapsulated.sharedKey.usages, ['deriveBits']);
|
||||
|
||||
// Verify ciphertext length matches expected for algorithm
|
||||
assert.strictEqual(encapsulated.ciphertext.byteLength, results.ciphertext.byteLength);
|
||||
|
||||
// Test with different shared key algorithm
|
||||
const encapsulated2 = await subtle.encapsulateKey(
|
||||
{ name },
|
||||
publicKey,
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign', 'verify']
|
||||
);
|
||||
|
||||
assert(encapsulated2.sharedKey instanceof CryptoKey);
|
||||
assert.strictEqual(encapsulated2.sharedKey.algorithm.name, 'HMAC');
|
||||
assert.strictEqual(encapsulated2.sharedKey.extractable, false);
|
||||
|
||||
// Test failure when using wrong key type
|
||||
await assert.rejects(
|
||||
subtle.encapsulateKey({ name }, privateKey, 'HKDF', false, ['deriveBits']), {
|
||||
name: 'InvalidAccessError',
|
||||
});
|
||||
|
||||
// Test failure when using key without proper usage
|
||||
await assert.rejects(
|
||||
subtle.encapsulateKey({ name }, noEncapsulatePublicKey, 'HKDF', false, ['deriveBits']), {
|
||||
name: 'InvalidAccessError',
|
||||
});
|
||||
}
|
||||
|
||||
async function testEncapsulateBits({ name, publicKeyPem, privateKeyPem, results }) {
|
||||
const [
|
||||
publicKey,
|
||||
noEncapsulatePublicKey,
|
||||
privateKey,
|
||||
] = await Promise.all([
|
||||
crypto.createPublicKey(publicKeyPem)
|
||||
.toCryptoKey(name, false, ['encapsulateBits']),
|
||||
crypto.createPublicKey(publicKeyPem)
|
||||
.toCryptoKey(name, false, ['encapsulateKey']),
|
||||
crypto.createPrivateKey(privateKeyPem)
|
||||
.toCryptoKey(name, false, ['decapsulateBits']),
|
||||
]);
|
||||
|
||||
// Test successful encapsulation
|
||||
const encapsulated = await subtle.encapsulateBits({ name }, publicKey);
|
||||
|
||||
assert(encapsulated.sharedKey instanceof ArrayBuffer);
|
||||
assert(encapsulated.ciphertext instanceof ArrayBuffer);
|
||||
assert.strictEqual(encapsulated.sharedKey.byteLength, 32); // ML-KEM shared secret is 32 bytes
|
||||
|
||||
// Verify ciphertext length matches expected for algorithm
|
||||
assert.strictEqual(encapsulated.ciphertext.byteLength, results.ciphertext.byteLength);
|
||||
|
||||
// Test failure when using wrong key type
|
||||
await assert.rejects(
|
||||
subtle.encapsulateBits({ name }, privateKey), {
|
||||
name: 'InvalidAccessError',
|
||||
});
|
||||
|
||||
// Test failure when using key without proper usage
|
||||
await assert.rejects(
|
||||
subtle.encapsulateBits({ name }, noEncapsulatePublicKey), {
|
||||
name: 'InvalidAccessError',
|
||||
});
|
||||
}
|
||||
|
||||
async function testDecapsulateKey({ name, publicKeyPem, privateKeyPem, results }) {
|
||||
const [
|
||||
publicKey,
|
||||
privateKey,
|
||||
noDecapsulatePrivateKey,
|
||||
] = await Promise.all([
|
||||
crypto.createPublicKey(publicKeyPem)
|
||||
.toCryptoKey(name, false, ['encapsulateKey']),
|
||||
crypto.createPrivateKey(privateKeyPem)
|
||||
.toCryptoKey(name, false, ['decapsulateKey']),
|
||||
crypto.createPrivateKey(privateKeyPem)
|
||||
.toCryptoKey(name, false, ['decapsulateBits']),
|
||||
]);
|
||||
|
||||
// Test successful round-trip: encapsulate then decapsulate
|
||||
const encapsulated = await subtle.encapsulateKey(
|
||||
{ name },
|
||||
publicKey,
|
||||
'HKDF',
|
||||
false,
|
||||
['deriveBits']
|
||||
);
|
||||
|
||||
const decapsulatedKey = await subtle.decapsulateKey(
|
||||
{ name },
|
||||
privateKey,
|
||||
encapsulated.ciphertext,
|
||||
'HKDF',
|
||||
false,
|
||||
['deriveBits']
|
||||
);
|
||||
|
||||
assert(decapsulatedKey instanceof CryptoKey);
|
||||
assert.strictEqual(decapsulatedKey.type, 'secret');
|
||||
assert.strictEqual(decapsulatedKey.algorithm.name, 'HKDF');
|
||||
assert.strictEqual(decapsulatedKey.extractable, false);
|
||||
assert.deepStrictEqual(decapsulatedKey.usages, ['deriveBits']);
|
||||
|
||||
// Verify the keys are the same by using KeyObject.from() and comparing
|
||||
const originalKeyData = KeyObject.from(encapsulated.sharedKey).export();
|
||||
const decapsulatedKeyData = KeyObject.from(decapsulatedKey).export();
|
||||
assert(originalKeyData.equals(decapsulatedKeyData));
|
||||
|
||||
// Test with test vector ciphertext and expected shared key
|
||||
const vectorDecapsulatedKey = await subtle.decapsulateKey(
|
||||
{ name },
|
||||
privateKey,
|
||||
results.ciphertext,
|
||||
'HKDF',
|
||||
false,
|
||||
['deriveBits']
|
||||
);
|
||||
|
||||
const vectorKeyData = KeyObject.from(vectorDecapsulatedKey).export();
|
||||
assert(vectorKeyData.equals(results.sharedKey));
|
||||
|
||||
// Test failure when using wrong key type
|
||||
await assert.rejects(
|
||||
subtle.decapsulateKey({ name }, publicKey, encapsulated.ciphertext,
|
||||
'HKDF', false, ['deriveKey']), {
|
||||
name: 'InvalidAccessError'
|
||||
});
|
||||
|
||||
// Test failure when using key without proper usage
|
||||
await assert.rejects(
|
||||
subtle.decapsulateKey({ name }, noDecapsulatePrivateKey, encapsulated.ciphertext,
|
||||
'HKDF', false, ['deriveKey']), {
|
||||
name: 'InvalidAccessError'
|
||||
});
|
||||
|
||||
// Test failure with wrong ciphertext length
|
||||
const wrongLengthCiphertext = new Uint8Array(encapsulated.ciphertext.byteLength - 1);
|
||||
await assert.rejects(
|
||||
subtle.decapsulateKey({ name }, privateKey, wrongLengthCiphertext,
|
||||
'HKDF', false, ['deriveKey']), {
|
||||
name: 'OperationError',
|
||||
});
|
||||
}
|
||||
|
||||
async function testDecapsulateBits({ name, publicKeyPem, privateKeyPem, results }) {
|
||||
const [
|
||||
publicKey,
|
||||
privateKey,
|
||||
noDecapsulatePrivateKey,
|
||||
] = await Promise.all([
|
||||
crypto.createPublicKey(publicKeyPem)
|
||||
.toCryptoKey(name, false, ['encapsulateBits']),
|
||||
crypto.createPrivateKey(privateKeyPem)
|
||||
.toCryptoKey(name, false, ['decapsulateBits']),
|
||||
crypto.createPrivateKey(privateKeyPem)
|
||||
.toCryptoKey(name, false, ['decapsulateKey']),
|
||||
]);
|
||||
|
||||
// Test successful round-trip: encapsulate then decapsulate
|
||||
const encapsulated = await subtle.encapsulateBits({ name }, publicKey);
|
||||
|
||||
const decapsulatedBits = await subtle.decapsulateBits(
|
||||
{ name },
|
||||
privateKey,
|
||||
encapsulated.ciphertext
|
||||
);
|
||||
|
||||
assert(decapsulatedBits instanceof ArrayBuffer);
|
||||
assert.strictEqual(decapsulatedBits.byteLength, 32); // ML-KEM shared secret is 32 bytes
|
||||
|
||||
// Verify the shared secrets are the same
|
||||
assert(Buffer.from(encapsulated.sharedKey).equals(Buffer.from(decapsulatedBits)));
|
||||
|
||||
// Test with test vector ciphertext and expected shared key
|
||||
const vectorDecapsulatedBits = await subtle.decapsulateBits(
|
||||
{ name },
|
||||
privateKey,
|
||||
results.ciphertext
|
||||
);
|
||||
|
||||
assert(Buffer.from(vectorDecapsulatedBits).equals(results.sharedKey));
|
||||
|
||||
// Test failure when using wrong key type
|
||||
await assert.rejects(
|
||||
subtle.decapsulateBits({ name }, publicKey, encapsulated.ciphertext), {
|
||||
name: 'InvalidAccessError'
|
||||
});
|
||||
|
||||
// Test failure when using key without proper usage
|
||||
await assert.rejects(
|
||||
subtle.decapsulateBits({ name }, noDecapsulatePrivateKey, encapsulated.ciphertext), {
|
||||
name: 'InvalidAccessError'
|
||||
});
|
||||
|
||||
// Test failure with wrong ciphertext length
|
||||
const wrongLengthCiphertext = new Uint8Array(encapsulated.ciphertext.byteLength - 1);
|
||||
await assert.rejects(
|
||||
subtle.decapsulateBits({ name }, privateKey, wrongLengthCiphertext), {
|
||||
name: 'OperationError',
|
||||
});
|
||||
}
|
||||
|
||||
(async function() {
|
||||
const variations = [];
|
||||
|
||||
vectors.forEach((vector) => {
|
||||
variations.push(testEncapsulateKey(vector));
|
||||
variations.push(testEncapsulateBits(vector));
|
||||
variations.push(testDecapsulateKey(vector));
|
||||
variations.push(testDecapsulateBits(vector));
|
||||
});
|
||||
|
||||
await Promise.all(variations);
|
||||
})().then(common.mustCall());
|
||||
|
|
@ -213,7 +213,7 @@ async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) {
|
|||
await assert.rejects(subtle.exportKey('pkcs8', key), (err) => {
|
||||
assert.strictEqual(err.name, 'OperationError');
|
||||
assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED');
|
||||
assert.strictEqual(err.cause.message, 'key does not have an available seed');
|
||||
assert.strictEqual(err.cause.message, 'Failed to get raw seed');
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
315
test/parallel/test-webcrypto-export-import-ml-kem.js
Normal file
315
test/parallel/test-webcrypto-export-import-ml-kem.js
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
|
||||
if (!hasOpenSSL(3, 5))
|
||||
common.skip('requires OpenSSL >= 3.5');
|
||||
|
||||
const assert = require('assert');
|
||||
const { subtle } = globalThis.crypto;
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
function getKeyFileName(type, suffix) {
|
||||
return `${type.replaceAll('-', '_')}_${suffix}.pem`;
|
||||
}
|
||||
|
||||
function toDer(pem) {
|
||||
const der = pem.replace(/(?:-----(?:BEGIN|END) (?:PRIVATE|PUBLIC) KEY-----|\s)/g, '');
|
||||
return Buffer.alloc(Buffer.byteLength(der, 'base64'), der, 'base64');
|
||||
}
|
||||
|
||||
const keyData = {
|
||||
'ML-KEM-512': {
|
||||
pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'private_seed_only'), 'ascii')),
|
||||
pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'private'), 'ascii')),
|
||||
pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'private_priv_only'), 'ascii')),
|
||||
spki: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'public'), 'ascii')),
|
||||
pub_len: 800,
|
||||
},
|
||||
'ML-KEM-768': {
|
||||
pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'private_seed_only'), 'ascii')),
|
||||
pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'private'), 'ascii')),
|
||||
pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'private_priv_only'), 'ascii')),
|
||||
spki: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'public'), 'ascii')),
|
||||
pub_len: 1184,
|
||||
},
|
||||
'ML-KEM-1024': {
|
||||
pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'private_seed_only'), 'ascii')),
|
||||
pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'private'), 'ascii')),
|
||||
pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'private_priv_only'), 'ascii')),
|
||||
spki: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'public'), 'ascii')),
|
||||
pub_len: 1568,
|
||||
},
|
||||
};
|
||||
|
||||
const testVectors = [
|
||||
{
|
||||
name: 'ML-KEM-512',
|
||||
privateUsages: ['decapsulateKey', 'decapsulateBits'],
|
||||
publicUsages: ['encapsulateKey', 'encapsulateBits']
|
||||
},
|
||||
{
|
||||
name: 'ML-KEM-768',
|
||||
privateUsages: ['decapsulateKey', 'decapsulateBits'],
|
||||
publicUsages: ['encapsulateKey', 'encapsulateBits']
|
||||
},
|
||||
{
|
||||
name: 'ML-KEM-1024',
|
||||
privateUsages: ['decapsulateKey', 'decapsulateBits'],
|
||||
publicUsages: ['encapsulateKey', 'encapsulateBits']
|
||||
},
|
||||
];
|
||||
|
||||
async function testImportSpki({ name, publicUsages }, extractable) {
|
||||
const key = await subtle.importKey(
|
||||
'spki',
|
||||
keyData[name].spki,
|
||||
{ name },
|
||||
extractable,
|
||||
publicUsages);
|
||||
assert.strictEqual(key.type, 'public');
|
||||
assert.strictEqual(key.extractable, extractable);
|
||||
assert.deepStrictEqual(key.usages, publicUsages);
|
||||
assert.deepStrictEqual(key.algorithm.name, name);
|
||||
assert.strictEqual(key.algorithm, key.algorithm);
|
||||
assert.strictEqual(key.usages, key.usages);
|
||||
|
||||
if (extractable) {
|
||||
// Test the roundtrip
|
||||
const spki = await subtle.exportKey('spki', key);
|
||||
assert.strictEqual(
|
||||
Buffer.from(spki).toString('hex'),
|
||||
keyData[name].spki.toString('hex'));
|
||||
} else {
|
||||
await assert.rejects(
|
||||
subtle.exportKey('spki', key), {
|
||||
message: /key is not extractable/
|
||||
});
|
||||
}
|
||||
|
||||
// Bad usage
|
||||
await assert.rejects(
|
||||
subtle.importKey(
|
||||
'spki',
|
||||
keyData[name].spki,
|
||||
{ name },
|
||||
extractable,
|
||||
['wrapKey']),
|
||||
{ message: /Unsupported key usage/ });
|
||||
}
|
||||
|
||||
async function testImportPkcs8({ name, privateUsages }, extractable) {
|
||||
const key = await subtle.importKey(
|
||||
'pkcs8',
|
||||
keyData[name].pkcs8,
|
||||
{ name },
|
||||
extractable,
|
||||
privateUsages);
|
||||
assert.strictEqual(key.type, 'private');
|
||||
assert.strictEqual(key.extractable, extractable);
|
||||
assert.deepStrictEqual(key.usages, privateUsages);
|
||||
assert.deepStrictEqual(key.algorithm.name, name);
|
||||
assert.strictEqual(key.algorithm, key.algorithm);
|
||||
assert.strictEqual(key.usages, key.usages);
|
||||
|
||||
if (extractable) {
|
||||
// Test the roundtrip
|
||||
const pkcs8 = await subtle.exportKey('pkcs8', key);
|
||||
assert.strictEqual(
|
||||
Buffer.from(pkcs8).toString('hex'),
|
||||
keyData[name].pkcs8_seed_only.toString('hex'));
|
||||
} else {
|
||||
await assert.rejects(
|
||||
subtle.exportKey('pkcs8', key), {
|
||||
message: /key is not extractable/
|
||||
});
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
subtle.importKey(
|
||||
'pkcs8',
|
||||
keyData[name].pkcs8,
|
||||
{ name },
|
||||
extractable,
|
||||
[/* empty usages */]),
|
||||
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
|
||||
}
|
||||
|
||||
async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) {
|
||||
const key = await subtle.importKey(
|
||||
'pkcs8',
|
||||
keyData[name].pkcs8_seed_only,
|
||||
{ name },
|
||||
extractable,
|
||||
privateUsages);
|
||||
assert.strictEqual(key.type, 'private');
|
||||
assert.strictEqual(key.extractable, extractable);
|
||||
assert.deepStrictEqual(key.usages, privateUsages);
|
||||
assert.deepStrictEqual(key.algorithm.name, name);
|
||||
assert.strictEqual(key.algorithm, key.algorithm);
|
||||
assert.strictEqual(key.usages, key.usages);
|
||||
|
||||
if (extractable) {
|
||||
// Test the roundtrip
|
||||
const pkcs8 = await subtle.exportKey('pkcs8', key);
|
||||
assert.strictEqual(
|
||||
Buffer.from(pkcs8).toString('hex'),
|
||||
keyData[name].pkcs8_seed_only.toString('hex'));
|
||||
} else {
|
||||
await assert.rejects(
|
||||
subtle.exportKey('pkcs8', key), {
|
||||
message: /key is not extractable/
|
||||
});
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
subtle.importKey(
|
||||
'pkcs8',
|
||||
keyData[name].pkcs8_seed_only,
|
||||
{ name },
|
||||
extractable,
|
||||
[/* empty usages */]),
|
||||
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
|
||||
}
|
||||
|
||||
async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) {
|
||||
const key = await subtle.importKey(
|
||||
'pkcs8',
|
||||
keyData[name].pkcs8_priv_only,
|
||||
{ name },
|
||||
extractable,
|
||||
privateUsages);
|
||||
assert.strictEqual(key.type, 'private');
|
||||
assert.strictEqual(key.extractable, extractable);
|
||||
assert.deepStrictEqual(key.usages, privateUsages);
|
||||
assert.deepStrictEqual(key.algorithm.name, name);
|
||||
assert.strictEqual(key.algorithm, key.algorithm);
|
||||
assert.strictEqual(key.usages, key.usages);
|
||||
|
||||
if (extractable) {
|
||||
await assert.rejects(subtle.exportKey('pkcs8', key), (err) => {
|
||||
assert.strictEqual(err.name, 'OperationError');
|
||||
assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED');
|
||||
assert.strictEqual(err.cause.message, 'Failed to get raw seed');
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
await assert.rejects(
|
||||
subtle.exportKey('pkcs8', key), {
|
||||
message: /key is not extractable/
|
||||
});
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
subtle.importKey(
|
||||
'pkcs8',
|
||||
keyData[name].pkcs8_seed_only,
|
||||
{ name },
|
||||
extractable,
|
||||
[/* empty usages */]),
|
||||
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
|
||||
}
|
||||
|
||||
async function testImportRawPublic({ name, publicUsages }, extractable) {
|
||||
const pub = keyData[name].spki.subarray(-keyData[name].pub_len);
|
||||
|
||||
const publicKey = await subtle.importKey(
|
||||
'raw-public',
|
||||
pub,
|
||||
{ name },
|
||||
extractable, publicUsages);
|
||||
|
||||
assert.strictEqual(publicKey.type, 'public');
|
||||
assert.deepStrictEqual(publicKey.usages, publicUsages);
|
||||
assert.strictEqual(publicKey.algorithm.name, name);
|
||||
assert.strictEqual(publicKey.algorithm, publicKey.algorithm);
|
||||
assert.strictEqual(publicKey.usages, publicKey.usages);
|
||||
assert.strictEqual(publicKey.extractable, extractable);
|
||||
|
||||
if (extractable) {
|
||||
const value = await subtle.exportKey('raw-public', publicKey);
|
||||
assert.deepStrictEqual(Buffer.from(value), pub);
|
||||
|
||||
await assert.rejects(subtle.exportKey('raw', publicKey), {
|
||||
name: 'NotSupportedError',
|
||||
message: `Unable to export ${publicKey.algorithm.name} public key using raw format`,
|
||||
});
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
subtle.importKey(
|
||||
'raw-public',
|
||||
pub.subarray(0, pub.byteLength - 1),
|
||||
{ name },
|
||||
extractable, publicUsages),
|
||||
{ message: 'Invalid keyData' });
|
||||
|
||||
await assert.rejects(
|
||||
subtle.importKey(
|
||||
'raw-public',
|
||||
pub,
|
||||
{ name: name === 'ML-KEM-512' ? 'ML-KEM-768' : 'ML-KEM-512' },
|
||||
extractable, publicUsages),
|
||||
{ message: 'Invalid keyData' });
|
||||
}
|
||||
|
||||
async function testImportRawSeed({ name, privateUsages }, extractable) {
|
||||
const seed = keyData[name].pkcs8_seed_only.subarray(-64);
|
||||
|
||||
const privateKey = await subtle.importKey(
|
||||
'raw-seed',
|
||||
seed,
|
||||
{ name },
|
||||
extractable, privateUsages);
|
||||
|
||||
assert.strictEqual(privateKey.type, 'private');
|
||||
assert.deepStrictEqual(privateKey.usages, privateUsages);
|
||||
assert.strictEqual(privateKey.algorithm.name, name);
|
||||
assert.strictEqual(privateKey.algorithm, privateKey.algorithm);
|
||||
assert.strictEqual(privateKey.usages, privateKey.usages);
|
||||
assert.strictEqual(privateKey.extractable, extractable);
|
||||
|
||||
if (extractable) {
|
||||
const value = await subtle.exportKey('raw-seed', privateKey);
|
||||
assert.deepStrictEqual(Buffer.from(value), seed);
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
subtle.importKey(
|
||||
'raw-seed',
|
||||
seed.subarray(0, 30),
|
||||
{ name },
|
||||
extractable,
|
||||
privateUsages),
|
||||
{ message: 'Invalid keyData' });
|
||||
}
|
||||
|
||||
(async function() {
|
||||
const tests = [];
|
||||
for (const vector of testVectors) {
|
||||
for (const extractable of [true, false]) {
|
||||
tests.push(testImportSpki(vector, extractable));
|
||||
tests.push(testImportPkcs8(vector, extractable));
|
||||
tests.push(testImportPkcs8SeedOnly(vector, extractable));
|
||||
tests.push(testImportPkcs8PrivOnly(vector, extractable));
|
||||
tests.push(testImportRawSeed(vector, extractable));
|
||||
tests.push(testImportRawPublic(vector, extractable));
|
||||
}
|
||||
}
|
||||
await Promise.all(tests);
|
||||
})().then(common.mustCall());
|
||||
|
||||
(async function() {
|
||||
const alg = 'ML-KEM-512';
|
||||
const pub = keyData[alg].spki.subarray(-keyData[alg].pub_len);
|
||||
await assert.rejects(subtle.importKey('raw', pub, alg, false, []), {
|
||||
name: 'NotSupportedError',
|
||||
message: 'Unable to import ML-KEM-512 using raw format',
|
||||
});
|
||||
})().then(common.mustCall());
|
||||
|
|
@ -790,3 +790,48 @@ if (hasOpenSSL(3, 5)) {
|
|||
|
||||
Promise.all(tests).then(common.mustCall());
|
||||
}
|
||||
|
||||
// Test ML-KEM Key Generation
|
||||
if (hasOpenSSL(3, 5)) {
|
||||
async function test(
|
||||
name,
|
||||
privateUsages,
|
||||
publicUsages = privateUsages) {
|
||||
|
||||
let usages = privateUsages;
|
||||
if (publicUsages !== privateUsages)
|
||||
usages = usages.concat(publicUsages);
|
||||
|
||||
const { publicKey, privateKey } = await subtle.generateKey({
|
||||
name,
|
||||
}, true, usages);
|
||||
|
||||
assert(publicKey);
|
||||
assert(privateKey);
|
||||
assert(isCryptoKey(publicKey));
|
||||
assert(isCryptoKey(privateKey));
|
||||
|
||||
assert.strictEqual(publicKey.type, 'public');
|
||||
assert.strictEqual(privateKey.type, 'private');
|
||||
assert.strictEqual(publicKey.toString(), '[object CryptoKey]');
|
||||
assert.strictEqual(privateKey.toString(), '[object CryptoKey]');
|
||||
assert.strictEqual(publicKey.extractable, true);
|
||||
assert.strictEqual(privateKey.extractable, true);
|
||||
assert.deepStrictEqual(publicKey.usages, publicUsages);
|
||||
assert.deepStrictEqual(privateKey.usages, privateUsages);
|
||||
assert.strictEqual(publicKey.algorithm.name, name);
|
||||
assert.strictEqual(privateKey.algorithm.name, name);
|
||||
assert.strictEqual(privateKey.algorithm, privateKey.algorithm);
|
||||
assert.strictEqual(privateKey.usages, privateKey.usages);
|
||||
assert.strictEqual(publicKey.algorithm, publicKey.algorithm);
|
||||
assert.strictEqual(publicKey.usages, publicKey.usages);
|
||||
}
|
||||
|
||||
const kTests = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024'];
|
||||
|
||||
const tests = kTests.map((name) => test(name,
|
||||
['decapsulateBits', 'decapsulateKey'],
|
||||
['encapsulateBits', 'encapsulateKey']));
|
||||
|
||||
Promise.all(tests).then(common.mustCall());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ for await (const mod of sources) {
|
|||
|
||||
vectors.verify = vectors.sign;
|
||||
vectors.decrypt = vectors.encrypt;
|
||||
vectors.decapsulateBits = vectors.encapsulateBits;
|
||||
|
||||
for (const enc of vectors.encrypt) {
|
||||
for (const exp of vectors.exportKey) {
|
||||
|
|
@ -41,6 +42,57 @@ for (const exportKey of vectors.exportKey) {
|
|||
if (!exportKey[0]) vectors.getPublicKey.push(exportKey);
|
||||
}
|
||||
|
||||
function supportsRawSecret(alg) {
|
||||
if (typeof alg === 'string') {
|
||||
alg = alg.toLowerCase();
|
||||
return alg.startsWith('aes') ||
|
||||
alg.startsWith('argon2') ||
|
||||
alg.startsWith('kmac') ||
|
||||
alg === 'chacha20-poly1305' ||
|
||||
alg === 'pbkdf2' ||
|
||||
alg === 'hkdf' ||
|
||||
alg === 'hmac';
|
||||
}
|
||||
|
||||
if (typeof alg?.name === 'string') {
|
||||
return supportsRawSecret(alg.name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function supports256RawSecret(alg) {
|
||||
if (!supportsRawSecret(alg)) return false;
|
||||
switch (alg?.name?.toLowerCase?.()) {
|
||||
case 'hmac':
|
||||
case 'kmac128':
|
||||
case 'kmac256':
|
||||
return typeof alg.length !== 'number' || alg.length === 256;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const encap of vectors.encapsulateBits) {
|
||||
for (const imp of vectors.importKey) {
|
||||
if (supports256RawSecret(imp[1])) {
|
||||
vectors.encapsulateKey.push([encap[0] && imp[0], encap[1], imp[1]]);
|
||||
} else {
|
||||
vectors.encapsulateKey.push([false, encap[1], imp[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const decap of vectors.decapsulateBits) {
|
||||
for (const imp of vectors.importKey) {
|
||||
if (supports256RawSecret(imp[1])) {
|
||||
vectors.decapsulateKey.push([decap[0] && imp[0], decap[1], imp[1]]);
|
||||
} else {
|
||||
vectors.decapsulateKey.push([false, decap[1], imp[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const operation of Object.keys(vectors)) {
|
||||
for (const [expectation, ...args] of vectors[operation]) {
|
||||
assert.strictEqual(
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ const customTypesMap = {
|
|||
'CryptoKey': 'webcrypto.html#class-cryptokey',
|
||||
'CryptoKeyPair': 'webcrypto.html#class-cryptokeypair',
|
||||
'Crypto': 'webcrypto.html#class-crypto',
|
||||
'EncapsulatedBits': 'webcrypto.html#class-encapsulatedbits',
|
||||
'EncapsulatedKey': 'webcrypto.html#class-encapsulatedkey',
|
||||
'SubtleCrypto': 'webcrypto.html#class-subtlecrypto',
|
||||
'RsaOaepParams': 'webcrypto.html#class-rsaoaepparams',
|
||||
'AesCtrParams': 'webcrypto.html#class-aesctrparams',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user