mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
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:
parent
cec039f786
commit
84aaed7597
1
deps/ncrypto/ncrypto.cc
vendored
1
deps/ncrypto/ncrypto.cc
vendored
|
|
@ -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;
|
||||
|
|
|
|||
1
deps/ncrypto/ncrypto.h
vendored
1
deps/ncrypto/ncrypto.h
vendored
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -480,11 +484,12 @@ 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'` | ✔ | ✔ | ✔ | | ✔ | | | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ | ✔ | | | | |
|
||||
| `'cSHAKE128'`[^modern-algos] | | | | | | | | ✔ | |
|
||||
| `'cSHAKE256'`[^modern-algos] | | | | | | | | ✔ | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | | | ✔ | | | ✔ |
|
||||
|
|
@ -631,11 +636,12 @@ Valid key usages depend on the key algorithm (identified by
|
|||
`cryptokey.algorithm.name`).
|
||||
|
||||
| Supported Key Algorithm | `'encrypt'` | `'decrypt'` | `'sign'` | `'verify'` | `'deriveKey'` | `'deriveBits'` | `'wrapKey'` | `'unwrapKey'` |
|
||||
| ---------------------------- | ----------- | ----------- | -------- | ---------- | ------------- | -------------- | ----------- | ------------- |
|
||||
| ------------------------------------ | ----------- | ----------- | -------- | ---------- | ------------- | -------------- | ----------- | ------------- |
|
||||
| `'AES-CBC'` | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'AES-CTR'` | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'AES-GCM'` | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'AES-KW'` | | | | | | | ✔ | ✔ |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | | | | | ✔ | ✔ |
|
||||
| `'ECDH'` | | | | | ✔ | ✔ | | |
|
||||
| `'ECDSA'` | | | ✔ | ✔ | | | | |
|
||||
| `'Ed25519'` | | | ✔ | ✔ | | | | |
|
||||
|
|
@ -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.
|
||||
|
|
@ -910,11 +929,12 @@ 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'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
|
|
@ -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.
|
||||
|
|
@ -1032,11 +1059,12 @@ 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'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
|
|
@ -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/
|
||||
|
|
|
|||
191
lib/internal/crypto/chacha20_poly1305.js
Normal file
191
lib/internal/crypto/chacha20_poly1305.js
Normal 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,
|
||||
};
|
||||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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,11 +704,22 @@ converters.AesGcmParams = createDictionaryConverter(
|
|||
converter: (V, opts) =>
|
||||
converters.octet(V, { ...opts, enforceRange: true }),
|
||||
validator: (V, dict) => {
|
||||
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,
|
||||
|
|
|
|||
1
node.gyp
1
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',
|
||||
|
|
|
|||
322
src/crypto/crypto_chacha20_poly1305.cc
Normal file
322
src/crypto/crypto_chacha20_poly1305.cc
Normal 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
|
||||
64
src/crypto/crypto_chacha20_poly1305.h
Normal file
64
src/crypto/crypto_chacha20_poly1305.h
Normal 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_
|
||||
|
|
@ -38,6 +38,7 @@ namespace crypto {
|
|||
|
||||
#define CRYPTO_NAMESPACE_LIST_BASE(V) \
|
||||
V(AES) \
|
||||
V(ChaCha20Poly1305) \
|
||||
V(CipherBase) \
|
||||
V(DiffieHellman) \
|
||||
V(DSAAlg) \
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
102
test/fixtures/crypto/chacha20_poly1305.js
vendored
Normal file
102
test/fixtures/crypto/chacha20_poly1305.js
vendored
Normal 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: [] };
|
||||
};
|
||||
|
|
@ -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'],
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' });
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ async function prepareKeys() {
|
|||
},
|
||||
keys.X448.privateKey,
|
||||
...otherArgs),
|
||||
{ message: 'algorithm.public must be an X448 key' });
|
||||
{ message: 'key algorithm mismatch' });
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ async function prepareKeys() {
|
|||
},
|
||||
keys['P-521'].privateKey,
|
||||
...otherArgs),
|
||||
{ message: 'algorithm.public must be an ECDH key' });
|
||||
{ message: 'key algorithm mismatch' });
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -59,6 +59,15 @@ const vectors = {
|
|||
'unwrapKey',
|
||||
],
|
||||
},
|
||||
'ChaCha20-Poly1305': {
|
||||
result: 'CryptoKey',
|
||||
usages: [
|
||||
'encrypt',
|
||||
'decrypt',
|
||||
'wrapKey',
|
||||
'unwrapKey',
|
||||
],
|
||||
},
|
||||
'AES-KW': {
|
||||
algorithm: { length: 256 },
|
||||
result: 'CryptoKey',
|
||||
|
|
|
|||
|
|
@ -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'.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user