diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index 290c3a816fe310..c6ef072e051a9a 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -2927,6 +2927,7 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm);
const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap);
const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap);
const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap);
+const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305);
bool Cipher::isGcmMode() const {
if (!cipher_) return false;
diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h
index 3278d5612eb11b..42f9cf6aa2a246 100644
--- a/deps/ncrypto/ncrypto.h
+++ b/deps/ncrypto/ncrypto.h
@@ -373,6 +373,7 @@ class Cipher final {
static const Cipher AES_128_KW;
static const Cipher AES_192_KW;
static const Cipher AES_256_KW;
+ static const Cipher CHACHA20_POLY1305;
struct CipherParams {
int padding;
diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md
index b2506dee18c3c3..4eade7ec621962 100644
--- a/doc/api/webcrypto.md
+++ b/doc/api/webcrypto.md
@@ -2,6 +2,18 @@
+### Static method: `SubtleCrypto.supports(operation, algorithm[, lengthOrAdditionalAlgorithm])`
+
+
+
+> Stability: 1.1 - Active development
+
+
+
+* `operation` {string} "encrypt", "decrypt", "sign", "verify", "digest", "generateKey", "deriveKey", "deriveBits", "importKey", "exportKey", "getPublicKey", "wrapKey", or "unwrapKey"
+* `algorithm` {string|Algorithm}
+* `lengthOrAdditionalAlgorithm` {null|number|string|Algorithm|undefined} Depending on the operation this is either ignored, the value of the length argument when operation is "deriveBits", the algorithm of key to be derived when operation is "deriveKey", the algorithm of key to be exported before wrapping when operation is "wrapKey", or the algorithm of key to be imported after unwrapping when operation is "unwrapKey". **Default:** `null` when operation is "deriveBits", `undefined` otherwise.
+* Returns: {boolean} Indicating whether the implementation supports the given operation
+
+
+
+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.decrypt(algorithm, key, data)`
-* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams}
+* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams}
* `key` {CryptoKey}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
@@ -568,10 +732,11 @@ an {ArrayBuffer} containing the plaintext result.
The algorithms currently supported include:
-* `'RSA-OAEP'`
-* `'AES-CTR'`
* `'AES-CBC'`
+* `'AES-CTR'`
* `'AES-GCM'`
+* `'ChaCha20-Poly1305'`[^modern-algos]
+* `'RSA-OAEP'`
### `subtle.deriveBits(algorithm, baseKey[, length])`
@@ -606,7 +771,7 @@ material provided by `baseKey`, `subtle.deriveBits()` attempts to generate
`length` bits.
When `length` is not provided or `null` the maximum number of bits for a given
-algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'`
+algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'`[^secure-curves]
algorithms, for other algorithms `length` is required to be a number.
If successful, the returned promise will be resolved with an {ArrayBuffer}
@@ -615,10 +780,10 @@ containing the generated data.
The algorithms currently supported include:
* `'ECDH'`
-* `'X25519'`
-* `'X448'` [^1]
* `'HKDF'`
* `'PBKDF2'`
+* `'X25519'`
+* `'X448'`[^secure-curves]
### `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)`
@@ -655,18 +820,25 @@ generate raw keying material, then passing the result into the
The algorithms currently supported include:
* `'ECDH'`
-* `'X25519'`
-* `'X448'` [^1]
* `'HKDF'`
* `'PBKDF2'`
+* `'X25519'`
+* `'X448'`[^secure-curves]
### `subtle.digest(algorithm, data)`
-* `algorithm` {string|Algorithm}
+* `algorithm` {string|Algorithm|CShakeParams}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
@@ -676,10 +848,15 @@ with an {ArrayBuffer} containing the computed digest.
If `algorithm` is provided as a {string}, it must be one of:
+* `'cSHAKE128'`[^modern-algos]
+* `'cSHAKE256'`[^modern-algos]
* `'SHA-1'`
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If `algorithm` is provided as an {Object}, it must have a `name` property
whose value is one of the above.
@@ -688,9 +865,13 @@ whose value is one of the above.
-* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams}
+* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams}
* `key` {CryptoKey}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
@@ -702,16 +883,23 @@ containing the encrypted result.
The algorithms currently supported include:
-* `'RSA-OAEP'`
-* `'AES-CTR'`
* `'AES-CBC'`
+* `'AES-CTR'`
* `'AES-GCM'`
+* `'ChaCha20-Poly1305'`[^modern-algos]
+* `'RSA-OAEP'`
### `subtle.exportKey(format, key)`
-* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`.
+* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos],
+ `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos].
* `key` {CryptoKey}
* Returns: {Promise} Fulfills with an {ArrayBuffer|Object} upon success.
@@ -739,25 +928,50 @@ When `format` is `'jwk'` and the export is successful, the returned promise
will be resolved with a JavaScript object conforming to the [JSON Web Key][]
specification.
-| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` |
-| ------------------------------------------------------- | -------- | --------- | ------- | ------- |
-| `'AES-CBC'` | | | ✔ | ✔ |
-| `'AES-CTR'` | | | ✔ | ✔ |
-| `'AES-GCM'` | | | ✔ | ✔ |
-| `'AES-KW'` | | | ✔ | ✔ |
-| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
-| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
-| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ |
-| `'Ed448'` [^1] | ✔ | ✔ | ✔ | ✔ |
-| `'HMAC'` | | | ✔ | ✔ |
-| `'RSA-OAEP'` | ✔ | ✔ | ✔ | |
-| `'RSA-PSS'` | ✔ | ✔ | ✔ | |
-| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | |
+| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` |
+| ------------------------------------ | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ |
+| `'AES-CBC'` | | | ✔ | ✔ | ✔ | | |
+| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | |
+| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
+| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
+| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
+| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
+| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
+| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
+| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
+| `'HMAC'` | | | ✔ | ✔ | ✔ | | |
+| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
+| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
+| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
+| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | |
+| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | |
+| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | |
+
+### `subtle.getPublicKey(key, keyUsages)`
+
+
+
+> Stability: 1.1 - Active development
+
+* `key` {CryptoKey} A private key from which to derive the corresponding public key.
+* `keyUsages` {string\[]} See [Key usages][].
+* Returns: {Promise} Fulfills with a {CryptoKey} upon success.
+
+Derives the public key from a given private key.
### `subtle.generateKey(algorithm, extractable, keyUsages)`
@@ -777,29 +991,39 @@ may generate either a single {CryptoKey} or a {CryptoKeyPair}.
The {CryptoKeyPair} (public and private key) generating algorithms supported
include:
-* `'RSASSA-PKCS1-v1_5'`
-* `'RSA-PSS'`
-* `'RSA-OAEP'`
+* `'ECDH'`
* `'ECDSA'`
* `'Ed25519'`
-* `'Ed448'` [^1]
-* `'ECDH'`
+* `'Ed448'`[^secure-curves]
+* `'ML-DSA-44'`[^modern-algos]
+* `'ML-DSA-65'`[^modern-algos]
+* `'ML-DSA-87'`[^modern-algos]
+* `'RSA-OAEP'`
+* `'RSA-PSS'`
+* `'RSASSA-PKCS1-v1_5'`
* `'X25519'`
-* `'X448'` [^1]
+* `'X448'`[^secure-curves]
The {CryptoKey} (secret key) generating algorithms supported include:
-* `'HMAC'`
-* `'AES-CTR'`
* `'AES-CBC'`
+* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
+* `'ChaCha20-Poly1305'`[^modern-algos]
+* `'HMAC'`
### `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)`
-* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`.
+* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos],
+ `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos].
* `keyData` {ArrayBuffer|TypedArray|DataView|Buffer|Object}
@@ -833,30 +1058,37 @@ If importing a `'PBKDF2'` key, `extractable` must be `false`.
The algorithms currently supported include:
-| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` |
-| ------------------------------------------------------- | -------- | --------- | ------- | ------- |
-| `'AES-CBC'` | | | ✔ | ✔ |
-| `'AES-CTR'` | | | ✔ | ✔ |
-| `'AES-GCM'` | | | ✔ | ✔ |
-| `'AES-KW'` | | | ✔ | ✔ |
-| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
-| `'X25519'` | ✔ | ✔ | ✔ | ✔ |
-| `'X448'` [^1] | ✔ | ✔ | ✔ | ✔ |
-| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
-| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ |
-| `'Ed448'` [^1] | ✔ | ✔ | ✔ | ✔ |
-| `'HDKF'` | | | | ✔ |
-| `'HMAC'` | | | ✔ | ✔ |
-| `'PBKDF2'` | | | | ✔ |
-| `'RSA-OAEP'` | ✔ | ✔ | ✔ | |
-| `'RSA-PSS'` | ✔ | ✔ | ✔ | |
-| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | |
+| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` |
+| ------------------------------------ | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ |
+| `'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] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
### `subtle.sign(algorithm, key, data)`
-* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params}
+* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams}
* `key` {CryptoKey}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
@@ -880,26 +1112,34 @@ an {ArrayBuffer} containing the generated signature.
The algorithms currently supported include:
-* `'RSASSA-PKCS1-v1_5'`
-* `'RSA-PSS'`
* `'ECDSA'`
* `'Ed25519'`
-* `'Ed448'` [^1]
+* `'Ed448'`[^secure-curves]
* `'HMAC'`
+* `'ML-DSA-44'`[^modern-algos]
+* `'ML-DSA-65'`[^modern-algos]
+* `'ML-DSA-87'`[^modern-algos]
+* `'RSA-PSS'`
+* `'RSASSA-PKCS1-v1_5'`
### `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)`
-* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`.
+* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos],
+ `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos].
* `wrappedKey` {ArrayBuffer|TypedArray|DataView|Buffer}
* `unwrappingKey` {CryptoKey}
-* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams}
+* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams}
* `unwrappedKeyAlgo` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams}
@@ -919,34 +1159,42 @@ promise is resolved with a {CryptoKey} object.
The wrapping algorithms currently supported include:
-* `'RSA-OAEP'`
-* `'AES-CTR'`
* `'AES-CBC'`
+* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
+* `'ChaCha20-Poly1305'`[^modern-algos]
+* `'RSA-OAEP'`
The unwrapped key algorithms supported include:
-* `'RSASSA-PKCS1-v1_5'`
-* `'RSA-PSS'`
-* `'RSA-OAEP'`
-* `'ECDSA'`
-* `'Ed25519'`
-* `'Ed448'` [^1]
-* `'ECDH'`
-* `'X25519'`
-* `'X448'` [^1]
-* `'HMAC'`
-* `'AES-CTR'`
* `'AES-CBC'`
+* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
+* `'ChaCha20-Poly1305'`[^modern-algos]
+* `'ECDH'`
+* `'ECDSA'`
+* `'Ed25519'`
+* `'Ed448'`[^secure-curves]
+* `'HMAC'`
+* `'ML-DSA-44'`[^modern-algos]
+* `'ML-DSA-65'`[^modern-algos]
+* `'ML-DSA-87'`[^modern-algos]
+* `'RSA-OAEP'`
+* `'RSA-PSS'`
+* `'RSASSA-PKCS1-v1_5'`
+* `'X25519'`
+* `'X448'`[^secure-curves]
### `subtle.verify(algorithm, key, signature, data)`
-* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params}
+* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams}
* `key` {CryptoKey}
* `signature` {ArrayBuffer|TypedArray|DataView|Buffer}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
@@ -971,25 +1219,33 @@ with either `true` or `false`.
The algorithms currently supported include:
-* `'RSASSA-PKCS1-v1_5'`
-* `'RSA-PSS'`
* `'ECDSA'`
* `'Ed25519'`
-* `'Ed448'` [^1]
+* `'Ed448'`[^secure-curves]
* `'HMAC'`
+* `'ML-DSA-44'`[^modern-algos]
+* `'ML-DSA-65'`[^modern-algos]
+* `'ML-DSA-87'`[^modern-algos]
+* `'RSA-PSS'`
+* `'RSASSA-PKCS1-v1_5'`
### `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)`
-* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`.
+* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos],
+ `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos].
* `key` {CryptoKey}
* `wrappingKey` {CryptoKey}
-* `wrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams}
+* `wrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams}
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
@@ -1006,11 +1262,12 @@ containing the encrypted key data.
The wrapping algorithms currently supported include:
-* `'RSA-OAEP'`
-* `'AES-CTR'`
* `'AES-CBC'`
+* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
+* `'ChaCha20-Poly1305'`[^modern-algos]
+* `'RSA-OAEP'`
## Algorithm parameters
@@ -1032,6 +1289,50 @@ added: v15.0.0
* Type: {string}
+### Class: `AeadParams`
+
+
+
+#### `aeadParams.additionalData`
+
+
+
+* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
+
+Extra input that is not encrypted but is included in the authentication
+of the data. The use of `additionalData` is optional.
+
+#### `aeadParams.iv`
+
+
+
+* Type: {ArrayBuffer|TypedArray|DataView|Buffer}
+
+The initialization vector must be unique for every encryption operation using a
+given key.
+
+#### `aeadParams.name`
+
+
+
+* Type: {string} Must be `'AES-GCM'` or `'ChaCha20-Poly1305'`.
+
+#### `aeadParams.tagLength`
+
+
+
+* Type: {number} The size in bits of the generated authentication tag.
+
### Class: `AesDerivedKeyParams`
-#### `aesGcmParams.additionalData`
+#### `aesKeyAlgorithm.length`
-* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
+* Type: {number}
-With the AES-GCM method, the `additionalData` is extra input that is not
-encrypted but is included in the authentication of the data. The use of
-`additionalData` is optional.
+The length of the AES key in bits.
-#### `aesGcmParams.iv`
+#### `aesKeyAlgorithm.name`
-* Type: {ArrayBuffer|TypedArray|DataView|Buffer}
+* Type: {string}
-The initialization vector must be unique for every encryption operation using a
-given key.
+### Class: `AesKeyGenParams`
-Ideally, this is a deterministic 12-byte value that is computed in such a way
-that it is guaranteed to be unique across all invocations that use the same key.
-Alternatively, the initialization vector may consist of at least 12
-cryptographically random bytes. For more information on constructing
-initialization vectors for AES-GCM, refer to Section 8 of [NIST SP 800-38D][].
+
-#### `aesGcmParams.name`
+#### `aesKeyGenParams.length`
-* Type: {string} Must be `'AES-GCM'`.
+* Type: {number}
+
+The length of the AES key to be generated. This must be either `128`, `192`,
+or `256`.
-#### `aesGcmParams.tagLength`
+#### `aesKeyGenParams.name`
-* Type: {number} The size in bits of the generated authentication tag.
- This values must be one of `32`, `64`, `96`, `104`, `112`, `120`, or
- `128`. **Default:** `128`.
+* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or
+ `'AES-KW'`
-### Class: `AesKeyAlgorithm`
+### Class: `ContextParams`
-#### `aesKeyAlgorithm.length`
+#### `contextParams.name`
-* Type: {number}
-
-The length of the AES key in bits.
+* Type: {string} Must be `'ML-DSA-44'`[^modern-algos], `'ML-DSA-65'`[^modern-algos], or `'ML-DSA-87'`[^modern-algos].
-#### `aesKeyAlgorithm.name`
+#### `contextParams.context`
-* Type: {string}
+* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
-### Class: `AesKeyGenParams`
+The `context` member represents the optional context data to associate with
+the message.
+The Node.js Web Crypto API implementation only supports zero-length context
+which is equivalent to not providing context at all.
+
+### Class: `CShakeParams`
-#### `aesKeyGenParams.length`
+#### `cShakeParams.customization`
-* Type: {number}
+* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
-The length of the AES key to be generated. This must be either `128`, `192`,
-or `256`.
+The `customization` member represents the customization string.
+The Node.js Web Crypto API implementation only supports zero-length customization
+which is equivalent to not providing customization at all.
-#### `aesKeyGenParams.name`
+#### `cShakeParams.functionName`
-* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or
- `'AES-KW'`
+* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
+
+The `functionName` member represents represents the function name, used by NIST to define
+functions based on cSHAKE.
+The Node.js Web Crypto API implementation only supports zero-length functionName
+which is equivalent to not providing functionName at all.
+
+#### `cShakeParams.length`
+
+
+
+* Type: {number} represents the requested output length in bits.
+
+#### `cShakeParams.name`
+
+
+
+* Type: {string} Must be `'cSHAKE128'`[^modern-algos] or `'cSHAKE256'`[^modern-algos]
### Class: `EcdhKeyDeriveParams`
@@ -1234,7 +1556,7 @@ added: v15.0.0
added: v15.0.0
-->
-* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`.
+* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`[^secure-curves].
#### `ecdhKeyDeriveParams.public`
@@ -1259,6 +1581,10 @@ added: v15.0.0
* Type: {string|Algorithm}
@@ -1269,6 +1595,9 @@ If represented as a {string}, the value must be one of:
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If represented as an {Algorithm}, the object's `name` property
must be one of the above listed values.
@@ -1361,7 +1690,7 @@ added:
- v16.17.0
-->
-* Type: {string} Must be `'Ed448'`.
+* Type: {string} Must be `'Ed448'`[^secure-curves].
#### `ed448Params.context`
@@ -1388,6 +1717,10 @@ added: v15.0.0
* Type: {string|Algorithm}
@@ -1398,6 +1731,9 @@ If represented as a {string}, the value must be one of:
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If represented as an {Algorithm}, the object's `name` property
must be one of the above listed values.
@@ -1444,6 +1780,10 @@ added: v15.0.0
* Type: {string|Algorithm}
@@ -1454,6 +1794,9 @@ If represented as a {string}, the value must be one of:
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If represented as an {Algorithm}, the object's `name` property
must be one of the above listed values.
@@ -1519,6 +1862,10 @@ added: v15.0.0
* Type: {string|Algorithm}
@@ -1529,6 +1876,9 @@ If represented as a {string}, the value must be one of:
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If represented as an {Algorithm}, the object's `name` property
must be one of the above listed values.
@@ -1577,6 +1927,10 @@ added: v15.0.0
* Type: {string|Algorithm}
@@ -1587,6 +1941,9 @@ If represented as a {string}, the value must be one of:
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If represented as an {Algorithm}, the object's `name` property
must be one of the above listed values.
@@ -1629,6 +1986,10 @@ added: v15.0.0
* Type: {string|Algorithm}
@@ -1639,6 +2000,9 @@ If represented as a {string}, the value must be one of:
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If represented as an {Algorithm}, the object's `name` property
must be one of the above listed values.
@@ -1704,6 +2068,10 @@ added: v15.0.0
* Type: {string|Algorithm}
@@ -1714,6 +2082,9 @@ If represented as a {string}, the value must be one of:
* `'SHA-256'`
* `'SHA-384'`
* `'SHA-512'`
+* `'SHA3-256'`[^modern-algos]
+* `'SHA3-384'`[^modern-algos]
+* `'SHA3-512'`[^modern-algos]
If represented as an {Algorithm}, the object's `name` property
must be one of the above listed values.
@@ -1803,12 +2174,17 @@ added: v15.0.0
The length (in bytes) of the random salt to use.
-[^1]: An experimental implementation of Ed448 and X448 algorithms from
- [Secure Curves in the Web Cryptography API][] as of 21 October 2024
+[^secure-curves]: See [Secure Curves in the Web Cryptography API][]
+
+[^modern-algos]: See [Modern Algorithms in the Web Cryptography API][]
+
+[^openssl35]: Requires OpenSSL >= 3.5
[JSON Web Key]: https://tools.ietf.org/html/rfc7517
[Key usages]: #cryptokeyusages
-[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
+[Modern Algorithms in the Web Cryptography API]: #modern-algorithms-in-the-web-cryptography-api
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
-[Secure Curves in the Web Cryptography API]: https://wicg.github.io/webcrypto-secure-curves/
+[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.getPublicKey()`]: #subtlegetpublickeykey-keyusages
diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js
new file mode 100644
index 00000000000000..bcc778b24d7738
--- /dev/null
+++ b/lib/internal/crypto/chacha20_poly1305.js
@@ -0,0 +1,191 @@
+'use strict';
+
+const {
+ ArrayBufferIsView,
+ ArrayBufferPrototypeSlice,
+ ArrayFrom,
+ PromiseReject,
+ SafeSet,
+ TypedArrayPrototypeSlice,
+} = primordials;
+
+const {
+ ChaCha20Poly1305CipherJob,
+ KeyObjectHandle,
+ kCryptoJobAsync,
+ kWebCryptoCipherDecrypt,
+ kWebCryptoCipherEncrypt,
+} = internalBinding('crypto');
+
+const {
+ hasAnyNotIn,
+ jobPromise,
+ validateKeyOps,
+ kHandle,
+ kKeyObject,
+} = require('internal/crypto/util');
+
+const {
+ lazyDOMException,
+ promisify,
+} = require('internal/util');
+
+const {
+ InternalCryptoKey,
+ SecretKeyObject,
+ createSecretKey,
+} = require('internal/crypto/keys');
+
+const {
+ randomBytes: _randomBytes,
+} = require('internal/crypto/random');
+
+const randomBytes = promisify(_randomBytes);
+
+function validateKeyLength(length) {
+ if (length !== 256)
+ throw lazyDOMException('Invalid key length', 'DataError');
+}
+
+function c20pCipher(mode, key, data, algorithm) {
+ let tag;
+ switch (mode) {
+ case kWebCryptoCipherDecrypt: {
+ const slice = ArrayBufferIsView(data) ?
+ TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
+
+ if (data.byteLength < 16) {
+ return PromiseReject(lazyDOMException(
+ 'The provided data is too small.',
+ 'OperationError'));
+ }
+
+ tag = slice(data, -16);
+ data = slice(data, 0, -16);
+ break;
+ }
+ case kWebCryptoCipherEncrypt:
+ tag = 16;
+ break;
+ }
+
+ return jobPromise(() => new ChaCha20Poly1305CipherJob(
+ kCryptoJobAsync,
+ mode,
+ key[kKeyObject][kHandle],
+ data,
+ algorithm.iv,
+ tag,
+ algorithm.additionalData));
+}
+
+async function c20pGenerateKey(algorithm, extractable, keyUsages) {
+ const { name } = algorithm;
+
+ const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];
+
+ const usagesSet = new SafeSet(keyUsages);
+ if (hasAnyNotIn(usagesSet, checkUsages)) {
+ throw lazyDOMException(
+ `Unsupported key usage for a ${algorithm.name} key`,
+ 'SyntaxError');
+ }
+
+ const keyData = await randomBytes(32).catch((err) => {
+ throw lazyDOMException(
+ 'The operation failed for an operation-specific reason' +
+ `[${err.message}]`,
+ { name: 'OperationError', cause: err });
+ });
+
+ return new InternalCryptoKey(
+ createSecretKey(keyData),
+ { name },
+ ArrayFrom(usagesSet),
+ extractable);
+}
+
+function c20pImportKey(
+ algorithm,
+ format,
+ keyData,
+ extractable,
+ keyUsages) {
+ const { name } = algorithm;
+ const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];
+
+ const usagesSet = new SafeSet(keyUsages);
+ if (hasAnyNotIn(usagesSet, checkUsages)) {
+ throw lazyDOMException(
+ `Unsupported key usage for a ${algorithm.name} key`,
+ 'SyntaxError');
+ }
+
+ let keyObject;
+ switch (format) {
+ case 'KeyObject': {
+ keyObject = keyData;
+ break;
+ }
+ case 'raw-secret': {
+ keyObject = createSecretKey(keyData);
+ break;
+ }
+ case 'jwk': {
+ if (!keyData.kty)
+ throw lazyDOMException('Invalid keyData', 'DataError');
+
+ if (keyData.kty !== 'oct')
+ throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
+
+ if (usagesSet.size > 0 &&
+ keyData.use !== undefined &&
+ keyData.use !== 'enc') {
+ throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
+ }
+
+ validateKeyOps(keyData.key_ops, usagesSet);
+
+ if (keyData.ext !== undefined &&
+ keyData.ext === false &&
+ extractable === true) {
+ throw lazyDOMException(
+ 'JWK "ext" Parameter and extractable mismatch',
+ 'DataError');
+ }
+
+ const handle = new KeyObjectHandle();
+ try {
+ handle.initJwk(keyData);
+ } catch (err) {
+ throw lazyDOMException(
+ 'Invalid keyData', { name: 'DataError', cause: err });
+ }
+
+ if (keyData.alg !== undefined && keyData.alg !== 'C20P') {
+ throw lazyDOMException(
+ 'JWK "alg" does not match the requested algorithm',
+ 'DataError');
+ }
+
+ keyObject = new SecretKeyObject(handle);
+ break;
+ }
+ default:
+ return undefined;
+ }
+
+ validateKeyLength(keyObject.symmetricKeySize * 8);
+
+ return new InternalCryptoKey(
+ keyObject,
+ { name },
+ keyUsages,
+ extractable);
+}
+
+module.exports = {
+ c20pCipher,
+ c20pGenerateKey,
+ c20pImportKey,
+};
diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js
index 067e0633b80007..1c4d1285ec6c34 100644
--- a/lib/internal/crypto/hash.js
+++ b/lib/internal/crypto/hash.js
@@ -209,10 +209,21 @@ async function asyncDigest(algorithm, data) {
case 'SHA-384':
// Fall through
case 'SHA-512':
+ // Fall through
+ case 'SHA3-256':
+ // Fall through
+ case 'SHA3-384':
+ // Fall through
+ case 'SHA3-512':
+ // Fall through
+ case 'cSHAKE128':
+ // Fall through
+ case 'cSHAKE256':
return jobPromise(() => new HashJob(
kCryptoJobAsync,
normalizeHashName(algorithm.name),
- data));
+ data,
+ algorithm.length));
}
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
diff --git a/lib/internal/crypto/hashnames.js b/lib/internal/crypto/hashnames.js
index 7af2091d84de39..7a625c47e2f4b2 100644
--- a/lib/internal/crypto/hashnames.js
+++ b/lib/internal/crypto/hashnames.js
@@ -17,7 +17,7 @@ const kHashContextJwkHmac = 6;
// make it easier in the code.
const kHashNames = {
- sha1: {
+ 'sha1': {
[kHashContextNode]: 'sha1',
[kHashContextWebCrypto]: 'SHA-1',
[kHashContextJwkRsa]: 'RS1',
@@ -25,7 +25,7 @@ const kHashNames = {
[kHashContextJwkRsaOaep]: 'RSA-OAEP',
[kHashContextJwkHmac]: 'HS1',
},
- sha256: {
+ 'sha256': {
[kHashContextNode]: 'sha256',
[kHashContextWebCrypto]: 'SHA-256',
[kHashContextJwkRsa]: 'RS256',
@@ -33,7 +33,7 @@ const kHashNames = {
[kHashContextJwkRsaOaep]: 'RSA-OAEP-256',
[kHashContextJwkHmac]: 'HS256',
},
- sha384: {
+ 'sha384': {
[kHashContextNode]: 'sha384',
[kHashContextWebCrypto]: 'SHA-384',
[kHashContextJwkRsa]: 'RS384',
@@ -41,7 +41,7 @@ const kHashNames = {
[kHashContextJwkRsaOaep]: 'RSA-OAEP-384',
[kHashContextJwkHmac]: 'HS384',
},
- sha512: {
+ 'sha512': {
[kHashContextNode]: 'sha512',
[kHashContextWebCrypto]: 'SHA-512',
[kHashContextJwkRsa]: 'RS512',
@@ -49,6 +49,26 @@ const kHashNames = {
[kHashContextJwkRsaOaep]: 'RSA-OAEP-512',
[kHashContextJwkHmac]: 'HS512',
},
+ 'shake128': {
+ [kHashContextNode]: 'shake128',
+ [kHashContextWebCrypto]: 'cSHAKE128',
+ },
+ 'shake256': {
+ [kHashContextNode]: 'shake256',
+ [kHashContextWebCrypto]: 'cSHAKE256',
+ },
+ 'sha3-256': {
+ [kHashContextNode]: 'sha3-256',
+ [kHashContextWebCrypto]: 'SHA3-256',
+ },
+ 'sha3-384': {
+ [kHashContextNode]: 'sha3-384',
+ [kHashContextWebCrypto]: 'SHA3-384',
+ },
+ 'sha3-512': {
+ [kHashContextNode]: 'sha3-512',
+ [kHashContextWebCrypto]: 'SHA3-512',
+ },
};
{
diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js
index 9b4d1469cc9df5..1a5e9ccd06813e 100644
--- a/lib/internal/crypto/hkdf.js
+++ b/lib/internal/crypto/hkdf.js
@@ -170,4 +170,5 @@ module.exports = {
hkdf,
hkdfSync,
hkdfDeriveBits,
+ validateHkdfDeriveBitsLength,
};
diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js
index 60b4d26d35e967..c34f36a277ea37 100644
--- a/lib/internal/crypto/keys.js
+++ b/lib/internal/crypto/keys.js
@@ -199,6 +199,10 @@ const {
result = require('internal/crypto/aes')
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
break;
+ case 'ChaCha20-Poly1305':
+ result = require('internal/crypto/chacha20_poly1305')
+ .c20pImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
+ break;
case 'HKDF':
// Fall through
case 'PBKDF2':
@@ -293,6 +297,14 @@ const {
result = require('internal/crypto/cfrg')
.cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87':
+ result = require('internal/crypto/ml_dsa')
+ .mlDsaImportKey('KeyObject', this, algorithm, extractable, keyUsages);
+ break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js
index 0f9b1f9618d260..4a78c45c70d84d 100644
--- a/lib/internal/crypto/mac.js
+++ b/lib/internal/crypto/mac.js
@@ -62,23 +62,11 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {
return new InternalCryptoKey(
key,
- { name, length, hash: { name: hash.name } },
+ { name, length, hash },
ArrayFrom(usageSet),
extractable);
}
-function getAlgorithmName(hash) {
- switch (hash) {
- case 'SHA-1': // Fall through
- case 'SHA-256': // Fall through
- case 'SHA-384': // Fall through
- case 'SHA-512': // Fall through
- return `HS${hash.slice(4)}`;
- default:
- throw lazyDOMException('Unsupported digest algorithm', 'DataError');
- }
-}
-
function hmacImportKey(
format,
keyData,
@@ -126,7 +114,9 @@ function hmacImportKey(
}
if (keyData.alg !== undefined) {
- if (keyData.alg !== getAlgorithmName(algorithm.hash.name))
+ const expected =
+ normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac);
+ if (expected && keyData.alg !== expected)
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js
new file mode 100644
index 00000000000000..21dc95786b10e1
--- /dev/null
+++ b/lib/internal/crypto/ml_dsa.js
@@ -0,0 +1,318 @@
+'use strict';
+
+const {
+ SafeSet,
+ Uint8Array,
+} = primordials;
+
+const { Buffer } = require('buffer');
+
+const {
+ KeyObjectHandle,
+ SignJob,
+ kCryptoJobAsync,
+ kKeyTypePrivate,
+ kKeyTypePublic,
+ kSignJobModeSign,
+ kSignJobModeVerify,
+ kKeyFormatDER,
+ kWebCryptoKeyFormatRaw,
+ kWebCryptoKeyFormatPKCS8,
+ kWebCryptoKeyFormatSPKI,
+} = internalBinding('crypto');
+
+const {
+ codes: {
+ ERR_CRYPTO_INVALID_JWK,
+ },
+} = require('internal/errors');
+
+const {
+ getUsagesUnion,
+ hasAnyNotIn,
+ jobPromise,
+ validateKeyOps,
+ 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,
+} = require('internal/crypto/keys');
+
+const generateKeyPair = promisify(_generateKeyPair);
+
+function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) {
+ const checkSet = isPublic ? ['verify'] : ['sign'];
+ if (hasAnyNotIn(usages, checkSet)) {
+ throw lazyDOMException(
+ `Unsupported key usage for a ${name} key`,
+ 'SyntaxError');
+ }
+}
+
+function createMlDsaRawKey(name, keyData, isPublic) {
+ const handle = new KeyObjectHandle();
+ const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
+ if (!handle.initMlDsaRaw(name, keyData, keyType)) {
+ throw lazyDOMException('Invalid keyData', 'DataError');
+ }
+
+ return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
+}
+
+async function mlDsaGenerateKey(algorithm, extractable, keyUsages) {
+ const { name } = algorithm;
+
+ const usageSet = new SafeSet(keyUsages);
+ if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
+ 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, 'verify');
+ const privateUsages = getUsagesUnion(usageSet, 'sign');
+
+ 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 mlDsaExportKey(key, format) {
+ try {
+ switch (format) {
+ case kWebCryptoKeyFormatRaw: {
+ if (key.type === 'private') {
+ const { priv } = key[kKeyObject][kHandle].exportJwk({}, false);
+ return Buffer.alloc(32, priv, 'base64url').buffer;
+ }
+
+ const { pub } = key[kKeyObject][kHandle].exportJwk({}, false);
+ return Buffer.alloc(Buffer.byteLength(pub, 'base64url'), pub, 'base64url').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 buffer = new Uint8Array(54);
+ buffer.set([
+ 0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0B, 0x06,
+ 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
+ 0x03, 0x00, 0x04, 0x22, 0x80, 0x20,
+ ], 0);
+ switch (key.algorithm.name) {
+ case 'ML-DSA-44':
+ buffer.set([0x11], 17);
+ break;
+ case 'ML-DSA-65':
+ buffer.set([0x12], 17);
+ break;
+ case 'ML-DSA-87':
+ buffer.set([0x13], 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 mlDsaImportKey(
+ format,
+ keyData,
+ algorithm,
+ extractable,
+ keyUsages) {
+
+ const { name } = algorithm;
+ let keyObject;
+ const usagesSet = new SafeSet(keyUsages);
+ switch (format) {
+ case 'KeyObject': {
+ verifyAcceptableMlDsaKeyUse(name, keyData.type === 'public', usagesSet);
+ keyObject = keyData;
+ break;
+ }
+ case 'spki': {
+ verifyAcceptableMlDsaKeyUse(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': {
+ verifyAcceptableMlDsaKeyUse(name, false, usagesSet);
+ try {
+ keyObject = createPrivateKey({
+ key: keyData,
+ format: 'der',
+ type: 'pkcs8',
+ });
+ } catch (err) {
+ throw lazyDOMException(
+ 'Invalid keyData', { name: 'DataError', cause: err });
+ }
+ break;
+ }
+ case 'jwk': {
+ if (!keyData.kty)
+ throw lazyDOMException('Invalid keyData', 'DataError');
+ if (keyData.kty !== 'AKP')
+ throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
+ if (keyData.alg !== name)
+ throw lazyDOMException(
+ 'JWK "alg" Parameter and algorithm name mismatch', 'DataError');
+ const isPublic = keyData.priv === undefined;
+
+ if (usagesSet.size > 0 && keyData.use !== undefined) {
+ if (keyData.use !== 'sig')
+ throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
+ }
+
+ validateKeyOps(keyData.key_ops, usagesSet);
+
+ if (keyData.ext !== undefined &&
+ keyData.ext === false &&
+ extractable === true) {
+ throw lazyDOMException(
+ 'JWK "ext" Parameter and extractable mismatch',
+ 'DataError');
+ }
+
+ if (!isPublic && typeof keyData.pub !== 'string') {
+ throw lazyDOMException('Invalid JWK', 'DataError');
+ }
+
+ verifyAcceptableMlDsaKeyUse(
+ name,
+ isPublic,
+ usagesSet);
+
+ try {
+ const publicKeyObject = createMlDsaRawKey(
+ name,
+ Buffer.from(keyData.pub, 'base64url'),
+ true);
+
+ if (isPublic) {
+ keyObject = publicKeyObject;
+ } else {
+ keyObject = createMlDsaRawKey(
+ name,
+ Buffer.from(keyData.priv, 'base64url'),
+ false);
+
+ if (!createPublicKey(keyObject).equals(publicKeyObject)) {
+ throw new ERR_CRYPTO_INVALID_JWK();
+ }
+ }
+ } catch (err) {
+ throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err });
+ }
+ break;
+ }
+ case 'raw-public':
+ case 'raw-seed': {
+ const isPublic = format === 'raw-public';
+ verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet);
+
+ try {
+ keyObject = createMlDsaRawKey(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 mlDsaSignVerify(key, data, algorithm, signature) {
+ const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
+ const type = mode === kSignJobModeSign ? 'private' : 'public';
+
+ if (key.type !== type)
+ throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
+
+ return jobPromise(() => new SignJob(
+ kCryptoJobAsync,
+ mode,
+ key[kKeyObject][kHandle],
+ undefined,
+ undefined,
+ undefined,
+ data,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ signature));
+}
+
+module.exports = {
+ mlDsaExportKey,
+ mlDsaImportKey,
+ mlDsaGenerateKey,
+ mlDsaSignVerify,
+};
diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js
index 0dc0d25682d2cd..ebdba7334f6556 100644
--- a/lib/internal/crypto/pbkdf2.js
+++ b/lib/internal/crypto/pbkdf2.js
@@ -128,4 +128,5 @@ module.exports = {
pbkdf2,
pbkdf2Sync,
pbkdf2DeriveBits,
+ validatePbkdf2DeriveBitsLength,
};
diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js
index bf6c341c5a723d..dd0669df4ed032 100644
--- a/lib/internal/crypto/rsa.js
+++ b/lib/internal/crypto/rsa.js
@@ -161,7 +161,7 @@ async function rsaKeyGenerate(
name,
modulusLength,
publicExponent,
- hash: { name: hash.name },
+ hash,
};
let publicUsages;
@@ -281,7 +281,7 @@ function rsaImportKey(
algorithm.name === 'RSA-PSS' ? normalizeHashName.kContextJwkRsaPss :
normalizeHashName.kContextJwkRsaOaep);
- if (keyData.alg !== expected)
+ if (expected && keyData.alg !== expected)
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js
index c7e0c31a4cc609..685637ab7435df 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -33,6 +33,9 @@ const {
secureHeapUsed: _secureHeapUsed,
getCachedAliases,
getOpenSSLSecLevelCrypto: getOpenSSLSecLevel,
+ EVP_PKEY_ML_DSA_44,
+ EVP_PKEY_ML_DSA_65,
+ EVP_PKEY_ML_DSA_87,
} = internalBinding('crypto');
const { getOptionValue } = require('internal/options');
@@ -168,140 +171,232 @@ const kNamedCurveAliases = {
'P-521': 'secp521r1',
};
-const kSupportedAlgorithms = {
- 'digest': {
- 'SHA-1': null,
- 'SHA-256': null,
- 'SHA-384': null,
- 'SHA-512': null,
+// Algorithm definitions organized by algorithm name
+const kAlgorithmDefinitions = {
+ 'AES-CBC': {
+ 'generateKey': 'AesKeyGenParams',
+ 'exportKey': null,
+ 'importKey': null,
+ 'encrypt': 'AesCbcParams',
+ 'decrypt': 'AesCbcParams',
+ 'get key length': 'AesDerivedKeyParams',
},
- 'generateKey': {
- 'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams',
- 'RSA-PSS': 'RsaHashedKeyGenParams',
- 'RSA-OAEP': 'RsaHashedKeyGenParams',
- 'ECDSA': 'EcKeyGenParams',
- 'ECDH': 'EcKeyGenParams',
- 'AES-CTR': 'AesKeyGenParams',
- 'AES-CBC': 'AesKeyGenParams',
- 'AES-GCM': 'AesKeyGenParams',
- 'AES-KW': 'AesKeyGenParams',
- 'HMAC': 'HmacKeyGenParams',
- 'Ed25519': null,
- 'X25519': null,
+ 'AES-CTR': {
+ 'generateKey': 'AesKeyGenParams',
+ 'exportKey': null,
+ 'importKey': null,
+ 'encrypt': 'AesCtrParams',
+ 'decrypt': 'AesCtrParams',
+ 'get key length': 'AesDerivedKeyParams',
},
- 'exportKey': {
- 'RSASSA-PKCS1-v1_5': null,
- 'RSA-PSS': null,
- 'RSA-OAEP': null,
- 'ECDSA': null,
- 'ECDH': null,
- 'HMAC': null,
- 'AES-CTR': null,
- 'AES-CBC': null,
- 'AES-GCM': null,
- 'AES-KW': null,
- 'Ed25519': null,
- 'X25519': null,
+ 'AES-GCM': {
+ 'generateKey': 'AesKeyGenParams',
+ 'exportKey': null,
+ 'importKey': null,
+ 'encrypt': 'AeadParams',
+ 'decrypt': 'AeadParams',
+ 'get key length': 'AesDerivedKeyParams',
},
- 'sign': {
- 'RSASSA-PKCS1-v1_5': null,
- 'RSA-PSS': 'RsaPssParams',
- 'ECDSA': 'EcdsaParams',
- 'HMAC': null,
- 'Ed25519': null,
+ 'AES-KW': {
+ 'generateKey': 'AesKeyGenParams',
+ 'exportKey': null,
+ 'importKey': null,
+ 'get key length': 'AesDerivedKeyParams',
+ 'wrapKey': null,
+ 'unwrapKey': null,
},
- 'verify': {
- 'RSASSA-PKCS1-v1_5': null,
- 'RSA-PSS': 'RsaPssParams',
- 'ECDSA': 'EcdsaParams',
- 'HMAC': null,
- 'Ed25519': null,
+ 'ChaCha20-Poly1305': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'encrypt': 'AeadParams',
+ 'decrypt': 'AeadParams',
+ 'get key length': null,
},
- 'importKey': {
- 'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams',
- 'RSA-PSS': 'RsaHashedImportParams',
- 'RSA-OAEP': 'RsaHashedImportParams',
- 'ECDSA': 'EcKeyImportParams',
- 'ECDH': 'EcKeyImportParams',
- 'HMAC': 'HmacImportParams',
- 'HKDF': null,
- 'PBKDF2': null,
- 'AES-CTR': null,
- 'AES-CBC': null,
- 'AES-GCM': null,
- 'AES-KW': null,
- 'Ed25519': null,
- 'X25519': null,
+ 'cSHAKE128': { 'digest': 'CShakeParams' },
+ 'cSHAKE256': { 'digest': 'CShakeParams' },
+ 'ECDH': {
+ 'generateKey': 'EcKeyGenParams',
+ 'exportKey': null,
+ 'importKey': 'EcKeyImportParams',
+ 'deriveBits': 'EcdhKeyDeriveParams',
},
- 'deriveBits': {
- 'HKDF': 'HkdfParams',
- 'PBKDF2': 'Pbkdf2Params',
- 'ECDH': 'EcdhKeyDeriveParams',
- 'X25519': 'EcdhKeyDeriveParams',
+ 'ECDSA': {
+ 'generateKey': 'EcKeyGenParams',
+ 'exportKey': null,
+ 'importKey': 'EcKeyImportParams',
+ 'sign': 'EcdsaParams',
+ 'verify': 'EcdsaParams',
},
- 'encrypt': {
- 'RSA-OAEP': 'RsaOaepParams',
- 'AES-CBC': 'AesCbcParams',
- 'AES-GCM': 'AesGcmParams',
- 'AES-CTR': 'AesCtrParams',
+ 'Ed25519': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'sign': null,
+ 'verify': null,
},
- 'decrypt': {
- 'RSA-OAEP': 'RsaOaepParams',
- 'AES-CBC': 'AesCbcParams',
- 'AES-GCM': 'AesGcmParams',
- 'AES-CTR': 'AesCtrParams',
+ 'Ed448': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'sign': 'Ed448Params',
+ 'verify': 'Ed448Params',
},
- 'get key length': {
- 'AES-CBC': 'AesDerivedKeyParams',
- 'AES-CTR': 'AesDerivedKeyParams',
- 'AES-GCM': 'AesDerivedKeyParams',
- 'AES-KW': 'AesDerivedKeyParams',
- 'HMAC': 'HmacImportParams',
- 'HKDF': null,
- 'PBKDF2': null,
+ 'HKDF': {
+ 'importKey': null,
+ 'deriveBits': 'HkdfParams',
+ 'get key length': null,
},
- 'wrapKey': {
- 'AES-KW': null,
+ 'HMAC': {
+ 'generateKey': 'HmacKeyGenParams',
+ 'exportKey': null,
+ 'importKey': 'HmacImportParams',
+ 'sign': null,
+ 'verify': null,
+ 'get key length': 'HmacImportParams',
},
- 'unwrapKey': {
- 'AES-KW': null,
+ 'ML-DSA-44': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'sign': 'ContextParams',
+ 'verify': 'ContextParams',
},
-};
-
-const experimentalAlgorithms = ObjectEntries({
- 'X448': {
- generateKey: null,
- importKey: null,
- deriveBits: 'EcdhKeyDeriveParams',
- exportKey: null,
+ 'ML-DSA-65': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'sign': 'ContextParams',
+ 'verify': 'ContextParams',
},
- 'Ed448': {
- generateKey: null,
- sign: 'Ed448Params',
- verify: 'Ed448Params',
- importKey: null,
- exportKey: null,
+ 'ML-DSA-87': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'sign': 'ContextParams',
+ 'verify': 'ContextParams',
},
-});
+ 'PBKDF2': {
+ 'importKey': null,
+ 'deriveBits': 'Pbkdf2Params',
+ 'get key length': null,
+ },
+ 'RSA-OAEP': {
+ 'generateKey': 'RsaHashedKeyGenParams',
+ 'exportKey': null,
+ 'importKey': 'RsaHashedImportParams',
+ 'encrypt': 'RsaOaepParams',
+ 'decrypt': 'RsaOaepParams',
+ },
+ 'RSA-PSS': {
+ 'generateKey': 'RsaHashedKeyGenParams',
+ 'exportKey': null,
+ 'importKey': 'RsaHashedImportParams',
+ 'sign': 'RsaPssParams',
+ 'verify': 'RsaPssParams',
+ },
+ 'RSASSA-PKCS1-v1_5': {
+ 'generateKey': 'RsaHashedKeyGenParams',
+ 'exportKey': null,
+ 'importKey': 'RsaHashedImportParams',
+ 'sign': null,
+ 'verify': null,
+ },
+ 'SHA-1': { 'digest': null },
+ 'SHA-256': { 'digest': null },
+ 'SHA-384': { 'digest': null },
+ 'SHA-512': { 'digest': null },
+ 'SHA3-256': { 'digest': null },
+ 'SHA3-384': { 'digest': null },
+ 'SHA3-512': { 'digest': null },
+ 'X25519': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'deriveBits': 'EcdhKeyDeriveParams',
+ },
+ 'X448': {
+ 'generateKey': null,
+ 'exportKey': null,
+ 'importKey': null,
+ 'deriveBits': 'EcdhKeyDeriveParams',
+ },
+};
+
+// Conditionally supported algorithms
+const conditionalAlgorithms = {
+ 'AES-KW': !process.features.openssl_is_boringssl,
+ 'ChaCha20-Poly1305': !process.features.openssl_is_boringssl ||
+ ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
+ 'cSHAKE128': !process.features.openssl_is_boringssl ||
+ ArrayPrototypeIncludes(getHashes(), 'shake128'),
+ 'cSHAKE256': !process.features.openssl_is_boringssl ||
+ ArrayPrototypeIncludes(getHashes(), 'shake256'),
+ 'Ed448': !process.features.openssl_is_boringssl,
+ 'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
+ 'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
+ 'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
+ 'SHA3-256': !process.features.openssl_is_boringssl ||
+ ArrayPrototypeIncludes(getHashes(), 'sha3-256'),
+ 'SHA3-384': !process.features.openssl_is_boringssl ||
+ ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
+ 'SHA3-512': !process.features.openssl_is_boringssl ||
+ ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
+ 'X448': !process.features.openssl_is_boringssl,
+};
-for (let i = 0; i < experimentalAlgorithms.length; i++) {
- const name = experimentalAlgorithms[i][0];
- const ops = ObjectEntries(experimentalAlgorithms[i][1]);
- for (let j = 0; j < ops.length; j++) {
- const { 0: op, 1: dict } = ops[j];
- ObjectDefineProperty(kSupportedAlgorithms[op], name, {
- get() {
- emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
- return dict;
- },
- __proto__: null,
- enumerable: true,
- });
+// Experimental algorithms
+const experimentalAlgorithms = [
+ 'ChaCha20-Poly1305',
+ 'cSHAKE128',
+ 'cSHAKE256',
+ 'Ed448',
+ 'ML-DSA-44',
+ 'ML-DSA-65',
+ 'ML-DSA-87',
+ 'SHA3-256',
+ 'SHA3-384',
+ 'SHA3-512',
+ 'X448',
+];
+
+// Transform the algorithm definitions into the operation-keyed structure
+function createSupportedAlgorithms(algorithmDefs) {
+ const result = {};
+
+ for (const { 0: algorithmName, 1: operations } of ObjectEntries(algorithmDefs)) {
+ // Skip algorithms that are conditionally not supported
+ if (ObjectPrototypeHasOwnProperty(conditionalAlgorithms, algorithmName) &&
+ !conditionalAlgorithms[algorithmName]) {
+ continue;
+ }
+
+ for (const { 0: operation, 1: dict } of ObjectEntries(operations)) {
+ result[operation] ||= {};
+
+ // Add experimental warnings for experimental algorithms
+ if (ArrayPrototypeIncludes(experimentalAlgorithms, algorithmName)) {
+ ObjectDefineProperty(result[operation], algorithmName, {
+ get() {
+ emitExperimentalWarning(`The ${algorithmName} Web Crypto API algorithm`);
+ return dict;
+ },
+ __proto__: null,
+ enumerable: true,
+ });
+ } else {
+ result[operation][algorithmName] = dict;
+ }
+ }
}
+
+ return result;
}
+const kSupportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
+
const simpleAlgorithmDictionaries = {
- AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
+ AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
EcKeyGenParams: {},
HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
@@ -314,10 +409,15 @@ const simpleAlgorithmDictionaries = {
info: 'BufferSource',
},
Ed448Params: { context: 'BufferSource' },
+ ContextParams: { context: 'BufferSource' },
Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' },
RsaOaepParams: { label: 'BufferSource' },
RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' },
EcKeyImportParams: {},
+ CShakeParams: {
+ functionName: 'BufferSource',
+ customization: 'BufferSource',
+ },
};
function validateMaxBufferLength(data, name) {
@@ -526,15 +626,28 @@ function getBlockSize(name) {
// Fall through
case 'SHA-512':
return 1024;
+ case 'SHA3-256':
+ return 1088;
+ case 'SHA3-384':
+ return 832;
+ case 'SHA3-512':
+ return 576;
}
}
function getDigestSizeInBytes(name) {
switch (name) {
- case 'SHA-1': return 20;
- case 'SHA-256': return 32;
- case 'SHA-384': return 48;
- case 'SHA-512': return 64;
+ case 'SHA-1':
+ return 20;
+ case 'SHA-256': // Fall through
+ case 'SHA3-256':
+ return 32;
+ case 'SHA-384': // Fall through
+ case 'SHA3-384':
+ return 48;
+ case 'SHA-512': // Fall through
+ case 'SHA3-512':
+ return 64;
}
}
diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index 82bdc29f50a4d4..5961ee3e67dbf7 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -8,6 +8,7 @@ const {
ReflectApply,
ReflectConstruct,
StringPrototypeRepeat,
+ StringPrototypeSlice,
SymbolToStringTag,
} = primordials;
@@ -29,6 +30,7 @@ const {
} = require('internal/errors');
const {
+ createPublicKey,
CryptoKey,
importGenericSecretKey,
} = require('internal/crypto/keys');
@@ -47,6 +49,7 @@ const {
} = require('internal/crypto/util');
const {
+ emitExperimentalWarning,
kEnumerableProperty,
lazyDOMException,
} = require('internal/util');
@@ -152,6 +155,20 @@ async function generateKey(
result = await require('internal/crypto/aes')
.aesGenerateKey(algorithm, extractable, keyUsages);
break;
+ case 'ChaCha20-Poly1305':
+ resultType = 'CryptoKey';
+ result = await require('internal/crypto/chacha20_poly1305')
+ .c20pGenerateKey(algorithm, extractable, keyUsages);
+ break;
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87':
+ resultType = 'CryptoKeyPair';
+ result = await require('internal/crypto/ml_dsa')
+ .mlDsaGenerateKey(algorithm, extractable, keyUsages);
+ break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
@@ -240,6 +257,8 @@ function getKeyLength({ name, length, hash }) {
case 'HKDF':
case 'PBKDF2':
return null;
+ case 'ChaCha20-Poly1305':
+ return 256;
}
}
@@ -311,7 +330,7 @@ async function deriveKey(
return ReflectApply(
importKey,
this,
- ['raw', bits, derivedKeyAlgorithm, extractable, keyUsages],
+ ['raw-secret', bits, derivedKeyAlgorithm, extractable, keyUsages],
);
}
@@ -338,6 +357,14 @@ async function exportKeySpki(key) {
case 'X448':
return require('internal/crypto/cfrg')
.cfrgExportKey(key, kWebCryptoKeyFormatSPKI);
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87': {
+ return require('internal/crypto/ml_dsa')
+ .mlDsaExportKey(key, kWebCryptoKeyFormatSPKI);
+ }
default:
return undefined;
}
@@ -366,12 +393,20 @@ async function exportKeyPkcs8(key) {
case 'X448':
return require('internal/crypto/cfrg')
.cfrgExportKey(key, kWebCryptoKeyFormatPKCS8);
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87': {
+ return require('internal/crypto/ml_dsa')
+ .mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8);
+ }
default:
return undefined;
}
}
-async function exportKeyRawPublic(key) {
+async function exportKeyRawPublic(key, format) {
switch (key.algorithm.name) {
case 'ECDSA':
// Fall through
@@ -387,12 +422,39 @@ async function exportKeyRawPublic(key) {
case 'X448':
return require('internal/crypto/cfrg')
.cfrgExportKey(key, kWebCryptoKeyFormatRaw);
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87': {
+ // ML-DSA keys don't recognize "raw"
+ if (format !== 'raw-public') {
+ return undefined;
+ }
+ return require('internal/crypto/ml_dsa')
+ .mlDsaExportKey(key, kWebCryptoKeyFormatRaw);
+ }
default:
return undefined;
}
}
-async function exportKeyRawSecret(key) {
+async function exportKeyRawSeed(key) {
+ switch (key.algorithm.name) {
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87': {
+ return require('internal/crypto/ml_dsa')
+ .mlDsaExportKey(key, kWebCryptoKeyFormatRaw);
+ }
+ default:
+ return undefined;
+ }
+}
+
+async function exportKeyRawSecret(key, format) {
switch (key.algorithm.name) {
case 'AES-CTR':
// Fall through
@@ -404,6 +466,11 @@ async function exportKeyRawSecret(key) {
// Fall through
case 'HMAC':
return key[kKeyObject][kHandle].export().buffer;
+ case 'ChaCha20-Poly1305':
+ if (format === 'raw-secret') {
+ return key[kKeyObject][kHandle].export().buffer;
+ }
+ return undefined;
default:
return undefined;
}
@@ -415,21 +482,27 @@ async function exportKeyJWK(key) {
ext: key.extractable,
};
switch (key.algorithm.name) {
- case 'RSASSA-PKCS1-v1_5':
- parameters.alg = normalizeHashName(
+ case 'RSASSA-PKCS1-v1_5': {
+ const alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkRsa);
+ if (alg) parameters.alg = alg;
break;
- case 'RSA-PSS':
- parameters.alg = normalizeHashName(
+ }
+ case 'RSA-PSS': {
+ const alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkRsaPss);
+ if (alg) parameters.alg = alg;
break;
- case 'RSA-OAEP':
- parameters.alg = normalizeHashName(
+ }
+ case 'RSA-OAEP': {
+ const alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkRsaOaep);
+ if (alg) parameters.alg = alg;
break;
+ }
case 'ECDSA':
// Fall through
case 'ECDH':
@@ -437,6 +510,12 @@ async function exportKeyJWK(key) {
case 'X25519':
// Fall through
case 'X448':
+ // Fall through
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87':
break;
case 'Ed25519':
// Fall through
@@ -453,11 +532,16 @@ async function exportKeyJWK(key) {
parameters.alg = require('internal/crypto/aes')
.getAlgorithmName(key.algorithm.name, key.algorithm.length);
break;
- case 'HMAC':
- parameters.alg = normalizeHashName(
+ case 'ChaCha20-Poly1305':
+ parameters.alg = 'C20P';
+ break;
+ case 'HMAC': {
+ const alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkHmac);
+ if (alg) parameters.alg = alg;
break;
+ }
default:
return undefined;
}
@@ -508,15 +592,29 @@ async function exportKey(format, key) {
result = await exportKeyJWK(key);
break;
}
- case 'raw': {
+ case 'raw-secret': {
if (key.type === 'secret') {
- result = await exportKeyRawSecret(key);
- break;
+ result = await exportKeyRawSecret(key, format);
}
-
+ break;
+ }
+ case 'raw-public': {
if (key.type === 'public') {
- result = await exportKeyRawPublic(key);
- break;
+ result = await exportKeyRawPublic(key, format);
+ }
+ break;
+ }
+ case 'raw-seed': {
+ if (key.type === 'private') {
+ result = await exportKeyRawSeed(key);
+ }
+ break;
+ }
+ case 'raw': {
+ if (key.type === 'secret') {
+ result = await exportKeyRawSecret(key, format);
+ } else if (key.type === 'public') {
+ result = await exportKeyRawPublic(key, format);
}
break;
}
@@ -531,6 +629,16 @@ async function exportKey(format, key) {
return result;
}
+function aliasKeyFormat(format) {
+ switch (format) {
+ case 'raw-public':
+ case 'raw-secret':
+ return 'raw';
+ default:
+ return format;
+ }
+}
+
async function importKey(
format,
keyData,
@@ -572,12 +680,14 @@ async function importKey(
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
+ format = aliasKeyFormat(format);
result = require('internal/crypto/rsa')
.rsaImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
case 'ECDSA':
// Fall through
case 'ECDH':
+ format = aliasKeyFormat(format);
result = require('internal/crypto/ec')
.ecImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
@@ -588,10 +698,12 @@ async function importKey(
case 'X25519':
// Fall through
case 'X448':
+ format = aliasKeyFormat(format);
result = require('internal/crypto/cfrg')
.cfrgImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
case 'HMAC':
+ format = aliasKeyFormat(format);
result = require('internal/crypto/mac')
.hmacImportKey(format, keyData, algorithm, extractable, keyUsages);
break;
@@ -602,12 +714,18 @@ async function importKey(
case 'AES-GCM':
// Fall through
case 'AES-KW':
+ format = aliasKeyFormat(format);
result = require('internal/crypto/aes')
.aesImportKey(algorithm, format, keyData, extractable, keyUsages);
break;
+ case 'ChaCha20-Poly1305':
+ result = require('internal/crypto/chacha20_poly1305')
+ .c20pImportKey(algorithm, format, keyData, extractable, keyUsages);
+ break;
case 'HKDF':
// Fall through
case 'PBKDF2':
+ format = aliasKeyFormat(format);
result = importGenericSecretKey(
algorithm,
format,
@@ -615,6 +733,14 @@ async function importKey(
extractable,
keyUsages);
break;
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87':
+ result = require('internal/crypto/ml_dsa')
+ .mlDsaImportKey(format, keyData, algorithm, extractable, keyUsages);
+ break;
}
if (!result) {
@@ -796,6 +922,13 @@ function signVerify(algorithm, key, data, signature) {
case 'HMAC':
return require('internal/crypto/mac')
.hmacSignVerify(key, data, algorithm, signature);
+ case 'ML-DSA-44':
+ // Fall through
+ case 'ML-DSA-65':
+ // Fall through
+ case 'ML-DSA-87':
+ return require('internal/crypto/ml_dsa')
+ .mlDsaSignVerify(key, data, algorithm, signature);
}
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
@@ -877,6 +1010,9 @@ async function cipherOrWrap(mode, algorithm, key, data, op) {
case 'AES-GCM':
return require('internal/crypto/aes')
.aesCipher(mode, key, data, algorithm);
+ case 'ChaCha20-Poly1305':
+ return require('internal/crypto/chacha20_poly1305')
+ .c20pCipher(mode, key, data, algorithm);
case 'AES-KW':
if (op === 'wrapKey' || op === 'unwrapKey') {
return require('internal/crypto/aes')
@@ -932,6 +1068,31 @@ async function decrypt(algorithm, key, data) {
return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt');
}
+// Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey
+async function getPublicKey(key, keyUsages) {
+ emitExperimentalWarning('The getPublicKey Web Crypto API method');
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
+
+ webidl ??= require('internal/crypto/webidl');
+ const prefix = "Failed to execute 'getPublicKey' on 'SubtleCrypto'";
+ webidl.requiredArguments(arguments.length, 2, { prefix });
+ key = webidl.converters.CryptoKey(key, {
+ prefix,
+ context: '1st argument',
+ });
+ keyUsages = webidl.converters['sequence'](keyUsages, {
+ prefix,
+ context: '2nd argument',
+ });
+
+ if (key.type !== 'private')
+ throw lazyDOMException('key must be a private key', 'InvalidAccessError');
+
+ const keyObject = createPublicKey(key[kKeyObject]);
+
+ return keyObject.toCryptoKey(key.algorithm, true, keyUsages);
+}
+
// The SubtleCrypto and Crypto classes are defined as part of the
// Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/
@@ -939,7 +1100,168 @@ class SubtleCrypto {
constructor() {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
+
+ // Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports
+ static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
+ emitExperimentalWarning('The supports Web Crypto API method');
+ if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor');
+ webidl ??= require('internal/crypto/webidl');
+ const prefix = "Failed to execute 'supports' on 'SubtleCrypto'";
+ webidl.requiredArguments(arguments.length, 2, { prefix });
+
+ operation = webidl.converters.DOMString(operation, {
+ prefix,
+ context: '1st argument',
+ });
+ algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
+ prefix,
+ context: '2nd argument',
+ });
+
+ switch (operation) {
+ case 'encrypt':
+ case 'decrypt':
+ case 'sign':
+ case 'verify':
+ case 'digest':
+ case 'generateKey':
+ case 'deriveKey':
+ case 'deriveBits':
+ case 'importKey':
+ case 'exportKey':
+ case 'wrapKey':
+ case 'unwrapKey':
+ case 'getPublicKey':
+ break;
+ default:
+ return false;
+ }
+
+ let length;
+ let additionalAlgorithm;
+ if (operation === 'deriveKey') {
+ additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
+ prefix,
+ context: '3rd argument',
+ });
+
+ if (!check('importKey', additionalAlgorithm)) {
+ return false;
+ }
+
+ try {
+ length = getKeyLength(normalizeAlgorithm(additionalAlgorithm, 'get key length'));
+ } catch {
+ return false;
+ }
+
+ operation = 'deriveBits';
+ } else if (operation === 'wrapKey') {
+ additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
+ prefix,
+ context: '3rd argument',
+ });
+
+ if (!check('exportKey', additionalAlgorithm)) {
+ return false;
+ }
+ } else if (operation === 'unwrapKey') {
+ additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
+ prefix,
+ context: '3rd argument',
+ });
+
+ if (!check('importKey', additionalAlgorithm)) {
+ return false;
+ }
+ } else if (operation === 'deriveBits') {
+ length = lengthOrAdditionalAlgorithm;
+ if (length !== null) {
+ length = webidl.converters['unsigned long'](length, {
+ prefix,
+ context: '3rd argument',
+ });
+ }
+ } else if (operation === 'getPublicKey') {
+ let normalizedAlgorithm;
+ try {
+ normalizedAlgorithm = normalizeAlgorithm(algorithm, 'exportKey');
+ } catch {
+ return false;
+ }
+
+ switch (StringPrototypeSlice(normalizedAlgorithm.name, 0, 2)) {
+ case 'ML': // ML-DSA-*, ML-KEM-*
+ case 'SL': // SLH-DSA-*
+ case 'RS': // RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5
+ case 'EC': // ECDSA, ECDH
+ case 'Ed': // Ed*
+ case 'X2': // X25519
+ case 'X4': // X448
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ return check(operation, algorithm, length);
+ }
+}
+
+function check(op, alg, length) {
+ let normalizedAlgorithm;
+ try {
+ normalizedAlgorithm = normalizeAlgorithm(alg, op);
+ } catch {
+ if (op === 'wrapKey') {
+ return check('encrypt', alg);
+ }
+
+ if (op === 'unwrapKey') {
+ return check('decrypt', alg);
+ }
+
+ return false;
+ }
+
+ switch (op) {
+ case 'encrypt':
+ case 'decrypt':
+ case 'sign':
+ case 'verify':
+ case 'digest':
+ case 'generateKey':
+ case 'importKey':
+ case 'exportKey':
+ case 'wrapKey':
+ case 'unwrapKey':
+ return true;
+ case 'deriveBits': {
+ if (normalizedAlgorithm.name === 'HKDF') {
+ try {
+ require('internal/crypto/hkdf').validateHkdfDeriveBitsLength(length);
+ } catch {
+ return false;
+ }
+ }
+
+ if (normalizedAlgorithm.name === 'PBKDF2') {
+ try {
+ require('internal/crypto/pbkdf2').validatePbkdf2DeriveBitsLength(length);
+ } catch {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ default: {
+ const assert = require('internal/assert');
+ assert.fail('Unreachable code');
+ }
+ }
}
+
const subtle = ReflectConstruct(function() {}, [], SubtleCrypto);
class Crypto {
@@ -1083,6 +1405,13 @@ ObjectDefineProperties(
writable: true,
value: unwrapKey,
},
+ getPublicKey: {
+ __proto__: null,
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ value: getPublicKey,
+ },
});
module.exports = {
diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js
index cba02279977e4b..cf930086e169ea 100644
--- a/lib/internal/crypto/webidl.js
+++ b/lib/internal/crypto/webidl.js
@@ -23,6 +23,8 @@ const {
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
String,
+ StringPrototypeStartsWith,
+ StringPrototypeToLowerCase,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetSymbolToStringTag,
} = primordials;
@@ -192,6 +194,16 @@ converters.object = (V, opts) => {
const isNonSharedArrayBuffer = isArrayBuffer;
+function ensureSHA(V, label) {
+ if (
+ typeof V === 'string' ?
+ !StringPrototypeStartsWith(StringPrototypeToLowerCase(V), 'sha') :
+ V.name?.toLowerCase?.().startsWith('sha') === false
+ )
+ throw lazyDOMException(
+ `Only SHA hashes are supported in ${label}`, 'NotSupportedError');
+}
+
converters.Uint8Array = (V, opts = kEmptyObject) => {
if (!ArrayBufferIsView(V) ||
TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') {
@@ -332,6 +344,10 @@ converters.AlgorithmIdentifier = (V, opts) => {
converters.KeyFormat = createEnumConverter('KeyFormat', [
'raw',
+ 'raw-public',
+ 'raw-seed',
+ 'raw-secret',
+ 'raw-private',
'pkcs8',
'spki',
'jwk',
@@ -389,6 +405,7 @@ converters.RsaHashedKeyGenParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
+ validator: (V, dict) => ensureSHA(V, 'RsaHashedKeyGenParams'),
required: true,
},
]);
@@ -399,6 +416,7 @@ converters.RsaHashedImportParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
+ validator: (V, dict) => ensureSHA(V, 'RsaHashedImportParams'),
required: true,
},
]);
@@ -445,6 +463,7 @@ converters.HmacKeyGenParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
+ validator: (V, dict) => ensureSHA(V, 'HmacKeyGenParams'),
required: true,
},
{
@@ -464,6 +483,15 @@ function validateHmacKeyAlgorithm(length) {
throw lazyDOMException('Unsupported algorithm.length', 'NotSupportedError');
}
+function validateZeroLength(parameterName) {
+ return (V, dict) => {
+ if (V.byteLength) {
+ throw lazyDOMException(
+ `Non zero-length ${parameterName} is not supported.`, 'NotSupportedError');
+ }
+ };
+}
+
converters.RsaPssParams = createDictionaryConverter(
'RsaPssParams', [
...new SafeArrayIterator(dictAlgorithm),
@@ -490,6 +518,7 @@ converters.EcdsaParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
+ validator: (V, dict) => ensureSHA(V, 'EcdsaParams'),
required: true,
},
]);
@@ -500,6 +529,7 @@ converters.HmacImportParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
+ validator: (V, dict) => ensureSHA(V, 'HmacImportParams'),
required: true,
},
{
@@ -545,6 +575,8 @@ converters.JsonWebKey = createDictionaryConverter(
simpleDomStringKey('dp'),
simpleDomStringKey('dq'),
simpleDomStringKey('qi'),
+ simpleDomStringKey('pub'),
+ simpleDomStringKey('priv'),
{
key: 'oth',
converter: converters['sequence'],
@@ -558,6 +590,7 @@ converters.HkdfParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
+ validator: (V, dict) => ensureSHA(V, 'HkdfParams'),
required: true,
},
{
@@ -572,12 +605,40 @@ converters.HkdfParams = createDictionaryConverter(
},
]);
+converters.CShakeParams = createDictionaryConverter(
+ 'CShakeParams', [
+ ...new SafeArrayIterator(dictAlgorithm),
+ {
+ key: 'length',
+ converter: (V, opts) =>
+ converters['unsigned long'](V, { ...opts, enforceRange: true }),
+ validator: (V, opts) => {
+ // The Web Crypto spec allows for SHAKE output length that are not multiples of
+ // 8. We don't.
+ if (V % 8)
+ throw lazyDOMException('Unsupported CShakeParams length', 'NotSupportedError');
+ },
+ required: true,
+ },
+ {
+ key: 'functionName',
+ converter: converters.BufferSource,
+ validator: validateZeroLength('CShakeParams.functionName'),
+ },
+ {
+ key: 'customization',
+ converter: converters.BufferSource,
+ validator: validateZeroLength('CShakeParams.customization'),
+ },
+ ]);
+
converters.Pbkdf2Params = createDictionaryConverter(
'Pbkdf2Params', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
+ validator: (V, dict) => ensureSHA(V, 'Pbkdf2Params'),
required: true,
},
{
@@ -620,13 +681,22 @@ converters.AesCbcParams = createDictionaryConverter(
},
]);
-converters.AesGcmParams = createDictionaryConverter(
- 'AesGcmParams', [
+converters.AeadParams = createDictionaryConverter(
+ 'AeadParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'iv',
converter: converters.BufferSource,
- validator: (V, dict) => validateMaxBufferLength(V, 'algorithm.iv'),
+ validator: (V, dict) => {
+ switch (StringPrototypeToLowerCase(dict.name)) {
+ case 'chacha20-poly1305':
+ validateByteLength(V, 'algorithm.iv', 12);
+ break;
+ case 'aes-gcm':
+ validateMaxBufferLength(V, 'algorithm.iv');
+ break;
+ }
+ },
required: true,
},
{
@@ -634,10 +704,21 @@ converters.AesGcmParams = createDictionaryConverter(
converter: (V, opts) =>
converters.octet(V, { ...opts, enforceRange: true }),
validator: (V, dict) => {
- if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) {
- throw lazyDOMException(
- `${V} is not a valid AES-GCM tag length`,
- 'OperationError');
+ switch (StringPrototypeToLowerCase(dict.name)) {
+ case 'chacha20-poly1305':
+ if (V !== 128) {
+ throw lazyDOMException(
+ `${V} is not a valid ChaCha20-Poly1305 tag length`,
+ 'OperationError');
+ }
+ break;
+ case 'aes-gcm':
+ if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) {
+ throw lazyDOMException(
+ `${V} is not a valid AES-GCM tag length`,
+ 'OperationError');
+ }
+ break;
}
},
},
@@ -685,29 +766,26 @@ converters.EcdhKeyDeriveParams = createDictionaryConverter(
throw lazyDOMException(
'algorithm.public must be a public key', 'InvalidAccessError');
- if (V.algorithm.name.toUpperCase() !== dict.name.toUpperCase())
+ if (StringPrototypeToLowerCase(V.algorithm.name) !== StringPrototypeToLowerCase(dict.name))
throw lazyDOMException(
- `algorithm.public must be an ${dict.name.toUpperCase()} key`,
+ 'key algorithm mismatch',
'InvalidAccessError');
},
required: true,
},
]);
-converters.Ed448Params = createDictionaryConverter(
- 'Ed448Params', [
- ...new SafeArrayIterator(dictAlgorithm),
- {
- key: 'context',
- converter: converters.BufferSource,
- validator: (V, dict) => {
- if (V.byteLength)
- throw lazyDOMException(
- 'Non zero-length context is not supported.', 'NotSupportedError');
+for (const name of ['Ed448Params', 'ContextParams']) {
+ converters[name] = createDictionaryConverter(
+ name, [
+ ...new SafeArrayIterator(dictAlgorithm),
+ {
+ key: 'context',
+ converter: converters.BufferSource,
+ validator: validateZeroLength(`${name}.context`),
},
- required: false,
- },
- ]);
+ ]);
+}
module.exports = {
converters,
diff --git a/node.gyp b/node.gyp
index 428e0219ccd763..54d739d576973e 100644
--- a/node.gyp
+++ b/node.gyp
@@ -333,6 +333,7 @@
'node_crypto_sources': [
'src/crypto/crypto_aes.cc',
'src/crypto/crypto_bio.cc',
+ 'src/crypto/crypto_chacha20_poly1305.cc',
'src/crypto/crypto_common.cc',
'src/crypto/crypto_dsa.cc',
'src/crypto/crypto_hkdf.cc',
diff --git a/src/crypto/crypto_chacha20_poly1305.cc b/src/crypto/crypto_chacha20_poly1305.cc
new file mode 100644
index 00000000000000..bfe904c49ad771
--- /dev/null
+++ b/src/crypto/crypto_chacha20_poly1305.cc
@@ -0,0 +1,322 @@
+#include "crypto/crypto_chacha20_poly1305.h"
+#include "async_wrap-inl.h"
+#include "base_object-inl.h"
+#include "crypto/crypto_cipher.h"
+#include "crypto/crypto_keys.h"
+#include "crypto/crypto_util.h"
+#include "env-inl.h"
+#include "memory_tracker-inl.h"
+#include "threadpoolwork-inl.h"
+#include "v8.h"
+
+#include
+
+namespace node {
+
+using ncrypto::Cipher;
+using ncrypto::CipherCtxPointer;
+using ncrypto::DataPointer;
+using v8::FunctionCallbackInfo;
+using v8::JustVoid;
+using v8::Local;
+using v8::Maybe;
+using v8::Nothing;
+using v8::Object;
+using v8::Value;
+
+namespace crypto {
+namespace {
+constexpr size_t kChaCha20Poly1305KeySize = 32;
+constexpr size_t kChaCha20Poly1305IvSize = 12;
+constexpr size_t kChaCha20Poly1305TagSize = 16;
+
+bool ValidateIV(Environment* env,
+ CryptoJobMode mode,
+ Local value,
+ ChaCha20Poly1305CipherConfig* params) {
+ ArrayBufferOrViewContents iv(value);
+ if (!iv.CheckSizeInt32()) [[unlikely]] {
+ THROW_ERR_OUT_OF_RANGE(env, "iv is too large");
+ return false;
+ }
+
+ if (iv.size() != kChaCha20Poly1305IvSize) {
+ THROW_ERR_CRYPTO_INVALID_IV(env);
+ return false;
+ }
+
+ if (mode == kCryptoJobAsync) {
+ params->iv = iv.ToCopy();
+ } else {
+ params->iv = iv.ToByteSource();
+ }
+
+ return true;
+}
+
+bool ValidateAuthTag(Environment* env,
+ CryptoJobMode mode,
+ WebCryptoCipherMode cipher_mode,
+ Local value,
+ ChaCha20Poly1305CipherConfig* params) {
+ switch (cipher_mode) {
+ case kWebCryptoCipherDecrypt: {
+ if (!IsAnyBufferSource(value)) {
+ THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(
+ env, "Authentication tag must be a buffer");
+ return false;
+ }
+
+ ArrayBufferOrViewContents tag(value);
+ if (!tag.CheckSizeInt32()) [[unlikely]] {
+ THROW_ERR_OUT_OF_RANGE(env, "tag is too large");
+ return false;
+ }
+
+ if (tag.size() != kChaCha20Poly1305TagSize) {
+ THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(
+ env, "Invalid authentication tag length");
+ return false;
+ }
+
+ if (mode == kCryptoJobAsync) {
+ params->tag = tag.ToCopy();
+ } else {
+ params->tag = tag.ToByteSource();
+ }
+ break;
+ }
+ case kWebCryptoCipherEncrypt: {
+ // For encryption, the value should be the tag length (passed from
+ // JavaScript) We expect it to be the tag size constant for
+ // ChaCha20-Poly1305
+ if (!value->IsUint32()) {
+ THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env, "Tag length must be a number");
+ return false;
+ }
+
+ uint32_t tag_length = value.As()->Value();
+ if (tag_length != kChaCha20Poly1305TagSize) {
+ THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(
+ env, "Invalid tag length for ChaCha20-Poly1305");
+ return false;
+ }
+ // Tag is generated during encryption, not provided
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ return true;
+}
+
+bool ValidateAdditionalData(Environment* env,
+ CryptoJobMode mode,
+ Local value,
+ ChaCha20Poly1305CipherConfig* params) {
+ if (IsAnyBufferSource(value)) {
+ ArrayBufferOrViewContents additional_data(value);
+ if (!additional_data.CheckSizeInt32()) [[unlikely]] {
+ THROW_ERR_OUT_OF_RANGE(env, "additional data is too large");
+ return false;
+ }
+
+ if (mode == kCryptoJobAsync) {
+ params->additional_data = additional_data.ToCopy();
+ } else {
+ params->additional_data = additional_data.ToByteSource();
+ }
+ }
+
+ return true;
+}
+} // namespace
+
+ChaCha20Poly1305CipherConfig::ChaCha20Poly1305CipherConfig(
+ ChaCha20Poly1305CipherConfig&& other) noexcept
+ : mode(other.mode),
+ cipher(other.cipher),
+ iv(std::move(other.iv)),
+ additional_data(std::move(other.additional_data)),
+ tag(std::move(other.tag)) {}
+
+ChaCha20Poly1305CipherConfig& ChaCha20Poly1305CipherConfig::operator=(
+ ChaCha20Poly1305CipherConfig&& other) noexcept {
+ if (&other == this) return *this;
+ this->~ChaCha20Poly1305CipherConfig();
+ return *new (this) ChaCha20Poly1305CipherConfig(std::move(other));
+}
+
+void ChaCha20Poly1305CipherConfig::MemoryInfo(MemoryTracker* tracker) const {
+ // If mode is sync, then the data in each of these properties
+ // is not owned by the ChaCha20Poly1305CipherConfig, so we ignore it.
+ if (mode == kCryptoJobAsync) {
+ tracker->TrackFieldWithSize("iv", iv.size());
+ tracker->TrackFieldWithSize("additional_data", additional_data.size());
+ tracker->TrackFieldWithSize("tag", tag.size());
+ }
+}
+
+Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig(
+ CryptoJobMode mode,
+ const FunctionCallbackInfo& args,
+ unsigned int offset,
+ WebCryptoCipherMode cipher_mode,
+ ChaCha20Poly1305CipherConfig* params) {
+ Environment* env = Environment::GetCurrent(args);
+
+ params->mode = mode;
+ params->cipher = ncrypto::Cipher::CHACHA20_POLY1305;
+
+ if (!params->cipher) {
+ THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
+ return Nothing();
+ }
+
+ // IV parameter (required)
+ if (!ValidateIV(env, mode, args[offset], params)) {
+ return Nothing();
+ }
+
+ // Authentication tag parameter (only for decryption) or tag length (for
+ // encryption)
+ if (static_cast(args.Length()) > offset + 1) {
+ if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 1], params)) {
+ return Nothing();
+ }
+ }
+
+ // Additional authenticated data parameter (optional)
+ if (static_cast(args.Length()) > offset + 2) {
+ if (!ValidateAdditionalData(env, mode, args[offset + 2], params)) {
+ return Nothing();
+ }
+ }
+
+ return JustVoid();
+}
+
+WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher(
+ Environment* env,
+ const KeyObjectData& key_data,
+ WebCryptoCipherMode cipher_mode,
+ const ChaCha20Poly1305CipherConfig& params,
+ const ByteSource& in,
+ ByteSource* out) {
+ CHECK_EQ(key_data.GetKeyType(), kKeyTypeSecret);
+
+ // Validate key size
+ if (key_data.GetSymmetricKeySize() != kChaCha20Poly1305KeySize) {
+ return WebCryptoCipherStatus::INVALID_KEY_TYPE;
+ }
+
+ auto ctx = CipherCtxPointer::New();
+ CHECK(ctx);
+
+ const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
+
+ if (!ctx.init(params.cipher, encrypt)) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+
+ if (!ctx.setKeyLength(key_data.GetSymmetricKeySize()) ||
+ !ctx.init(
+ Cipher(),
+ encrypt,
+ reinterpret_cast(key_data.GetSymmetricKey()),
+ params.iv.data())) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+
+ size_t tag_len = 0;
+
+ switch (cipher_mode) {
+ case kWebCryptoCipherDecrypt: {
+ if (params.tag.size() != kChaCha20Poly1305TagSize) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+ if (!ctx.setAeadTag(ncrypto::Buffer{
+ .data = params.tag.data(),
+ .len = params.tag.size(),
+ })) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+ break;
+ }
+ case kWebCryptoCipherEncrypt: {
+ tag_len = kChaCha20Poly1305TagSize;
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ size_t total = 0;
+ int buf_len = in.size() + ctx.getBlockSize() + tag_len;
+ int out_len;
+
+ // Process additional authenticated data if present
+ ncrypto::Buffer buffer = {
+ .data = params.additional_data.data(),
+ .len = params.additional_data.size(),
+ };
+ if (params.additional_data.size() && !ctx.update(buffer, nullptr, &out_len)) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+
+ auto buf = DataPointer::Alloc(buf_len);
+ auto ptr = static_cast(buf.get());
+
+ // Process the input data
+ buffer = {
+ .data = in.data(),
+ .len = in.size(),
+ };
+ if (in.empty()) {
+ if (!ctx.update({}, ptr, &out_len)) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+ } else if (!ctx.update(buffer, ptr, &out_len)) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+
+ total += out_len;
+ CHECK_LE(out_len, buf_len);
+ out_len = ctx.getBlockSize();
+ if (!ctx.update({}, ptr + total, &out_len, true)) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+ total += out_len;
+
+ // If encrypting, grab the generated auth tag and append it to the ciphertext
+ if (encrypt) {
+ if (!ctx.getAeadTag(kChaCha20Poly1305TagSize, ptr + total)) {
+ return WebCryptoCipherStatus::FAILED;
+ }
+ total += kChaCha20Poly1305TagSize;
+ }
+
+ if (total == 0) {
+ *out = ByteSource();
+ return WebCryptoCipherStatus::OK;
+ }
+
+ // Size down to the actual used space
+ buf = buf.resize(total);
+ *out = ByteSource::Allocated(buf.release());
+
+ return WebCryptoCipherStatus::OK;
+}
+
+void ChaCha20Poly1305::Initialize(Environment* env, Local