crypto: add ChaCha20-Poly1305 Web Cryptography algorithm

PR-URL: https://github.com/nodejs/node/pull/59365
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Filip Skokan 2025-08-08 13:02:42 +02:00
parent cec039f786
commit 84aaed7597
No known key found for this signature in database
25 changed files with 1240 additions and 177 deletions

View File

@ -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;

View File

@ -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;

View File

@ -2,6 +2,9 @@
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ChaCha20-Poly1305 algorithm is now supported.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: SHA-3 algorithms are now supported.
@ -98,6 +101,7 @@ WICG proposal:
Algorithms:
* `'ChaCha20-Poly1305'`
* `'cSHAKE128'`
* `'cSHAKE256'`
* `'ML-DSA-44'`[^openssl35]
@ -479,36 +483,37 @@ const decrypted = new TextDecoder().decode(await crypto.subtle.decrypt(
The table 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'` | ✔ | ✔ | ✔ | | ✔ | | | | |
| `'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] | ✔ | ✔ | ✔ | | | ✔ | | | ✔ |
| 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] | ✔ | ✔ | ✔ | | | ✔ | | | ✔ |
## Class: `Crypto`
@ -630,27 +635,28 @@ The possible usages are:
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'` | | | | | | | ✔ | ✔ |
| `'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] | | | | | ✔ | ✔ | | |
| 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] | | | | | ✔ | ✔ | | |
## Class: `CryptoKeyPair`
@ -708,9 +714,13 @@ which can be used to detect whether a given algorithm identifier
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ChaCha20-Poly1305 algorithm is now supported.
-->
* `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.
@ -725,6 +735,7 @@ The algorithms currently supported include:
* `'AES-CBC'`
* `'AES-CTR'`
* `'AES-GCM'`
* `'ChaCha20-Poly1305'`[^modern-algos]
* `'RSA-OAEP'`
### `subtle.deriveBits(algorithm, baseKey[, length])`
@ -854,9 +865,13 @@ whose value is one of the above.
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ChaCha20-Poly1305 algorithm is now supported.
-->
* `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.
@ -871,6 +886,7 @@ The algorithms currently supported include:
* `'AES-CBC'`
* `'AES-CTR'`
* `'AES-GCM'`
* `'ChaCha20-Poly1305'`[^modern-algos]
* `'RSA-OAEP'`
### `subtle.exportKey(format, key)`
@ -878,6 +894,9 @@ The algorithms currently supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ChaCha20-Poly1305 algorithm is now supported.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ML-DSA algorithms are now supported.
@ -909,23 +928,24 @@ 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'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` |
| ---------------------------- | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ |
| `'AES-CBC'` | | | ✔ | ✔ | ✔ | | |
| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | |
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
| `'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'` | ✔ | ✔ | ✔ | | | | |
| 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)`
@ -946,6 +966,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/59365
description: ChaCha20-Poly1305 algorithm is now supported.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ML-DSA algorithms are now supported.
@ -987,6 +1010,7 @@ The {CryptoKey} (secret key) generating algorithms supported include:
* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
* `'ChaCha20-Poly1305'`[^modern-algos]
* `'HMAC'`
### `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)`
@ -994,6 +1018,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/59365
description: ChaCha20-Poly1305 algorithm is now supported.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ML-DSA algorithms are now supported.
@ -1031,27 +1058,28 @@ If importing a `'PBKDF2'` key, `extractable` must be `false`.
The algorithms currently supported include:
| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` |
| ---------------------------- | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ |
| `'AES-CBC'` | | | ✔ | ✔ | ✔ | | |
| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | |
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
| `'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] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
| 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)`
@ -1098,6 +1126,10 @@ The algorithms currently supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ChaCha20-Poly1305 algorithm is now supported.
-->
* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos],
@ -1107,7 +1139,7 @@ added: v15.0.0
<!--lint disable maximum-line-length remark-lint-->
* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams}
* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams}
* `unwrappedKeyAlgo` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams}
<!--lint enable maximum-line-length remark-lint-->
@ -1131,6 +1163,7 @@ The wrapping algorithms currently supported include:
* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
* `'ChaCha20-Poly1305'`[^modern-algos]
* `'RSA-OAEP'`
The unwrapped key algorithms supported include:
@ -1139,6 +1172,7 @@ The unwrapped key algorithms supported include:
* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
* `'ChaCha20-Poly1305'`[^modern-algos]
* `'ECDH'`
* `'ECDSA'`
* `'Ed25519'`
@ -1199,6 +1233,10 @@ The algorithms currently supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59365
description: ChaCha20-Poly1305 algorithm is now supported.
-->
<!--lint disable maximum-line-length remark-lint-->
@ -1207,7 +1245,7 @@ added: v15.0.0
`'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.
<!--lint enable maximum-line-length remark-lint-->
@ -1228,6 +1266,7 @@ The wrapping algorithms currently supported include:
* `'AES-CTR'`
* `'AES-GCM'`
* `'AES-KW'`
* `'ChaCha20-Poly1305'`[^modern-algos]
* `'RSA-OAEP'`
## Algorithm parameters
@ -1250,6 +1289,50 @@ added: v15.0.0
* Type: {string}
### Class: `AeadParams`
<!-- YAML
added: v15.0.0
-->
#### `aeadParams.additionalData`
<!-- YAML
added: v15.0.0
-->
* 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`
<!-- YAML
added: v15.0.0
-->
* Type: {ArrayBuffer|TypedArray|DataView|Buffer}
The initialization vector must be unique for every encryption operation using a
given key.
#### `aeadParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'AES-GCM'` or `'ChaCha20-Poly1305'`.
#### `aeadParams.tagLength`
<!-- YAML
added: v15.0.0
-->
* Type: {number} The size in bits of the generated authentication tag.
### Class: `AesDerivedKeyParams`
<!-- YAML
@ -1337,59 +1420,6 @@ added: v15.0.0
* Type: {string} Must be `'AES-CTR'`.
### Class: `AesGcmParams`
<!-- YAML
added: v15.0.0
-->
#### `aesGcmParams.additionalData`
<!-- YAML
added: v15.0.0
-->
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
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.
#### `aesGcmParams.iv`
<!-- YAML
added: v15.0.0
-->
* Type: {ArrayBuffer|TypedArray|DataView|Buffer}
The initialization vector must be unique for every encryption operation using a
given key.
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`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'AES-GCM'`.
#### `aesGcmParams.tagLength`
<!-- YAML
added: v15.0.0
-->
* 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`.
### Class: `AesKeyAlgorithm`
<!-- YAML
@ -2153,7 +2183,6 @@ The length (in bytes) of the random salt to use.
[JSON Web Key]: https://tools.ietf.org/html/rfc7517
[Key usages]: #cryptokeyusages
[Modern Algorithms in the Web Cryptography API]: #modern-algorithms-in-the-web-cryptography-api
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
[Secure Curves in the Web Cryptography API]: #secure-curves-in-the-web-cryptography-api
[Web Crypto API]: https://www.w3.org/TR/WebCryptoAPI/

View File

@ -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,
};

View File

@ -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':

View File

@ -245,13 +245,13 @@ const kSupportedAlgorithms = {
'encrypt': {
'RSA-OAEP': 'RsaOaepParams',
'AES-CBC': 'AesCbcParams',
'AES-GCM': 'AesGcmParams',
'AES-GCM': 'AeadParams',
'AES-CTR': 'AesCtrParams',
},
'decrypt': {
'RSA-OAEP': 'RsaOaepParams',
'AES-CBC': 'AesCbcParams',
'AES-GCM': 'AesGcmParams',
'AES-GCM': 'AeadParams',
'AES-CTR': 'AesCtrParams',
},
'get key length': {
@ -290,6 +290,14 @@ const experimentalAlgorithms = ObjectEntries({
'SHA3-256': { digest: null },
'SHA3-384': { digest: null },
'SHA3-512': { digest: null },
'ChaCha20-Poly1305': {
'encrypt': 'AeadParams',
'decrypt': 'AeadParams',
'generateKey': null,
'importKey': null,
'exportKey': null,
'get key length': null,
},
});
for (const { 0: algorithm, 1: nid } of [
@ -325,7 +333,7 @@ for (let i = 0; i < experimentalAlgorithms.length; i++) {
}
const simpleAlgorithmDictionaries = {
AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
EcKeyGenParams: {},
HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' },

View File

@ -155,6 +155,11 @@ 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':
@ -252,6 +257,8 @@ function getKeyLength({ name, length, hash }) {
case 'HKDF':
case 'PBKDF2':
return null;
case 'ChaCha20-Poly1305':
return 256;
}
}
@ -431,7 +438,7 @@ async function exportKeyRawSeed(key) {
}
}
async function exportKeyRawSecret(key) {
async function exportKeyRawSecret(key, format) {
switch (key.algorithm.name) {
case 'AES-CTR':
// Fall through
@ -443,6 +450,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;
}
@ -504,6 +516,9 @@ async function exportKeyJWK(key) {
parameters.alg = require('internal/crypto/aes')
.getAlgorithmName(key.algorithm.name, key.algorithm.length);
break;
case 'ChaCha20-Poly1305':
parameters.alg = 'C20P';
break;
case 'HMAC': {
const alg = normalizeHashName(
key.algorithm.hash.name,
@ -563,7 +578,7 @@ async function exportKey(format, key) {
}
case 'raw-secret': {
if (key.type === 'secret') {
result = await exportKeyRawSecret(key);
result = await exportKeyRawSecret(key, format);
}
break;
}
@ -581,7 +596,7 @@ async function exportKey(format, key) {
}
case 'raw': {
if (key.type === 'secret') {
result = await exportKeyRawSecret(key);
result = await exportKeyRawSecret(key, format);
} else if (key.type === 'public') {
result = await exportKeyRawPublic(key, format);
}
@ -687,6 +702,10 @@ async function importKey(
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':
@ -975,6 +994,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')

View File

@ -23,6 +23,8 @@ const {
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
String,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetSymbolToStringTag,
} = primordials;
@ -195,7 +197,7 @@ const isNonSharedArrayBuffer = isArrayBuffer;
function ensureSHA(V, label) {
if (
typeof V === 'string' ?
!V.toLowerCase().startsWith('sha') :
!StringPrototypeStartsWith(StringPrototypeToLowerCase(V), 'sha') :
V.name?.toLowerCase?.().startsWith('sha') === false
)
throw lazyDOMException(
@ -679,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,
},
{
@ -693,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;
}
},
},
@ -744,9 +766,9 @@ 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,

View File

@ -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',

View File

@ -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 <openssl/evp.h>
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> value,
ChaCha20Poly1305CipherConfig* params) {
ArrayBufferOrViewContents<unsigned char> 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> 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<unsigned char> 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<v8::Uint32>()->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> value,
ChaCha20Poly1305CipherConfig* params) {
if (IsAnyBufferSource(value)) {
ArrayBufferOrViewContents<unsigned char> 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<void> ChaCha20Poly1305CipherTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& 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<void>();
}
// IV parameter (required)
if (!ValidateIV(env, mode, args[offset], params)) {
return Nothing<void>();
}
// Authentication tag parameter (only for decryption) or tag length (for
// encryption)
if (static_cast<unsigned int>(args.Length()) > offset + 1) {
if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 1], params)) {
return Nothing<void>();
}
}
// Additional authenticated data parameter (optional)
if (static_cast<unsigned int>(args.Length()) > offset + 2) {
if (!ValidateAdditionalData(env, mode, args[offset + 2], params)) {
return Nothing<void>();
}
}
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<const unsigned char*>(key_data.GetSymmetricKey()),
params.iv.data<unsigned char>())) {
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<const char>{
.data = params.tag.data<char>(),
.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<const unsigned char> buffer = {
.data = params.additional_data.data<unsigned char>(),
.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<unsigned char*>(buf.get());
// Process the input data
buffer = {
.data = in.data<unsigned char>(),
.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<Object> target) {
ChaCha20Poly1305CryptoJob::Initialize(env, target);
}
void ChaCha20Poly1305::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
ChaCha20Poly1305CryptoJob::RegisterExternalReferences(registry);
}
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,64 @@
#ifndef SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_
#define SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "v8.h"
namespace node::crypto {
constexpr unsigned kChaCha20Poly1305AuthTagLength = 16;
struct ChaCha20Poly1305CipherConfig final : public MemoryRetainer {
CryptoJobMode mode;
ncrypto::Cipher cipher;
ByteSource iv;
ByteSource additional_data;
ByteSource tag;
ChaCha20Poly1305CipherConfig() = default;
ChaCha20Poly1305CipherConfig(ChaCha20Poly1305CipherConfig&& other) noexcept;
ChaCha20Poly1305CipherConfig& operator=(
ChaCha20Poly1305CipherConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ChaCha20Poly1305CipherConfig)
SET_SELF_SIZE(ChaCha20Poly1305CipherConfig)
};
struct ChaCha20Poly1305CipherTraits final {
static constexpr const char* JobName = "ChaCha20Poly1305CipherJob";
using AdditionalParameters = ChaCha20Poly1305CipherConfig;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
ChaCha20Poly1305CipherConfig* config);
static WebCryptoCipherStatus DoCipher(
Environment* env,
const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const ChaCha20Poly1305CipherConfig& params,
const ByteSource& in,
ByteSource* out);
};
using ChaCha20Poly1305CryptoJob = CipherJob<ChaCha20Poly1305CipherTraits>;
namespace ChaCha20Poly1305 {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace ChaCha20Poly1305
} // namespace node::crypto
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_

View File

@ -38,6 +38,7 @@ namespace crypto {
#define CRYPTO_NAMESPACE_LIST_BASE(V) \
V(AES) \
V(ChaCha20Poly1305) \
V(CipherBase) \
V(DiffieHellman) \
V(DSAAlg) \

View File

@ -30,6 +30,7 @@
// code should include the relevant src/crypto headers directly.
#include "crypto/crypto_aes.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_chacha20_poly1305.h"
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_context.h"
#include "crypto/crypto_dh.h"

View File

@ -0,0 +1,102 @@
'use strict';
module.exports = function() {
const kPlaintext =
Buffer.from('546869732073706563696669636174696f6e206465736372696265' +
'732061204a6176615363726970742041504920666f722070657266' +
'6f726d696e672062617369632063727970746f6772617068696320' +
'6f7065726174696f6e7320696e20776562206170706c6963617469' +
'6f6e732c20737563682061732068617368696e672c207369676e61' +
'747572652067656e65726174696f6e20616e642076657269666963' +
'6174696f6e2c20616e6420656e6372797074696f6e20616e642064' +
'656372797074696f6e2e204164646974696f6e616c6c792c206974' +
'2064657363726962657320616e2041504920666f72206170706c69' +
'636174696f6e7320746f2067656e657261746520616e642f6f7220' +
'6d616e61676520746865206b6579696e67206d6174657269616c20' +
'6e656365737361727920746f20706572666f726d20746865736520' +
'6f7065726174696f6e732e205573657320666f7220746869732041' +
'50492072616e67652066726f6d2075736572206f72207365727669' +
'63652061757468656e7469636174696f6e2c20646f63756d656e74' +
'206f7220636f6465207369676e696e672c20616e64207468652063' +
'6f6e666964656e7469616c69747920616e6420696e746567726974' +
'79206f6620636f6d6d756e69636174696f6e732e', 'hex');
const kKeyBytes = Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' +
'3eb7b74d8000bbf30', 'hex')
const iv = Buffer.from('3a92732aa6ea39bf3986e0c73', 'hex');
const additionalData = Buffer.from(
'5468657265206172652037206675727468657220656469746f72696' +
'16c206e6f74657320696e2074686520646f63756d656e742e', 'hex');
const tag = Buffer.from('87d611a2b8012f4eb792ccdee7998d22', 'hex')
const tag_with_empty_ad = Buffer.from('2ba3e8380c1f49f10665fd15a4ac599e', 'hex')
const kCiphertext = Buffer.from(
'01e15951ce23d7672df9d13f19c54ff5b3fe17114eb637ec25c1a8ac2' +
'24eebe154b3a1206187e18abd31d022b1a66551fbbf0ae2d9fa4e9ab4' +
'a680b185528000a7654731f05f405ce164cfc904d1759afa758ac459f' +
'e26420fccac9692af9259243f53e7e0f42d56c6a4b4827056ca76bc9d' +
'e92577a9f405810fd1e4cb7289f7d528772bde654fef456f031b87802' +
'6616df7349bef4fdff4a52953afadddbd61a3bd1a43815daf1b1ab962' +
'8aaeaee52866466dcb45650b489b2226a01da24d85c20af24e2beb790' +
'233081c5651258cf77e5c47e87ac070aeaa470d13b28b4df82729c350' +
'3cd80a65ac50a8d7a10dabe29ac696410b70209064c3b698343f97f5a' +
'38d63265504ee0922cf5a7c03fe0f3ac1fce28f8eed0153d2f6c500ef' +
'68c71e56e3f1abbfc194be4dd75b73983c3e7c0c68555b71eb4695110' +
'bb8cd8f495ce7c1e4512c72fca23a095897a9a0dfd584abc3e949cf3e' +
'0fa1d855284d74a915b6e7455e0307985a356c01878700b21c6e0afac' +
'ee72021a81c3164193e0126d5b841018da2c7c9aa0afb8cd746b378e3' +
'04590eb8b0428b4409b7bcb0cb4a5e9072bb693f011edbe9ab6c5e0c5' +
'ca51f344fb29034cdbe78b3b66d23467a75e5d28f7e7c92e4e7246ba0' +
'db7aa408efa3b33e57a4d67fda86d346fc690f07981631', 'hex');
const kTagLengths = [128];
const passing = [];
kTagLengths.forEach((tagLength) => {
const byteCount = tagLength / 8;
const result =
new Uint8Array(kCiphertext.byteLength + byteCount);
result.set(kCiphertext, 0);
result.set(tag.slice(0, byteCount),
kCiphertext.byteLength);
passing.push({
keyBuffer: kKeyBytes,
algorithm: { name: 'ChaCha20-Poly1305', iv, additionalData, tagLength },
plaintext: kPlaintext,
result
});
const noadresult =
new Uint8Array(kCiphertext.byteLength + byteCount);
noadresult.set(kCiphertext, 0);
noadresult.set(tag_with_empty_ad.slice(0, byteCount),
kCiphertext.byteLength);
passing.push({
keyBuffer: kKeyBytes,
algorithm: { name: 'ChaCha20-Poly1305', iv, tagLength },
plaintext: kPlaintext,
result: noadresult
});
});
const failing = [];
[24, 48, 72, 95, 129].forEach((badTagLength) => {
failing.push({
keyBuffer: kKeyBytes,
algorithm: {
name: 'ChaCha20-Poly1305',
iv,
additionalData,
tagLength: badTagLength
},
plaintext: kPlaintext,
result: kCiphertext
});
});
return { passing, failing, decryptionFailing: [] };
};

View File

@ -12,16 +12,19 @@ export const vectors = {
[pqc, 'ML-DSA-44'],
[pqc, 'ML-DSA-65'],
[pqc, 'ML-DSA-87'],
[true, 'ChaCha20-Poly1305'],
],
'importKey': [
[pqc, 'ML-DSA-44'],
[pqc, 'ML-DSA-65'],
[pqc, 'ML-DSA-87'],
[true, 'ChaCha20-Poly1305'],
],
'exportKey': [
[pqc, 'ML-DSA-44'],
[pqc, 'ML-DSA-65'],
[pqc, 'ML-DSA-87'],
[true, 'ChaCha20-Poly1305'],
],
'getPublicKey': [
[true, 'RSA-OAEP'],
@ -38,5 +41,13 @@ export const vectors = {
[false, 'AES-CBC'],
[false, 'AES-GCM'],
[false, 'AES-KW'],
[false, 'ChaCha20-Poly1305'],
],
'encrypt': [
[true, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }],
[false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(16) }],
[true, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }],
[false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 64 }],
[false, 'ChaCha20-Poly1305'],
]
};

View File

@ -25,13 +25,16 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) {
{
for (const length of [128, 192, 256]) {
const aes = createSecretKey(randomBytes(length >> 3));
for (const algorithm of ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']) {
const key = createSecretKey(randomBytes(length >> 3));
const algorithms = ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW'];
if (length === 256)
algorithms.push('ChaCha20-Poly1305');
for (const algorithm of algorithms) {
const usages = algorithm === 'AES-KW' ? ['wrapKey', 'unwrapKey'] : ['encrypt', 'decrypt'];
for (const extractable of [true, false]) {
const cryptoKey = aes.toCryptoKey(algorithm, extractable, usages);
assertCryptoKey(cryptoKey, aes, algorithm, extractable, usages);
assert.strictEqual(cryptoKey.algorithm.length, length);
const cryptoKey = key.toCryptoKey(algorithm, extractable, usages);
assertCryptoKey(cryptoKey, key, algorithm, extractable, usages);
assert.strictEqual(cryptoKey.algorithm.length, algorithm !== 'ChaCha20-Poly1305' ? length : undefined);
}
}
}

View File

@ -182,7 +182,7 @@ async function prepareKeys() {
},
keys.X448.privateKey,
8 * keys.X448.size),
{ message: 'algorithm.public must be an X448 key' });
{ message: 'key algorithm mismatch' });
}
{

View File

@ -218,7 +218,7 @@ async function prepareKeys() {
name: 'ECDH',
public: publicKey
}, keys['P-521'].privateKey, null), {
message: 'algorithm.public must be an ECDH key'
message: 'key algorithm mismatch'
});
}

View File

@ -136,7 +136,7 @@ async function prepareKeys() {
},
keys.X448.privateKey,
...otherArgs),
{ message: 'algorithm.public must be an X448 key' });
{ message: 'key algorithm mismatch' });
}
{

View File

@ -174,7 +174,7 @@ async function prepareKeys() {
},
keys['P-521'].privateKey,
...otherArgs),
{ message: 'algorithm.public must be an ECDH key' });
{ message: 'key algorithm mismatch' });
}
{

View File

@ -0,0 +1,255 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
if (process.features.openssl_is_boringssl)
common.skip('Skipping unsupported ChaCha20-Poly1305 test case');
const assert = require('assert');
const { subtle } = globalThis.crypto;
async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) {
// Using a copy of plaintext to prevent tampering of the original
plaintext = Buffer.from(plaintext);
const key = await subtle.importKey(
'raw-secret',
keyBuffer,
{ name: algorithm.name },
false,
['encrypt', 'decrypt']);
const output = await subtle.encrypt(algorithm, key, plaintext);
plaintext[0] = 255 - plaintext[0];
assert.strictEqual(
Buffer.from(output).toString('hex'),
Buffer.from(result).toString('hex'));
// Converting the returned ArrayBuffer into a Buffer right away,
// so that the next line works
const check = Buffer.from(await subtle.decrypt(algorithm, key, output));
check[0] = 255 - check[0];
assert.strictEqual(
Buffer.from(check).toString('hex'),
Buffer.from(plaintext).toString('hex'));
}
async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) {
const key = await subtle.importKey(
'raw-secret',
keyBuffer,
{ name: algorithm.name },
false,
['decrypt']);
return assert.rejects(subtle.encrypt(algorithm, key, plaintext), {
message: /The requested operation is not valid for the provided key/
});
}
async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) {
const key = await subtle.importKey(
'raw-secret',
keyBuffer,
{ name: algorithm.name },
false,
['encrypt']);
const output = await subtle.encrypt(algorithm, key, plaintext);
return assert.rejects(subtle.decrypt(algorithm, key, output), {
message: /The requested operation is not valid for the provided key/
});
}
async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) {
assert.notStrictEqual(algorithm.name, alg);
const key = await subtle.importKey(
'raw-secret',
keyBuffer,
{ name: alg },
false,
['encrypt']);
return assert.rejects(subtle.encrypt(algorithm, key, plaintext), {
message: /The requested operation is not valid for the provided key/
});
}
async function testDecrypt({ keyBuffer, algorithm, result }) {
const key = await subtle.importKey(
'raw-secret',
keyBuffer,
{ name: algorithm.name },
false,
['encrypt', 'decrypt']);
await subtle.decrypt(algorithm, key, result);
}
{
const {
passing,
failing,
decryptionFailing
} = require('../fixtures/crypto/chacha20_poly1305')();
(async function() {
const variations = [];
passing.forEach((vector) => {
variations.push(testEncrypt(vector));
variations.push(testEncryptNoEncrypt(vector));
variations.push(testEncryptNoDecrypt(vector));
variations.push(testEncryptWrongAlg(vector, 'AES-GCM'));
});
failing.forEach((vector) => {
variations.push(assert.rejects(testEncrypt(vector), {
message: /is not a valid ChaCha20-Poly1305 tag length/
}));
variations.push(assert.rejects(testDecrypt(vector), {
message: /is not a valid ChaCha20-Poly1305 tag length/
}));
});
decryptionFailing.forEach((vector) => {
variations.push(assert.rejects(testDecrypt(vector), {
name: 'OperationError'
}));
});
await Promise.all(variations);
})().then(common.mustCall());
}
{
(async function() {
const secretKey = await subtle.generateKey(
{
name: 'ChaCha20-Poly1305',
},
false,
['encrypt', 'decrypt'],
);
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
const aad = globalThis.crypto.getRandomValues(new Uint8Array(32));
const encrypted = await subtle.encrypt(
{
name: 'ChaCha20-Poly1305',
iv,
additionalData: aad,
},
secretKey,
globalThis.crypto.getRandomValues(new Uint8Array(32))
);
await subtle.decrypt(
{
name: 'ChaCha20-Poly1305',
iv,
additionalData: aad,
},
secretKey,
new Uint8Array(encrypted),
);
})().then(common.mustCall());
}
{
async function testRejectsImportKey(format, keyData, algorithm, extractable, usages, expectedError) {
await assert.rejects(
subtle.importKey(format, keyData, algorithm, extractable, usages),
expectedError
);
}
async function testRejectsGenerateKey(algorithm, extractable, usages, expectedError) {
await assert.rejects(
subtle.generateKey(algorithm, extractable, usages),
expectedError
);
}
(async function() {
const baseJwk = { kty: 'oct', k: 'AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA' };
const alg = { name: 'ChaCha20-Poly1305' };
const keyData32 = globalThis.crypto.getRandomValues(new Uint8Array(32));
// Test decrypt with data too small
const secretKey = await subtle.generateKey(alg, false, ['encrypt', 'decrypt']);
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
await assert.rejects(
subtle.decrypt({ name: 'ChaCha20-Poly1305', iv }, secretKey, new Uint8Array(8)),
{ name: 'OperationError', message: /The provided data is too small/ }
);
// Test invalid tagLength values
await assert.rejects(
subtle.encrypt({ name: 'ChaCha20-Poly1305', iv, tagLength: 64 }, secretKey, keyData32),
{ name: 'OperationError', message: /is not a valid ChaCha20-Poly1305 tag length/ }
);
await assert.rejects(
subtle.encrypt({ name: 'ChaCha20-Poly1305', iv, tagLength: 96 }, secretKey, keyData32),
{ name: 'OperationError', message: /is not a valid ChaCha20-Poly1305 tag length/ }
);
// JWK error conditions
const jwkTests = [
[{ k: baseJwk.k }, /Invalid keyData/],
[{ ...baseJwk, kty: 'RSA' }, /Invalid JWK "kty" Parameter/],
[{ ...baseJwk, use: 'sig' }, /Invalid JWK "use" Parameter/],
[{ ...baseJwk, ext: false }, /JWK "ext" Parameter and extractable mismatch/, true],
[{ ...baseJwk, alg: 'A256GCM' }, /JWK "alg" does not match the requested algorithm/],
[{ ...baseJwk, key_ops: ['sign'] }, /Key operations and usage mismatch|Unsupported key usage/],
[{ ...baseJwk, key_ops: ['encrypt'] }, /Key operations and usage mismatch/, false, ['decrypt']],
];
for (const [jwk, errorPattern, extractable = false, usages = ['encrypt']] of jwkTests) {
await testRejectsImportKey('jwk', jwk, alg, extractable, usages,
{ name: 'DataError', message: errorPattern });
}
// Valid JWK imports
const validKeys = await Promise.all([
subtle.importKey('jwk', { ...baseJwk, alg: 'C20P' }, alg, false, ['encrypt']),
subtle.importKey('jwk', { ...baseJwk, use: 'enc' }, alg, false, ['encrypt']),
]);
validKeys.forEach((key) => assert.strictEqual(key.algorithm.name, 'ChaCha20-Poly1305'));
// Invalid key usages
const usageTests = [
[['sign'], 'generateKey'],
[['verify'], 'importKey'],
];
for (const [usages, method] of usageTests) {
const fn = method === 'generateKey' ?
() => testRejectsGenerateKey(alg, false, usages, { name: 'SyntaxError', message: /Unsupported key usage/ }) :
() => testRejectsImportKey('raw-secret', keyData32, alg, false, usages, { name: 'SyntaxError', message: /Unsupported key usage/ });
await fn();
}
// Valid wrapKey/unwrapKey usage
const wrapKey = await subtle.importKey('raw-secret', keyData32, alg, false, ['wrapKey', 'unwrapKey']);
assert.strictEqual(wrapKey.algorithm.name, 'ChaCha20-Poly1305');
// Invalid key lengths
for (const size of [16, 64]) {
await testRejectsImportKey('raw-secret', new Uint8Array(size), alg, false, ['encrypt'],
{ name: 'DataError', message: /Invalid key length/ });
}
// Invalid JWK keyData
await testRejectsImportKey('jwk', { ...baseJwk, k: 'invalid-base64-!@#$%^&*()' }, alg, false, ['encrypt'],
{ name: 'DataError' });
})().then(common.mustCall());
}

View File

@ -59,6 +59,15 @@ const vectors = {
'unwrapKey',
],
},
'ChaCha20-Poly1305': {
result: 'CryptoKey',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
},
'AES-KW': {
algorithm: { length: 256 },
result: 'CryptoKey',

View File

@ -462,19 +462,19 @@ const opts = { prefix, context };
});
}
// AesGcmParams
// AeadParams
{
for (const good of [
{ name: 'AES-GCM', iv: Buffer.alloc(0) },
{ name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 64 },
{ name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 64, additionalData: Buffer.alloc(0) },
]) {
assert.deepStrictEqual(converters.AesGcmParams({ ...good, filtered: 'out' }, opts), good);
assert.deepStrictEqual(converters.AeadParams({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.AesGcmParams({ ...good, iv: undefined }, opts), {
assert.throws(() => converters.AeadParams({ ...good, iv: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'AesGcmParams' because 'iv' is required in 'AesGcmParams'.`,
message: `${prefix}: ${context} can not be converted to 'AeadParams' because 'iv' is required in 'AeadParams'.`,
});
}
}

View File

@ -39,6 +39,14 @@ const kWrappingData = {
},
pair: false
},
'ChaCha20-Poly1305': {
wrap: {
iv: new Uint8Array(12),
additionalData: new Uint8Array(16),
tagLength: 128
},
pair: false
},
'AES-KW': {
generate: { length: 128 },
wrap: { },
@ -170,6 +178,13 @@ async function generateKeysToWrap() {
usages: ['encrypt', 'decrypt'],
pair: false,
},
{
algorithm: {
name: 'ChaCha20-Poly1305'
},
usages: ['encrypt', 'decrypt'],
pair: false,
},
{
algorithm: {
name: 'AES-KW',
@ -235,6 +250,7 @@ async function generateKeysToWrap() {
function getFormats(key) {
switch (key.type) {
case 'secret': {
if (key.algorithm.name === 'ChaCha20-Poly1305') return ['raw-secret', 'jwk'];
return ['raw-secret', 'raw', 'jwk'];
};
case 'public': {

View File

@ -98,7 +98,7 @@ const customTypesMap = {
'AesCtrParams': 'webcrypto.html#class-aesctrparams',
'AesCbcParams': 'webcrypto.html#class-aescbcparams',
'AesDerivedKeyParams': 'webcrypto.html#class-aesderivedkeyparams',
'AesGcmParams': 'webcrypto.html#class-aesgcmparams',
'AeadParams': 'webcrypto.html#class-aeadparams',
'EcdhKeyDeriveParams': 'webcrypto.html#class-ecdhkeyderiveparams',
'HkdfParams': 'webcrypto.html#class-hkdfparams',
'KeyAlgorithm': 'webcrypto.html#class-keyalgorithm',