node/lib/internal/crypto/argon2.js
Filip Skokan 6478dd0e28
lib: prefer TypedArrayPrototype primordials
Refs: #59699
PR-URL: https://github.com/nodejs/node/pull/59766
Refs: https://github.com/nodejs/node/issues/59699
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jordan Harband <ljharb@gmail.com>
2025-09-07 12:34:43 +00:00

244 lines
6.4 KiB
JavaScript

'use strict';
const {
FunctionPrototypeCall,
MathPow,
StringPrototypeToLowerCase,
TypedArrayPrototypeGetBuffer,
Uint8Array,
} = primordials;
const { Buffer } = require('buffer');
const {
Argon2Job,
kCryptoJobAsync,
kCryptoJobSync,
kTypeArgon2d,
kTypeArgon2i,
kTypeArgon2id,
} = internalBinding('crypto');
const {
lazyDOMException,
promisify,
} = require('internal/util');
const {
getArrayBufferOrView,
kKeyObject,
} = 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']);
let type;
switch (algorithm) {
case 'argon2d':
type = kTypeArgon2d;
break;
case 'argon2i':
type = kTypeArgon2i;
break;
case 'argon2id':
type = kTypeArgon2id;
break;
default: // unreachable
throw new ERR_CRYPTO_ARGON2_NOT_SUPPORTED();
}
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 };
}
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 TypedArrayPrototypeGetBuffer(result);
}
module.exports = {
argon2,
argon2Sync,
argon2DeriveBits,
validateArgon2DeriveBitsLength,
};