node/lib/internal/crypto/argon2.js
Filip Skokan 0cabd66f31 crypto: pass CryptoKey handles to KDF jobs
Pass CryptoKey handles directly into KDF jobs instead of exporting
secret bytes in lib.

Normalize HKDF, PBKDF2, and Argon2 around the same job construction
pattern so WebCrypto derivation paths avoid extra key material copies
and keep operation failures in native job handling.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/63363
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-05-23 15:45:32 +00:00

232 lines
6.1 KiB
JavaScript

'use strict';
const {
FunctionPrototypeCall,
MathPow,
Uint8Array,
} = primordials;
const { Buffer } = require('buffer');
const {
Argon2Job,
kCryptoJobAsync,
kCryptoJobSync,
kCryptoJobWebCrypto,
kTypeArgon2d,
kTypeArgon2i,
kTypeArgon2id,
} = internalBinding('crypto');
const {
lazyDOMException,
} = require('internal/util');
const {
getCryptoKeyHandle,
} = require('internal/crypto/keys');
const {
getArrayBufferOrView,
jobPromise,
} = require('internal/crypto/util');
const {
validateString,
validateFunction,
validateInteger,
validateObject,
validateOneOf,
validateUint32,
} = require('internal/validators');
const {
codes: {
ERR_CRYPTO_ARGON2_NOT_SUPPORTED,
},
} = require('internal/errors');
/**
* @param {'argon2d' | 'argon2i' | 'argon2id'} algorithm
* @param {object} parameters
* @param {ArrayBufferLike} parameters.message
* @param {ArrayBufferLike} parameters.nonce
* @param {number} parameters.parallelism
* @param {number} parameters.tagLength
* @param {number} parameters.memory
* @param {number} parameters.passes
* @param {ArrayBufferLike} [parameters.secret]
* @param {ArrayBufferLike} [parameters.associatedData]
* @param {Function} callback
*/
function argon2(algorithm, parameters, callback) {
parameters = check(algorithm, parameters);
validateFunction(callback, 'callback');
const job = new Argon2Job(
kCryptoJobAsync,
parameters.message,
parameters.nonce,
parameters.parallelism,
parameters.tagLength,
parameters.memory,
parameters.passes,
parameters.secret,
parameters.associatedData,
parameters.type);
job.ondone = (error, result) => {
if (error !== undefined)
return FunctionPrototypeCall(callback, job, error);
const buf = Buffer.from(result);
return FunctionPrototypeCall(callback, job, null, buf);
};
job.run();
}
/**
* @param {'argon2d' | 'argon2i' | 'argon2id'} algorithm
* @param {object} parameters
* @param {ArrayBufferLike} parameters.message
* @param {ArrayBufferLike} parameters.nonce
* @param {number} parameters.parallelism
* @param {number} parameters.tagLength
* @param {number} parameters.memory
* @param {number} parameters.passes
* @param {ArrayBufferLike} [parameters.secret]
* @param {ArrayBufferLike} [parameters.associatedData]
* @returns {Buffer}
*/
function argon2Sync(algorithm, parameters) {
parameters = check(algorithm, parameters);
const job = new Argon2Job(
kCryptoJobSync,
parameters.message,
parameters.nonce,
parameters.parallelism,
parameters.tagLength,
parameters.memory,
parameters.passes,
parameters.secret,
parameters.associatedData,
parameters.type);
const { 0: err, 1: result } = job.run();
if (err !== undefined)
throw err;
return Buffer.from(result);
}
/**
* @param {'argon2d' | 'argon2i' | 'argon2id'} algorithm
* @param {object} parameters
* @param {ArrayBufferLike} parameters.message
* @param {ArrayBufferLike} parameters.nonce
* @param {number} parameters.parallelism
* @param {number} parameters.tagLength
* @param {number} parameters.memory
* @param {number} parameters.passes
* @param {ArrayBufferLike} [parameters.secret]
* @param {ArrayBufferLike} [parameters.associatedData]
* @returns {object}
*/
function check(algorithm, parameters) {
if (Argon2Job === undefined)
throw new ERR_CRYPTO_ARGON2_NOT_SUPPORTED();
validateString(algorithm, 'algorithm');
validateOneOf(algorithm, 'algorithm', ['argon2d', 'argon2i', 'argon2id']);
const type = {
'__proto__': null,
'argon2d': kTypeArgon2d,
'argon2i': kTypeArgon2i,
'argon2id': kTypeArgon2id,
}[algorithm];
validateObject(parameters, 'parameters');
const { parallelism, tagLength, memory, passes } = parameters;
const MAX_POSITIVE_UINT_32 = MathPow(2, 32) - 1;
const message = getArrayBufferOrView(parameters.message, 'parameters.message');
validateInteger(message.byteLength, 'parameters.message.byteLength', 0, MAX_POSITIVE_UINT_32);
const nonce = getArrayBufferOrView(parameters.nonce, 'parameters.nonce');
validateInteger(nonce.byteLength, 'parameters.nonce.byteLength', 8, MAX_POSITIVE_UINT_32);
validateInteger(parallelism, 'parameters.parallelism', 1, MathPow(2, 24) - 1);
validateInteger(tagLength, 'parameters.tagLength', 4, MAX_POSITIVE_UINT_32);
validateInteger(memory, 'parameters.memory', 8 * parallelism, MAX_POSITIVE_UINT_32);
validateUint32(passes, 'parameters.passes', true);
let secret;
if (parameters.secret === undefined) {
secret = new Uint8Array(0);
} else {
secret = getArrayBufferOrView(parameters.secret);
validateInteger(secret.byteLength, 'parameters.secret.byteLength', 0, MAX_POSITIVE_UINT_32);
}
let associatedData;
if (parameters.associatedData === undefined) {
associatedData = new Uint8Array(0);
} else {
associatedData = getArrayBufferOrView(parameters.associatedData);
validateInteger(associatedData.byteLength, 'parameters.associatedData.byteLength', 0, MAX_POSITIVE_UINT_32);
}
return { message, nonce, secret, associatedData, tagLength, passes, parallelism, memory, type };
}
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');
}
}
function argon2DeriveBits(algorithm, baseKey, length) {
validateArgon2DeriveBitsLength(length);
const type = {
'__proto__': null,
'Argon2d': kTypeArgon2d,
'Argon2i': kTypeArgon2i,
'Argon2id': kTypeArgon2id,
}[algorithm.name];
return jobPromise(() => new Argon2Job(
kCryptoJobWebCrypto,
getCryptoKeyHandle(baseKey),
algorithm.nonce,
algorithm.parallelism,
length / 8,
algorithm.memory,
algorithm.passes,
algorithm.secretValue === undefined ? new Uint8Array() : algorithm.secretValue,
algorithm.associatedData === undefined ? new Uint8Array() : algorithm.associatedData,
type));
}
module.exports = {
argon2,
argon2Sync,
argon2DeriveBits,
validateArgon2DeriveBitsLength,
};