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:
Filip Skokan 2025-08-26 18:05:56 +02:00 committed by GitHub
parent d00228f5ca
commit 27e2d81617
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 452 additions and 5 deletions

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

@ -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) }],

View 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());
}

View File

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