mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
crypto: add Argon2 Web Cryptography algorithms
PR-URL: https://github.com/nodejs/node/pull/59544 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
This commit is contained in:
parent
d00228f5ca
commit
27e2d81617
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
changes:
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/59544
|
||||||
|
description: Argon2 algorithms are now supported.
|
||||||
- version: REPLACEME
|
- version: REPLACEME
|
||||||
pr-url: https://github.com/nodejs/node/pull/59539
|
pr-url: https://github.com/nodejs/node/pull/59539
|
||||||
description: AES-OCB algorithm is now supported.
|
description: AES-OCB algorithm is now supported.
|
||||||
|
|
@ -108,15 +111,18 @@ WICG proposal:
|
||||||
Algorithms:
|
Algorithms:
|
||||||
|
|
||||||
* `'AES-OCB'`[^openssl30]
|
* `'AES-OCB'`[^openssl30]
|
||||||
|
* `'Argon2d'`[^openssl32]
|
||||||
|
* `'Argon2i'`[^openssl32]
|
||||||
|
* `'Argon2id'`[^openssl32]
|
||||||
* `'ChaCha20-Poly1305'`
|
* `'ChaCha20-Poly1305'`
|
||||||
* `'cSHAKE128'`
|
* `'cSHAKE128'`
|
||||||
* `'cSHAKE256'`
|
* `'cSHAKE256'`
|
||||||
* `'ML-DSA-44'`[^openssl35]
|
* `'ML-DSA-44'`[^openssl35]
|
||||||
* `'ML-DSA-65'`[^openssl35]
|
* `'ML-DSA-65'`[^openssl35]
|
||||||
* `'ML-DSA-87'`[^openssl35]
|
* `'ML-DSA-87'`[^openssl35]
|
||||||
* `'ML-KEM-1024'`[^openssl35]
|
|
||||||
* `'ML-KEM-512'`[^openssl35]
|
* `'ML-KEM-512'`[^openssl35]
|
||||||
* `'ML-KEM-768'`[^openssl35]
|
* `'ML-KEM-768'`[^openssl35]
|
||||||
|
* `'ML-KEM-1024'`[^openssl35]
|
||||||
* `'SHA3-256'`
|
* `'SHA3-256'`
|
||||||
* `'SHA3-384'`
|
* `'SHA3-384'`
|
||||||
* `'SHA3-512'`
|
* `'SHA3-512'`
|
||||||
|
|
@ -506,6 +512,9 @@ implementation and the APIs supported for each:
|
||||||
| `'AES-GCM'` | ✔ | ✔ | ✔ | |
|
| `'AES-GCM'` | ✔ | ✔ | ✔ | |
|
||||||
| `'AES-KW'` | ✔ | ✔ | ✔ | |
|
| `'AES-KW'` | ✔ | ✔ | ✔ | |
|
||||||
| `'AES-OCB'` | ✔ | ✔ | ✔ | |
|
| `'AES-OCB'` | ✔ | ✔ | ✔ | |
|
||||||
|
| `'Argon2d'` | | | ✔ | |
|
||||||
|
| `'Argon2i'` | | | ✔ | |
|
||||||
|
| `'Argon2id'` | | | ✔ | |
|
||||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | ✔ | |
|
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | ✔ | |
|
||||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
|
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
|
||||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
|
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
|
||||||
|
|
@ -545,6 +554,9 @@ implementation and the APIs supported for each:
|
||||||
| `'AES-GCM'` | ✔ | | | ✔ | | |
|
| `'AES-GCM'` | ✔ | | | ✔ | | |
|
||||||
| `'AES-KW'` | | | | ✔ | | |
|
| `'AES-KW'` | | | | ✔ | | |
|
||||||
| `'AES-OCB'` | ✔ | | | ✔ | | |
|
| `'AES-OCB'` | ✔ | | | ✔ | | |
|
||||||
|
| `'Argon2d'` | | | ✔ | | | |
|
||||||
|
| `'Argon2i'` | | | ✔ | | | |
|
||||||
|
| `'Argon2id'` | | | ✔ | | | |
|
||||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | | |
|
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | | |
|
||||||
| `'cSHAKE128'`[^modern-algos] | | | | | | ✔ |
|
| `'cSHAKE128'`[^modern-algos] | | | | | | ✔ |
|
||||||
| `'cSHAKE256'`[^modern-algos] | | | | | | ✔ |
|
| `'cSHAKE256'`[^modern-algos] | | | | | | ✔ |
|
||||||
|
|
@ -714,6 +726,9 @@ Valid key usages depend on the key algorithm (identified by
|
||||||
| `'AES-GCM'` | ✔ | | | ✔ | |
|
| `'AES-GCM'` | ✔ | | | ✔ | |
|
||||||
| `'AES-KW'` | | | | ✔ | |
|
| `'AES-KW'` | | | | ✔ | |
|
||||||
| `'AES-OCB'` | ✔ | | | ✔ | |
|
| `'AES-OCB'` | ✔ | | | ✔ | |
|
||||||
|
| `'Argon2d'` | | | ✔ | | |
|
||||||
|
| `'Argon2i'` | | | ✔ | | |
|
||||||
|
| `'Argon2id'` | | | ✔ | | |
|
||||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | |
|
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | |
|
||||||
| `'ECDH'` | | | ✔ | | |
|
| `'ECDH'` | | | ✔ | | |
|
||||||
| `'ECDSA'` | | ✔ | | | |
|
| `'ECDSA'` | | ✔ | | | |
|
||||||
|
|
@ -864,6 +879,9 @@ The algorithms currently supported include:
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v15.0.0
|
added: v15.0.0
|
||||||
changes:
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/59544
|
||||||
|
description: Argon2 algorithms are now supported.
|
||||||
- version:
|
- version:
|
||||||
- v22.5.0
|
- v22.5.0
|
||||||
- v20.17.0
|
- v20.17.0
|
||||||
|
|
@ -880,7 +898,7 @@ changes:
|
||||||
|
|
||||||
<!--lint disable maximum-line-length remark-lint-->
|
<!--lint disable maximum-line-length remark-lint-->
|
||||||
|
|
||||||
* `algorithm` {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params}
|
* `algorithm` {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|Argon2Params}
|
||||||
* `baseKey` {CryptoKey}
|
* `baseKey` {CryptoKey}
|
||||||
* `length` {number|null} **Default:** `null`
|
* `length` {number|null} **Default:** `null`
|
||||||
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
|
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
|
||||||
|
|
@ -900,6 +918,9 @@ containing the generated data.
|
||||||
|
|
||||||
The algorithms currently supported include:
|
The algorithms currently supported include:
|
||||||
|
|
||||||
|
* `'Argon2d'`[^modern-algos]
|
||||||
|
* `'Argon2i'`[^modern-algos]
|
||||||
|
* `'Argon2id'`[^modern-algos]
|
||||||
* `'ECDH'`
|
* `'ECDH'`
|
||||||
* `'HKDF'`
|
* `'HKDF'`
|
||||||
* `'PBKDF2'`
|
* `'PBKDF2'`
|
||||||
|
|
@ -911,6 +932,9 @@ The algorithms currently supported include:
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v15.0.0
|
added: v15.0.0
|
||||||
changes:
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/59544
|
||||||
|
description: Argon2 algorithms are now supported.
|
||||||
- version:
|
- version:
|
||||||
- v18.4.0
|
- v18.4.0
|
||||||
- v16.17.0
|
- v16.17.0
|
||||||
|
|
@ -920,7 +944,7 @@ changes:
|
||||||
|
|
||||||
<!--lint disable maximum-line-length remark-lint-->
|
<!--lint disable maximum-line-length remark-lint-->
|
||||||
|
|
||||||
* `algorithm` {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params}
|
* `algorithm` {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|Argon2Params}
|
||||||
* `baseKey` {CryptoKey}
|
* `baseKey` {CryptoKey}
|
||||||
* `derivedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
|
* `derivedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
|
||||||
* `extractable` {boolean}
|
* `extractable` {boolean}
|
||||||
|
|
@ -940,6 +964,9 @@ generate raw keying material, then passing the result into the
|
||||||
|
|
||||||
The algorithms currently supported include:
|
The algorithms currently supported include:
|
||||||
|
|
||||||
|
* `'Argon2d'`[^modern-algos]
|
||||||
|
* `'Argon2i'`[^modern-algos]
|
||||||
|
* `'Argon2id'`[^modern-algos]
|
||||||
* `'ECDH'`
|
* `'ECDH'`
|
||||||
* `'HKDF'`
|
* `'HKDF'`
|
||||||
* `'PBKDF2'`
|
* `'PBKDF2'`
|
||||||
|
|
@ -1235,7 +1262,7 @@ as the given `format` to create a {CryptoKey} instance using the provided
|
||||||
`algorithm`, `extractable`, and `keyUsages` arguments. If the import is
|
`algorithm`, `extractable`, and `keyUsages` arguments. If the import is
|
||||||
successful, the returned promise will be resolved with the created {CryptoKey}.
|
successful, the returned promise will be resolved with the created {CryptoKey}.
|
||||||
|
|
||||||
If importing a `'PBKDF2'` key, `extractable` must be `false`.
|
If importing KDF algorithm keys, `extractable` must be `false`.
|
||||||
|
|
||||||
The algorithms currently supported include:
|
The algorithms currently supported include:
|
||||||
|
|
||||||
|
|
@ -1246,6 +1273,9 @@ The algorithms currently supported include:
|
||||||
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
|
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
|
||||||
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
|
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
|
||||||
| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | |
|
| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||||
|
| `'Argon2d'`[^modern-algos] | | | | | ✔ | | |
|
||||||
|
| `'Argon2i'`[^modern-algos] | | | | | ✔ | | |
|
||||||
|
| `'Argon2id'`[^modern-algos] | | | | | ✔ | | |
|
||||||
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
|
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||||
|
|
@ -1666,6 +1696,90 @@ added: v15.0.0
|
||||||
* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or
|
* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or
|
||||||
`'AES-KW'`
|
`'AES-KW'`
|
||||||
|
|
||||||
|
### Class: `Argon2Params`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
#### `argon2Params.associatedData`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||||
|
|
||||||
|
Represents the optional associated data.
|
||||||
|
|
||||||
|
#### `argon2Params.memory`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {number}
|
||||||
|
|
||||||
|
Represents the memory size in kibibytes. It must be at least 8 times the degree of parallelism.
|
||||||
|
|
||||||
|
#### `argon2Params.name`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {string} Must be one of `'Argon2d'`, `'Argon2i'`, or `'Argon2id'`.
|
||||||
|
|
||||||
|
#### `argon2Params.nonce`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||||
|
|
||||||
|
Represents the nonce, which is a salt for password hashing applications.
|
||||||
|
|
||||||
|
#### `argon2Params.parallelism`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {number}
|
||||||
|
|
||||||
|
Represents the degree of parallelism.
|
||||||
|
|
||||||
|
#### `argon2Params.passes`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {number}
|
||||||
|
|
||||||
|
Represents the number of passes.
|
||||||
|
|
||||||
|
#### `argon2Params.secretValue`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||||
|
|
||||||
|
Represents the optional secret value.
|
||||||
|
|
||||||
|
#### `argon2Params.version`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Type: {number}
|
||||||
|
|
||||||
|
Represents the Argon2 version number. The default and currently only defined version is `19` (`0x13`).
|
||||||
|
|
||||||
### Class: `ContextParams`
|
### Class: `ContextParams`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
|
@ -2420,6 +2534,8 @@ The length (in bytes) of the random salt to use.
|
||||||
|
|
||||||
[^openssl30]: Requires OpenSSL >= 3.0
|
[^openssl30]: Requires OpenSSL >= 3.0
|
||||||
|
|
||||||
|
[^openssl32]: Requires OpenSSL >= 3.2
|
||||||
|
|
||||||
[^openssl35]: Requires OpenSSL >= 3.5
|
[^openssl35]: Requires OpenSSL >= 3.5
|
||||||
|
|
||||||
[JSON Web Key]: https://tools.ietf.org/html/rfc7517
|
[JSON Web Key]: https://tools.ietf.org/html/rfc7517
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
const {
|
const {
|
||||||
FunctionPrototypeCall,
|
FunctionPrototypeCall,
|
||||||
MathPow,
|
MathPow,
|
||||||
|
StringPrototypeToLowerCase,
|
||||||
Uint8Array,
|
Uint8Array,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
|
|
@ -17,7 +18,16 @@ const {
|
||||||
kTypeArgon2id,
|
kTypeArgon2id,
|
||||||
} = internalBinding('crypto');
|
} = internalBinding('crypto');
|
||||||
|
|
||||||
const { getArrayBufferOrView } = require('internal/crypto/util');
|
const {
|
||||||
|
lazyDOMException,
|
||||||
|
promisify,
|
||||||
|
} = require('internal/util');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getArrayBufferOrView,
|
||||||
|
kKeyObject,
|
||||||
|
} = require('internal/crypto/util');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
validateString,
|
validateString,
|
||||||
validateFunction,
|
validateFunction,
|
||||||
|
|
@ -179,7 +189,54 @@ function check(algorithm, parameters) {
|
||||||
return { message, nonce, secret, associatedData, tagLength, passes, parallelism, memory, type };
|
return { message, nonce, secret, associatedData, tagLength, passes, parallelism, memory, type };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const argon2Promise = promisify(argon2);
|
||||||
|
function validateArgon2DeriveBitsLength(length) {
|
||||||
|
if (length === null)
|
||||||
|
throw lazyDOMException('length cannot be null', 'OperationError');
|
||||||
|
|
||||||
|
if (length % 8) {
|
||||||
|
throw lazyDOMException(
|
||||||
|
'length must be a multiple of 8',
|
||||||
|
'OperationError');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length < 32) {
|
||||||
|
throw lazyDOMException(
|
||||||
|
'length must be >= 32',
|
||||||
|
'OperationError');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function argon2DeriveBits(algorithm, baseKey, length) {
|
||||||
|
validateArgon2DeriveBitsLength(length);
|
||||||
|
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await argon2Promise(
|
||||||
|
StringPrototypeToLowerCase(algorithm.name),
|
||||||
|
{
|
||||||
|
message: baseKey[kKeyObject].export(),
|
||||||
|
nonce: algorithm.nonce,
|
||||||
|
parallelism: algorithm.parallelism,
|
||||||
|
tagLength: length / 8,
|
||||||
|
memory: algorithm.memory,
|
||||||
|
passes: algorithm.passes,
|
||||||
|
secret: algorithm.secretValue,
|
||||||
|
associatedData: algorithm.associatedData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
throw lazyDOMException(
|
||||||
|
'The operation failed for an operation-specific reason',
|
||||||
|
{ name: 'OperationError', cause: err });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
argon2,
|
argon2,
|
||||||
argon2Sync,
|
argon2Sync,
|
||||||
|
argon2DeriveBits,
|
||||||
|
validateArgon2DeriveBitsLength,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,12 @@ const {
|
||||||
case 'HKDF':
|
case 'HKDF':
|
||||||
// Fall through
|
// Fall through
|
||||||
case 'PBKDF2':
|
case 'PBKDF2':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2d':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2i':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2id':
|
||||||
result = importGenericSecretKey(
|
result = importGenericSecretKey(
|
||||||
algorithm,
|
algorithm,
|
||||||
'KeyObject',
|
'KeyObject',
|
||||||
|
|
@ -998,6 +1004,7 @@ function importGenericSecretKey(
|
||||||
keyObject = keyData;
|
keyObject = keyData;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'raw-secret':
|
||||||
case 'raw': {
|
case 'raw': {
|
||||||
keyObject = createSecretKey(keyData);
|
keyObject = createSecretKey(keyData);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const {
|
||||||
EVP_PKEY_ML_KEM_768,
|
EVP_PKEY_ML_KEM_768,
|
||||||
EVP_PKEY_ML_KEM_1024,
|
EVP_PKEY_ML_KEM_1024,
|
||||||
kKeyVariantAES_OCB_128: hasAesOcbMode,
|
kKeyVariantAES_OCB_128: hasAesOcbMode,
|
||||||
|
Argon2Job,
|
||||||
} = internalBinding('crypto');
|
} = internalBinding('crypto');
|
||||||
|
|
||||||
const { getOptionValue } = require('internal/options');
|
const { getOptionValue } = require('internal/options');
|
||||||
|
|
@ -217,6 +218,21 @@ const kAlgorithmDefinitions = {
|
||||||
'decrypt': 'AeadParams',
|
'decrypt': 'AeadParams',
|
||||||
'get key length': 'AesDerivedKeyParams',
|
'get key length': 'AesDerivedKeyParams',
|
||||||
},
|
},
|
||||||
|
'Argon2d': {
|
||||||
|
'deriveBits': 'Argon2Params',
|
||||||
|
'get key length': null,
|
||||||
|
'importKey': null,
|
||||||
|
},
|
||||||
|
'Argon2i': {
|
||||||
|
'deriveBits': 'Argon2Params',
|
||||||
|
'get key length': null,
|
||||||
|
'importKey': null,
|
||||||
|
},
|
||||||
|
'Argon2id': {
|
||||||
|
'deriveBits': 'Argon2Params',
|
||||||
|
'get key length': null,
|
||||||
|
'importKey': null,
|
||||||
|
},
|
||||||
'ChaCha20-Poly1305': {
|
'ChaCha20-Poly1305': {
|
||||||
'generateKey': null,
|
'generateKey': null,
|
||||||
'exportKey': null,
|
'exportKey': null,
|
||||||
|
|
@ -360,6 +376,9 @@ const kAlgorithmDefinitions = {
|
||||||
const conditionalAlgorithms = {
|
const conditionalAlgorithms = {
|
||||||
'AES-KW': !process.features.openssl_is_boringssl,
|
'AES-KW': !process.features.openssl_is_boringssl,
|
||||||
'AES-OCB': !!hasAesOcbMode,
|
'AES-OCB': !!hasAesOcbMode,
|
||||||
|
'Argon2d': !!Argon2Job,
|
||||||
|
'Argon2i': !!Argon2Job,
|
||||||
|
'Argon2id': !!Argon2Job,
|
||||||
'ChaCha20-Poly1305': !process.features.openssl_is_boringssl ||
|
'ChaCha20-Poly1305': !process.features.openssl_is_boringssl ||
|
||||||
ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
|
ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
|
||||||
'cSHAKE128': !process.features.openssl_is_boringssl ||
|
'cSHAKE128': !process.features.openssl_is_boringssl ||
|
||||||
|
|
@ -385,6 +404,9 @@ const conditionalAlgorithms = {
|
||||||
// Experimental algorithms
|
// Experimental algorithms
|
||||||
const experimentalAlgorithms = [
|
const experimentalAlgorithms = [
|
||||||
'AES-OCB',
|
'AES-OCB',
|
||||||
|
'Argon2d',
|
||||||
|
'Argon2i',
|
||||||
|
'Argon2id',
|
||||||
'ChaCha20-Poly1305',
|
'ChaCha20-Poly1305',
|
||||||
'cSHAKE128',
|
'cSHAKE128',
|
||||||
'cSHAKE256',
|
'cSHAKE256',
|
||||||
|
|
@ -461,6 +483,11 @@ const simpleAlgorithmDictionaries = {
|
||||||
functionName: 'BufferSource',
|
functionName: 'BufferSource',
|
||||||
customization: 'BufferSource',
|
customization: 'BufferSource',
|
||||||
},
|
},
|
||||||
|
Argon2Params: {
|
||||||
|
associatedData: 'BufferSource',
|
||||||
|
nonce: 'BufferSource',
|
||||||
|
secretValue: 'BufferSource',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function validateMaxBufferLength(data, name) {
|
function validateMaxBufferLength(data, name) {
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,13 @@ async function deriveBits(algorithm, baseKey, length = null) {
|
||||||
case 'PBKDF2':
|
case 'PBKDF2':
|
||||||
return require('internal/crypto/pbkdf2')
|
return require('internal/crypto/pbkdf2')
|
||||||
.pbkdf2DeriveBits(algorithm, baseKey, length);
|
.pbkdf2DeriveBits(algorithm, baseKey, length);
|
||||||
|
case 'Argon2d':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2i':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2id':
|
||||||
|
return require('internal/crypto/argon2')
|
||||||
|
.argon2DeriveBits(algorithm, baseKey, length);
|
||||||
}
|
}
|
||||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||||
}
|
}
|
||||||
|
|
@ -273,6 +280,9 @@ function getKeyLength({ name, length, hash }) {
|
||||||
throw lazyDOMException('Invalid key length', 'OperationError');
|
throw lazyDOMException('Invalid key length', 'OperationError');
|
||||||
case 'HKDF':
|
case 'HKDF':
|
||||||
case 'PBKDF2':
|
case 'PBKDF2':
|
||||||
|
case 'Argon2d':
|
||||||
|
case 'Argon2i':
|
||||||
|
case 'Argon2id':
|
||||||
return null;
|
return null;
|
||||||
case 'ChaCha20-Poly1305':
|
case 'ChaCha20-Poly1305':
|
||||||
return 256;
|
return 256;
|
||||||
|
|
@ -340,6 +350,14 @@ async function deriveKey(
|
||||||
bits = await require('internal/crypto/pbkdf2')
|
bits = await require('internal/crypto/pbkdf2')
|
||||||
.pbkdf2DeriveBits(algorithm, baseKey, length);
|
.pbkdf2DeriveBits(algorithm, baseKey, length);
|
||||||
break;
|
break;
|
||||||
|
case 'Argon2d':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2i':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2id':
|
||||||
|
bits = await require('internal/crypto/argon2')
|
||||||
|
.argon2DeriveBits(algorithm, baseKey, length);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||||
}
|
}
|
||||||
|
|
@ -788,6 +806,20 @@ async function importKey(
|
||||||
extractable,
|
extractable,
|
||||||
keyUsages);
|
keyUsages);
|
||||||
break;
|
break;
|
||||||
|
case 'Argon2d':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2i':
|
||||||
|
// Fall through
|
||||||
|
case 'Argon2id':
|
||||||
|
if (format === 'raw-secret') {
|
||||||
|
result = importGenericSecretKey(
|
||||||
|
algorithm,
|
||||||
|
format,
|
||||||
|
keyData,
|
||||||
|
extractable,
|
||||||
|
keyUsages);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'ML-DSA-44':
|
case 'ML-DSA-44':
|
||||||
// Fall through
|
// Fall through
|
||||||
case 'ML-DSA-65':
|
case 'ML-DSA-65':
|
||||||
|
|
@ -1591,6 +1623,14 @@ function check(op, alg, length) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringPrototypeStartsWith(normalizedAlgorithm.name, 'Argon2')) {
|
||||||
|
try {
|
||||||
|
require('internal/crypto/argon2').validateArgon2DeriveBitsLength(length);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case 'generateKey': {
|
case 'generateKey': {
|
||||||
|
|
|
||||||
|
|
@ -805,6 +805,68 @@ for (const name of ['Ed448Params', 'ContextParams']) {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
converters.Argon2Params = createDictionaryConverter(
|
||||||
|
'Argon2Params', [
|
||||||
|
...new SafeArrayIterator(dictAlgorithm),
|
||||||
|
{
|
||||||
|
key: 'nonce',
|
||||||
|
converter: converters.BufferSource,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'parallelism',
|
||||||
|
converter: (V, opts) =>
|
||||||
|
converters['unsigned long'](V, { ...opts, enforceRange: true }),
|
||||||
|
validator: (V, dict) => {
|
||||||
|
if (V === 0 || V > MathPow(2, 24) - 1) {
|
||||||
|
throw lazyDOMException(
|
||||||
|
'parallelism must be > 0 and < 16777215',
|
||||||
|
'OperationError');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'memory',
|
||||||
|
converter: (V, opts) =>
|
||||||
|
converters['unsigned long'](V, { ...opts, enforceRange: true }),
|
||||||
|
validator: (V, dict) => {
|
||||||
|
if (V < 8 * dict.parallelism) {
|
||||||
|
throw lazyDOMException(
|
||||||
|
'memory must be at least 8 times the degree of parallelism',
|
||||||
|
'OperationError');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'passes',
|
||||||
|
converter: (V, opts) =>
|
||||||
|
converters['unsigned long'](V, { ...opts, enforceRange: true }),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'version',
|
||||||
|
converter: (V, opts) =>
|
||||||
|
converters.octet(V, { ...opts, enforceRange: true }),
|
||||||
|
validator: (V, dict) => {
|
||||||
|
if (V !== 0x13) {
|
||||||
|
throw lazyDOMException(
|
||||||
|
`${V} is not a valid Argon2 version`,
|
||||||
|
'OperationError');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'secretValue',
|
||||||
|
converter: converters.BufferSource,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'associatedData',
|
||||||
|
converter: converters.BufferSource,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
converters,
|
converters,
|
||||||
requiredArguments,
|
requiredArguments,
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,15 @@ import * as crypto from 'node:crypto'
|
||||||
import { hasOpenSSL } from '../../common/crypto.js'
|
import { hasOpenSSL } from '../../common/crypto.js'
|
||||||
|
|
||||||
const pqc = hasOpenSSL(3, 5);
|
const pqc = hasOpenSSL(3, 5);
|
||||||
|
const argon2 = hasOpenSSL(3, 2);
|
||||||
const shake128 = crypto.getHashes().includes('shake128');
|
const shake128 = crypto.getHashes().includes('shake128');
|
||||||
const shake256 = crypto.getHashes().includes('shake256');
|
const shake256 = crypto.getHashes().includes('shake256');
|
||||||
const chacha = crypto.getCiphers().includes('chacha20-poly1305');
|
const chacha = crypto.getCiphers().includes('chacha20-poly1305');
|
||||||
const ocb = hasOpenSSL(3);
|
const ocb = hasOpenSSL(3);
|
||||||
|
|
||||||
|
const { subtle } = globalThis.crypto;
|
||||||
|
const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']);
|
||||||
|
|
||||||
export const vectors = {
|
export const vectors = {
|
||||||
'digest': [
|
'digest': [
|
||||||
[false, 'cSHAKE128'],
|
[false, 'cSHAKE128'],
|
||||||
|
|
@ -27,6 +31,9 @@ export const vectors = {
|
||||||
[pqc, 'ML-DSA-44'],
|
[pqc, 'ML-DSA-44'],
|
||||||
[pqc, 'ML-DSA-65'],
|
[pqc, 'ML-DSA-65'],
|
||||||
[pqc, 'ML-DSA-87'],
|
[pqc, 'ML-DSA-87'],
|
||||||
|
[false, 'Argon2d'],
|
||||||
|
[false, 'Argon2i'],
|
||||||
|
[false, 'Argon2id'],
|
||||||
],
|
],
|
||||||
'generateKey': [
|
'generateKey': [
|
||||||
[pqc, 'ML-DSA-44'],
|
[pqc, 'ML-DSA-44'],
|
||||||
|
|
@ -37,6 +44,9 @@ export const vectors = {
|
||||||
[pqc, 'ML-KEM-1024'],
|
[pqc, 'ML-KEM-1024'],
|
||||||
[chacha, 'ChaCha20-Poly1305'],
|
[chacha, 'ChaCha20-Poly1305'],
|
||||||
[ocb, { name: 'AES-OCB', length: 128 }],
|
[ocb, { name: 'AES-OCB', length: 128 }],
|
||||||
|
[false, 'Argon2d'],
|
||||||
|
[false, 'Argon2i'],
|
||||||
|
[false, 'Argon2id'],
|
||||||
],
|
],
|
||||||
'importKey': [
|
'importKey': [
|
||||||
[pqc, 'ML-DSA-44'],
|
[pqc, 'ML-DSA-44'],
|
||||||
|
|
@ -47,6 +57,9 @@ export const vectors = {
|
||||||
[pqc, 'ML-KEM-1024'],
|
[pqc, 'ML-KEM-1024'],
|
||||||
[chacha, 'ChaCha20-Poly1305'],
|
[chacha, 'ChaCha20-Poly1305'],
|
||||||
[ocb, { name: 'AES-OCB', length: 128 }],
|
[ocb, { name: 'AES-OCB', length: 128 }],
|
||||||
|
[argon2, 'Argon2d'],
|
||||||
|
[argon2, 'Argon2i'],
|
||||||
|
[argon2, 'Argon2id'],
|
||||||
],
|
],
|
||||||
'exportKey': [
|
'exportKey': [
|
||||||
[pqc, 'ML-DSA-44'],
|
[pqc, 'ML-DSA-44'],
|
||||||
|
|
@ -57,6 +70,9 @@ export const vectors = {
|
||||||
[pqc, 'ML-KEM-1024'],
|
[pqc, 'ML-KEM-1024'],
|
||||||
[chacha, 'ChaCha20-Poly1305'],
|
[chacha, 'ChaCha20-Poly1305'],
|
||||||
[ocb, 'AES-OCB'],
|
[ocb, 'AES-OCB'],
|
||||||
|
[false, 'Argon2d'],
|
||||||
|
[false, 'Argon2i'],
|
||||||
|
[false, 'Argon2id'],
|
||||||
],
|
],
|
||||||
'getPublicKey': [
|
'getPublicKey': [
|
||||||
[true, 'RSA-OAEP'],
|
[true, 'RSA-OAEP'],
|
||||||
|
|
@ -80,6 +96,35 @@ export const vectors = {
|
||||||
[false, 'AES-OCB'],
|
[false, 'AES-OCB'],
|
||||||
[false, 'AES-KW'],
|
[false, 'AES-KW'],
|
||||||
[false, 'ChaCha20-Poly1305'],
|
[false, 'ChaCha20-Poly1305'],
|
||||||
|
[false, 'Argon2d'],
|
||||||
|
[false, 'Argon2i'],
|
||||||
|
[false, 'Argon2id'],
|
||||||
|
],
|
||||||
|
'deriveKey': [
|
||||||
|
[argon2,
|
||||||
|
{ name: 'X25519', public: X25519.publicKey },
|
||||||
|
'Argon2d'],
|
||||||
|
[argon2,
|
||||||
|
{ name: 'X25519', public: X25519.publicKey },
|
||||||
|
'Argon2i'],
|
||||||
|
[argon2,
|
||||||
|
{ name: 'X25519', public: X25519.publicKey },
|
||||||
|
'Argon2id'],
|
||||||
|
],
|
||||||
|
'deriveBits': [
|
||||||
|
[argon2, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1 }, 32],
|
||||||
|
[argon2, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 2, memory: 16, passes: 1 }, 32],
|
||||||
|
[argon2, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1, secretValue: Buffer.alloc(0) }, 32],
|
||||||
|
[argon2, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1, associatedData: Buffer.alloc(0) }, 32],
|
||||||
|
[argon2, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1, version: 0x13 }, 32],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1, version: 0x14 }, 32],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 7, passes: 1 }, 32],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 2, memory: 15, passes: 1 }, 32],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1 }, null],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1 }, 24],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 1, memory: 8, passes: 1 }, 31],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 0, memory: 8, passes: 1 }, 32],
|
||||||
|
[false, { name: 'Argon2d', nonce: Buffer.alloc(0), parallelism: 16777215, memory: 8, passes: 1 }, 32],
|
||||||
],
|
],
|
||||||
'encrypt': [
|
'encrypt': [
|
||||||
[chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }],
|
[chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }],
|
||||||
|
|
|
||||||
92
test/parallel/test-webcrypto-derivebits-argon2.js
Normal file
92
test/parallel/test-webcrypto-derivebits-argon2.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
|
||||||
|
const { hasOpenSSL } = require('../common/crypto');
|
||||||
|
|
||||||
|
if (!hasOpenSSL(3, 2))
|
||||||
|
common.skip('requires OpenSSL >= 3.2');
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { createSecretKey } = require('crypto');
|
||||||
|
const { subtle } = globalThis.crypto;
|
||||||
|
|
||||||
|
const vectors = [
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc9106#name-argon2d-test-vectors
|
||||||
|
{
|
||||||
|
algorithm: 'Argon2d',
|
||||||
|
length: 256,
|
||||||
|
password: Buffer.alloc(32, 0x01),
|
||||||
|
params: {
|
||||||
|
memory: 32,
|
||||||
|
passes: 3,
|
||||||
|
parallelism: 4,
|
||||||
|
nonce: Buffer.alloc(16, 0x02),
|
||||||
|
secretValue: Buffer.alloc(8, 0x03),
|
||||||
|
associatedData: Buffer.alloc(12, 0x04),
|
||||||
|
},
|
||||||
|
tag: Buffer.alloc(32,
|
||||||
|
'512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb',
|
||||||
|
'hex').buffer,
|
||||||
|
},
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc9106#name-argon2i-test-vectors
|
||||||
|
{
|
||||||
|
algorithm: 'Argon2i',
|
||||||
|
length: 256,
|
||||||
|
password: Buffer.alloc(32, 0x01),
|
||||||
|
params: {
|
||||||
|
memory: 32,
|
||||||
|
passes: 3,
|
||||||
|
parallelism: 4,
|
||||||
|
nonce: Buffer.alloc(16, 0x02),
|
||||||
|
secretValue: Buffer.alloc(8, 0x03),
|
||||||
|
associatedData: Buffer.alloc(12, 0x04),
|
||||||
|
},
|
||||||
|
tag: Buffer.alloc(32,
|
||||||
|
'c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8',
|
||||||
|
'hex').buffer,
|
||||||
|
},
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc9106#name-argon2id-test-vectors
|
||||||
|
{
|
||||||
|
algorithm: 'Argon2id',
|
||||||
|
length: 256,
|
||||||
|
password: Buffer.alloc(32, 0x01),
|
||||||
|
params: {
|
||||||
|
memory: 32,
|
||||||
|
passes: 3,
|
||||||
|
parallelism: 4,
|
||||||
|
nonce: Buffer.alloc(16, 0x02),
|
||||||
|
secretValue: Buffer.alloc(8, 0x03),
|
||||||
|
associatedData: Buffer.alloc(12, 0x04),
|
||||||
|
},
|
||||||
|
tag: Buffer.alloc(32,
|
||||||
|
'0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659',
|
||||||
|
'hex').buffer,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { algorithm, length, password, params, tag } of vectors) {
|
||||||
|
(async () => {
|
||||||
|
const parameters = { name: algorithm, ...params };
|
||||||
|
const usages = ['deriveBits', 'deriveKey'];
|
||||||
|
{
|
||||||
|
const key = await subtle.importKey('raw-secret', password, algorithm, false, usages);
|
||||||
|
|
||||||
|
const result = await subtle.deriveBits(parameters, key, length);
|
||||||
|
assert.deepStrictEqual(result, tag);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const derivedKeyType = { name: 'HMAC', length, hash: 'SHA-256' };
|
||||||
|
|
||||||
|
const key = createSecretKey(password)
|
||||||
|
.toCryptoKey(algorithm, false, usages);
|
||||||
|
const hmac = await subtle.deriveKey(parameters, key, derivedKeyType, true, ['sign', 'verify']);
|
||||||
|
|
||||||
|
const result = await subtle.exportKey('raw', hmac);
|
||||||
|
assert.deepStrictEqual(result, tag);
|
||||||
|
}
|
||||||
|
})().then(common.mustCall());
|
||||||
|
}
|
||||||
|
|
@ -105,6 +105,7 @@ const customTypesMap = {
|
||||||
'HkdfParams': 'webcrypto.html#class-hkdfparams',
|
'HkdfParams': 'webcrypto.html#class-hkdfparams',
|
||||||
'KeyAlgorithm': 'webcrypto.html#class-keyalgorithm',
|
'KeyAlgorithm': 'webcrypto.html#class-keyalgorithm',
|
||||||
'Pbkdf2Params': 'webcrypto.html#class-pbkdf2params',
|
'Pbkdf2Params': 'webcrypto.html#class-pbkdf2params',
|
||||||
|
'Argon2Params': 'webcrypto.html#class-argon2params',
|
||||||
'HmacKeyAlgorithm': 'webcrypto.html#class-hmackeyalgorithm',
|
'HmacKeyAlgorithm': 'webcrypto.html#class-hmackeyalgorithm',
|
||||||
'HmacKeyGenParams': 'webcrypto.html#class-hmackeygenparams',
|
'HmacKeyGenParams': 'webcrypto.html#class-hmackeygenparams',
|
||||||
'AesKeyAlgorithm': 'webcrypto.html#class-aeskeyalgorithm',
|
'AesKeyAlgorithm': 'webcrypto.html#class-aeskeyalgorithm',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user