From 3d7b75a4325fae5db052c278418dcd3b264aa662 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Tue, 24 Oct 2023 00:31:35 +0200 Subject: [PATCH 01/17] crypto: add argon2() and argon2Sync() methods --- lib/crypto.js | 6 + lib/internal/crypto/argon2.js | 151 ++++++++++++++++ lib/internal/errors.js | 3 + node.gyp | 12 +- src/async_wrap.h | 1 + src/crypto/README.md | 1 + src/crypto/crypto_argon2.cc | 257 ++++++++++++++++++++++++++++ src/crypto/crypto_argon2.h | 97 +++++++++++ src/crypto/crypto_util.h | 2 + src/node_crypto.cc | 7 + src/node_crypto.h | 1 + src/node_errors.h | 2 + test/parallel/test-crypto-argon2.js | 167 ++++++++++++++++++ 13 files changed, 702 insertions(+), 5 deletions(-) create mode 100644 lib/internal/crypto/argon2.js create mode 100644 src/crypto/crypto_argon2.cc create mode 100644 src/crypto/crypto_argon2.h create mode 100644 test/parallel/test-crypto-argon2.js diff --git a/lib/crypto.js b/lib/crypto.js index 41adecc97c2527..6d6472bc90a7a6 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -57,6 +57,10 @@ const { randomInt, randomUUID, } = require('internal/crypto/random'); +const { + argon2, + argon2Sync, +} = require('internal/crypto/argon2'); const { pbkdf2, pbkdf2Sync, @@ -180,6 +184,8 @@ function createVerify(algorithm, options) { module.exports = { // Methods + argon2, + argon2Sync, checkPrime, checkPrimeSync, createCipheriv, diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js new file mode 100644 index 00000000000000..59ce1343935de4 --- /dev/null +++ b/lib/internal/crypto/argon2.js @@ -0,0 +1,151 @@ +'use strict'; + +const { + FunctionPrototypeCall, +} = primordials; + +const { Buffer } = require('buffer'); + +const { + Argon2Job, + kCryptoJobAsync, + kCryptoJobSync, +} = internalBinding('crypto'); + +const { getArrayBufferOrView } = require('internal/crypto/util'); +const { lazyDOMException } = require('internal/util'); +const { validateFunction, validateInteger } = require('internal/validators'); + +const { + codes: { + ERR_CRYPTO_ARGON2_INVALID_PARAMETER, + ERR_CRYPTO_ARGON2_NOT_SUPPORTED, + ERR_CRYPTO_INVALID_SALT_LENGTH, + }, +} = require('internal/errors'); + +const defaults = { + algorithm: "ARGON2ID", + secret: Buffer.alloc(0), + ad: Buffer.alloc(0), + iter: 3, + lanes: 4, + memcost: 64 << 10, // 64Ki * 1 KiB = 64 MiB + keylen: 64, +}; + +/** + * @param {ArrayBufferLike} password + * @param {ArrayBufferLike} salt + * @param {number} keylen + * @param {*} [options] + * @param {Function} callback + */ +function argon2(password, salt, keylen, options, callback = defaults) { + if (callback === defaults) { + callback = options; + options = defaults; + } + + options = check(password, salt, keylen, options); + const { algorithm, secret, ad, iter, lanes, memcost } = options; + ({ password, salt, keylen } = options); + + validateFunction(callback, 'callback'); + + const job = new Argon2Job( + kCryptoJobAsync, password, salt, algorithm, secret, ad, iter, lanes, memcost, keylen); + + 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 {ArrayBufferLike} password + * @param {ArrayBufferLike} salt + * @param {number} keylen + * @param {*} [options] + */ +function argon2Sync(password, salt, keylen, options = defaults) { + options = check(password, salt, keylen, options); + const { algorithm, secret, ad, iter, lanes, memcost } = options; + ({ password, salt, keylen } = options); + const job = new Argon2Job( + kCryptoJobSync, password, salt, algorithm, secret, ad, iter, lanes, memcost, keylen); + const { 0: err, 1: result } = job.run(); + + if (err !== undefined) + throw err; + + return Buffer.from(result); +} + +/** + * @param {ArrayBufferLike} password + * @param {ArrayBufferLike} salt + * @param {number} keylen + * @param {*} options + */ +function check(password, salt, keylen, options) { + if (Argon2Job === undefined) + throw new ERR_CRYPTO_ARGON2_NOT_SUPPORTED(); + + password = getArrayBufferOrView(password, 'password'); + + salt = getArrayBufferOrView(salt, 'salt'); + if (salt.byteLength < 8 || salt.byteLength > (2 ** 32) - 1) { + throw new ERR_CRYPTO_INVALID_SALT_LENGTH(8, (2 ** 32) - 1); + } + + validateInteger(keylen, 'keylen', 4, (2 ** 32) - 1); + + let { algorithm, secret, ad, iter, lanes, memcost } = defaults; + if (options && options !== defaults) { + const has_algorithm = options.algorithm !== undefined; + if (has_algorithm) { + algorithm = options.algorithm.toUpperCase(); + if (algorithm !== 'ARGON2D' && algorithm !== 'ARGON2I' && algorithm !== 'ARGON2ID') + throw new ERR_CRYPTO_ARGON2_INVALID_PARAMETER('algorithm', algorithm); + } + const has_secret = options.secret !== undefined; + if (has_secret) { + secret = getArrayBufferOrView(options.secret); + } + const has_ad = options.ad !== undefined; + if (has_ad) { + ad = getArrayBufferOrView(options.ad); + } + const has_iter = options.iter !== undefined; + if (has_iter) { + iter = options.iter; + validateInteger(iter, 'iter', 1, (2 ** 32) - 1); + } + const has_lanes = options.lanes !== undefined; + if (has_lanes) { + lanes = options.lanes; + validateInteger(options.lanes, 'lanes', 1, (2 ** 24) - 1); + } + if (iter === 0) iter = defaults.iter; + if (lanes === 0) lanes = defaults.lanes; + + const has_memcost = options.memcost !== undefined; + if (has_memcost) { + memcost = options.memcost; + validateInteger(memcost, 'memcost', 8 * lanes, (2 ** 32) - 1); + } + if (memcost === 0) memcost = defaults.memcost; + } + + return { password, salt, algorithm, secret, ad, keylen, iter, lanes, memcost }; +} + +module.exports = { + argon2, + argon2Sync, +}; diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 363b9d3bb8fe8b..db33a8524ed27f 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1092,6 +1092,7 @@ E('ERR_CHILD_PROCESS_STDIO_MAXBUFFER', '%s maxBuffer length exceeded', E('ERR_CONSOLE_WRITABLE_STREAM', 'Console expects a writable stream instance for %s', TypeError); E('ERR_CONTEXT_NOT_INITIALIZED', 'context used is not initialized', Error); +E('ERR_CRYPTO_ARGON2_NOT_SUPPORTED', 'Argon2 algorithm not supported', Error); E('ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED', 'Custom engines not supported by this OpenSSL', Error); E('ERR_CRYPTO_ECDH_INVALID_FORMAT', 'Invalid ECDH format: %s', TypeError); @@ -1111,6 +1112,8 @@ E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError); E('ERR_CRYPTO_INVALID_JWK', 'Invalid JWK data', TypeError); E('ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE', 'Invalid key object type %s, expected %s.', TypeError); +E('ERR_CRYPTO_INVALID_SALT_LENGTH', + 'Invalid salt length, must contain between %d and %d bytes', RangeError); E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error); E('ERR_CRYPTO_PBKDF2_ERROR', 'PBKDF2 error', Error); E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error); diff --git a/node.gyp b/node.gyp index 3f6915b266fc38..e2f78c9cdfd797 100644 --- a/node.gyp +++ b/node.gyp @@ -296,6 +296,7 @@ ], 'node_crypto_sources': [ 'src/crypto/crypto_aes.cc', + 'src/crypto/crypto_argon2.cc', 'src/crypto/crypto_bio.cc', 'src/crypto/crypto_common.cc', 'src/crypto/crypto_dsa.cc', @@ -319,6 +320,7 @@ 'src/crypto/crypto_scrypt.cc', 'src/crypto/crypto_tls.cc', 'src/crypto/crypto_x509.cc', + 'src/crypto/crypto_argon2.h', 'src/crypto/crypto_bio.h', 'src/crypto/crypto_clienthello-inl.h', 'src/crypto/crypto_dh.h', @@ -925,11 +927,11 @@ 'variables': { 'mkssldef_flags': [ # Categories to export. - '-CAES,BF,BIO,DES,DH,DSA,EC,ECDH,ECDSA,ENGINE,EVP,HMAC,MD4,MD5,' - 'PSK,RC2,RC4,RSA,SHA,SHA0,SHA1,SHA256,SHA512,SOCK,STDIO,TLSEXT,' - 'UI,FP_API,TLS1_METHOD,TLS1_1_METHOD,TLS1_2_METHOD,SCRYPT,OCSP,' - 'NEXTPROTONEG,RMD160,CAST,DEPRECATEDIN_1_1_0,DEPRECATEDIN_1_2_0,' - 'DEPRECATEDIN_3_0', + '-CAES,ARGON2,BF,BIO,DES,DH,DSA,EC,ECDH,ECDSA,ENGINE,EVP,HMAC,' + 'MD4,MD5,PSK,RC2,RC4,RSA,SHA,SHA0,SHA1,SHA256,SHA512,SOCK,STDIO,' + 'TLSEXT,UI,FP_API,TLS1_METHOD,TLS1_1_METHOD,TLS1_2_METHOD,' + 'SCRYPT,OCSP,NEXTPROTONEG,RMD160,CAST,DEPRECATEDIN_1_1_0,' + 'DEPRECATEDIN_1_2_0,DEPRECATEDIN_3_0', # Defines. '-DWIN32', # Symbols to filter from the export list. diff --git a/src/async_wrap.h b/src/async_wrap.h index 01e981aa671a23..c8de3f058b4734 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -89,6 +89,7 @@ namespace node { V(KEYPAIRGENREQUEST) \ V(KEYGENREQUEST) \ V(KEYEXPORTREQUEST) \ + V(ARGON2REQUEST) \ V(CIPHERREQUEST) \ V(DERIVEBITSREQUEST) \ V(HASHREQUEST) \ diff --git a/src/crypto/README.md b/src/crypto/README.md index ceefda03976ba9..34eca7e31a12d5 100644 --- a/src/crypto/README.md +++ b/src/crypto/README.md @@ -33,6 +33,7 @@ following table: | File (\*.h/\*.cc) | Description | | -------------------- | -------------------------------------------------------------------------- | | `crypto_aes` | AES Cipher support. | +| `crypto_argon2` | Argon2 key / bit generation implementation. | | `crypto_cipher` | General Encryption/Decryption utilities. | | `crypto_clienthello` | TLS/SSL client hello parser implementation. Used during SSL/TLS handshake. | | `crypto_context` | Implementation of the `SecureContext` object. | diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc new file mode 100644 index 00000000000000..97ebcaa17a7ddf --- /dev/null +++ b/src/crypto/crypto_argon2.cc @@ -0,0 +1,257 @@ +#include "crypto/crypto_argon2.h" +#include "async_wrap-inl.h" +#include "crypto/crypto_util.h" +#include "env-inl.h" +#include "memory_tracker-inl.h" +#include "node_buffer.h" +#include "threadpoolwork-inl.h" +#include "v8.h" + +#include /* OSSL_KDF_* */ + +namespace node { + +using v8::FunctionCallbackInfo; +using v8::Int32; +using v8::Just; +using v8::Maybe; +using v8::Nothing; +using v8::Uint32; +using v8::Value; + +namespace crypto { +#ifndef OPENSSL_NO_ARGON2 + +Argon2Config::Argon2Config(Argon2Config&& other) noexcept + : mode(other.mode), + pass(std::move(other.pass)), + salt(std::move(other.salt)), + secret(std::move(other.secret)), + ad(std::move(other.ad)), + algorithm(other.algorithm), + iter(other.iter), + // threads(other.threads), + lanes(other.lanes), + memcost(other.memcost), + keylen(other.keylen) {} + +Argon2Config& Argon2Config::operator=(Argon2Config&& other) noexcept { + if (&other == this) return *this; + this->~Argon2Config(); + return *new (this) Argon2Config(std::move(other)); +} + +void Argon2Config::MemoryInfo(MemoryTracker* tracker) const { + if (mode == kCryptoJobAsync) { + tracker->TrackFieldWithSize("pass", pass.size()); + tracker->TrackFieldWithSize("salt", salt.size()); + tracker->TrackFieldWithSize("secret", secret.size()); + tracker->TrackFieldWithSize("ad", ad.size()); + } +} + +static Argon2Variant algorithmToVariant(std::string_view sv) { + if (sv == "ARGON2I") { + return kVariantArgon2_ARGON2I; + } + + if (sv == "ARGON2D") { + return kVariantArgon2_ARGON2D; + } + + if (sv == "ARGON2ID") { + return kVariantArgon2_ARGON2ID; + } + + return kVariantArgon2_UNKNOWN; +} + +static const char* variantToAlgorithm(Argon2Variant variant) { + switch (variant) { + case kVariantArgon2_ARGON2I: + return "ARGON2I"; + case kVariantArgon2_ARGON2D: + return "ARGON2D"; + case kVariantArgon2_ARGON2ID: + return "ARGON2ID"; + default: + return nullptr; + } +} + +static bool argon2_hash( + const char* pass, size_t passlen, + const char* salt, size_t saltlen, + Argon2Variant variant, + const char* secret, size_t secretlen, + const char* ad, size_t adlen, + uint32_t iter, uint32_t lanes, uint32_t memcost, + unsigned char* key, size_t keylen +) { + const auto algorithm = variantToAlgorithm(variant); + + auto kdf = EVPKdfPointer{EVP_KDF_fetch(nullptr, algorithm, nullptr)}; + if (!kdf) { + return false; + } + + auto kctx = EVPKdfCtxPointer{EVP_KDF_CTX_new(kdf.get())}; + if (!kctx) { + return false; + } + + std::vector params; + + params.push_back(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, + const_cast(pass), + passlen)); + + params.push_back(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, + const_cast(salt), + saltlen)); + + if (secretlen > 0) { + params.push_back( + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, + const_cast(secret), + secretlen)); + } + + params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); + params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, + &lanes)); + + if (adlen > 0) { + params.push_back( + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_ARGON2_AD, + const_cast(ad), + adlen)); + } + + params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, + &memcost)); + + params.push_back(OSSL_PARAM_construct_end()); + + int result = EVP_KDF_derive(kctx.get(), key, keylen, params.data()); + return result == 1; +} + +Maybe Argon2Traits::EncodeOutput( + Environment* env, + const Argon2Config& params, + ByteSource* out, + v8::Local* result) { + *result = out->ToArrayBuffer(env); + return Just(!result->IsEmpty()); +} + +Maybe Argon2Traits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + Argon2Config* params) { + Environment* env = Environment::GetCurrent(args); + + params->mode = mode; + + ArrayBufferOrViewContents pass(args[offset]); + ArrayBufferOrViewContents salt(args[offset + 1]); + Utf8Value algorithm(env->isolate(), args[offset + 2]); + ArrayBufferOrViewContents secret(args[offset + 3]); + ArrayBufferOrViewContents ad(args[offset + 4]); + + if (UNLIKELY(!pass.CheckSizeInt32())) { + THROW_ERR_OUT_OF_RANGE(env, "pass is too large"); + return Nothing(); + } + + if (UNLIKELY(!salt.CheckSizeInt32())) { + THROW_ERR_OUT_OF_RANGE(env, "salt is too large"); + return Nothing(); + } + + if (UNLIKELY(!secret.CheckSizeInt32())) { + THROW_ERR_OUT_OF_RANGE(env, "secret is too large"); + return Nothing(); + } + + if (UNLIKELY(!ad.CheckSizeInt32())) { + THROW_ERR_OUT_OF_RANGE(env, "ad is too large"); + return Nothing(); + } + + params->algorithm = algorithmToVariant(algorithm.ToStringView()); + if (UNLIKELY(params->algorithm == kVariantArgon2_UNKNOWN)) { + THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); + return Nothing(); + } + + const bool isAsync = mode == kCryptoJobAsync; + params->pass = isAsync ? pass.ToCopy() : pass.ToByteSource(); + params->salt = isAsync ? salt.ToCopy() : salt.ToByteSource(); + params->secret = isAsync ? secret.ToCopy() : secret.ToByteSource(); + params->ad = isAsync ? ad.ToCopy() : ad.ToByteSource(); + + CHECK(args[offset + 5]->IsUint32()); // iter + CHECK(args[offset + 6]->IsUint32()); // lanes + CHECK(args[offset + 7]->IsUint32()); // memcost + CHECK(args[offset + 8]->IsUint32()); // keylen + + params->iter = args[offset + 5].As()->Value(); + params->lanes = args[offset + 6].As()->Value(); + params->memcost = args[offset + 7].As()->Value(); + params->keylen = args[offset + 8].As()->Value(); + + if (!argon2_hash(params->pass.data(), + params->pass.size(), + params->salt.data(), + params->salt.size(), + params->algorithm, + params->secret.data(), + params->secret.size(), + params->ad.data(), + params->ad.size(), + params->iter, + params->lanes, + params->memcost, + nullptr, + params->keylen)) { + THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); + return Nothing(); + } + + return Just(true); +} + +bool Argon2Traits::DeriveBits( + Environment* env, + const Argon2Config& params, + ByteSource* out) { + ByteSource::Builder buf(params.keylen); + + // Both the pass and salt may be zero-length at this point + if (!argon2_hash(params.pass.data(), + params.pass.size(), + params.salt.data(), + params.salt.size(), + params.algorithm, + params.secret.data(), + params.secret.size(), + params.ad.data(), + params.ad.size(), + params.iter, + params.lanes, + params.memcost, + buf.data(), + params.keylen)) { + return false; + } + *out = std::move(buf).release(); + return true; +} + +#endif // !OPENSSL_NO_ARGON2 + +} // namespace crypto +} // namespace node diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h new file mode 100644 index 00000000000000..b3afc43f106fde --- /dev/null +++ b/src/crypto/crypto_argon2.h @@ -0,0 +1,97 @@ +#ifndef SRC_CRYPTO_CRYPTO_ARGON2_H_ +#define SRC_CRYPTO_CRYPTO_ARGON2_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "crypto/crypto_util.h" +#include "env.h" +#include "memory_tracker.h" +#include "v8.h" + +namespace node { +namespace crypto { +#ifndef OPENSSL_NO_ARGON2 + +// Argon2 is a password-based key derivation algorithm +// defined in https://datatracker.ietf.org/doc/html/rfc9106 + +// It takes as input a password, a salt value, and a +// handful of additional parameters that control the +// cost of the operation. In this case, the higher +// the cost, the better the result. The length parameter +// defines the number of bytes that are generated. + +// The salt must be as random as possible and should be +// at least 16 bytes in length. + +enum Argon2Variant { + kVariantArgon2_ARGON2D, + kVariantArgon2_ARGON2I, + kVariantArgon2_ARGON2ID, + kVariantArgon2_UNKNOWN, +}; + +struct Argon2Config final : public MemoryRetainer { + CryptoJobMode mode; + ByteSource pass; + ByteSource salt; + ByteSource secret; + ByteSource ad; + Argon2Variant algorithm; + uint32_t iter; + // TODO(ranisalt): uint32_t threads; + uint32_t lanes; + uint32_t memcost; + uint32_t keylen; + + Argon2Config() = default; + + explicit Argon2Config(Argon2Config&& other) noexcept; + + Argon2Config& operator=(Argon2Config&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(Argon2Config) + SET_SELF_SIZE(Argon2Config) +}; + +struct Argon2Traits final { + using AdditionalParameters = Argon2Config; + static constexpr const char* JobName = "Argon2Job"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_ARGON2REQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + Argon2Config* params); + + static bool DeriveBits( + Environment* env, + const Argon2Config& params, + ByteSource* out); + + static v8::Maybe EncodeOutput( + Environment* env, + const Argon2Config& params, + ByteSource* out, + v8::Local* result); +}; + +using Argon2Job = DeriveBitsJob; + +#else +// If there is no Argon2 support, Argon2Job becomes a non-op +struct Argon2Job { + static void Initialize( + Environment* env, + v8::Local target) {} +}; +#endif // !OPENSSL_NO_ARGON2 + +} // namespace crypto +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_ARGON2_H_ diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index 1ce5f35a70a7c8..f7ca7e8a541666 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -62,6 +62,8 @@ using SSLPointer = DeleteFnPtr; using PKCS8Pointer = DeleteFnPtr; using EVPKeyPointer = DeleteFnPtr; using EVPKeyCtxPointer = DeleteFnPtr; +using EVPKdfPointer = DeleteFnPtr; +using EVPKdfCtxPointer = DeleteFnPtr; using EVPMDPointer = DeleteFnPtr; using RSAPointer = DeleteFnPtr; using ECPointer = DeleteFnPtr; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index a94ef62d2c6d78..dd8f1d521ca417 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -59,6 +59,12 @@ namespace crypto { V(Verify) \ V(X509Certificate) +#ifdef OPENSSL_NO_ARGON2 +#define ARGON2_NAMESPACE_LIST(V) +#else +#define ARGON2_NAMESPACE_LIST(V) V(Argon2Job) +#endif // OPENSSL_NO_ARGON2 + #ifdef OPENSSL_NO_SCRYPT #define SCRYPT_NAMESPACE_LIST(V) #else @@ -67,6 +73,7 @@ namespace crypto { #define CRYPTO_NAMESPACE_LIST(V) \ CRYPTO_NAMESPACE_LIST_BASE(V) \ + ARGON2_NAMESPACE_LIST(V) \ SCRYPT_NAMESPACE_LIST(V) void Initialize(Local target, diff --git a/src/node_crypto.h b/src/node_crypto.h index 04d44599854226..96aced41c21957 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -29,6 +29,7 @@ // remains for convenience for any code that still imports it. New // code should include the relevant src/crypto headers directly. #include "crypto/crypto_aes.h" +#include "crypto/crypto_argon2.h" #include "crypto/crypto_bio.h" #include "crypto/crypto_cipher.h" #include "crypto/crypto_context.h" diff --git a/src/node_errors.h b/src/node_errors.h index 0f4a2d0cc6eaaf..4df50ba54ac114 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -39,6 +39,7 @@ void AppendExceptionLine(Environment* env, V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \ V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \ V(ERR_CRYPTO_INITIALIZATION_FAILED, Error) \ + V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, RangeError) \ V(ERR_CRYPTO_INVALID_AUTH_TAG, TypeError) \ V(ERR_CRYPTO_INVALID_COUNTER, TypeError) \ V(ERR_CRYPTO_INVALID_CURVE, TypeError) \ @@ -137,6 +138,7 @@ ERRORS_WITH_CODE(V) V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \ V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \ V(ERR_CRYPTO_INITIALIZATION_FAILED, "Initialization failed") \ + V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, "Invalid Argon2 params") \ V(ERR_CRYPTO_INVALID_AUTH_TAG, "Invalid authentication tag") \ V(ERR_CRYPTO_INVALID_COUNTER, "Invalid counter") \ V(ERR_CRYPTO_INVALID_CURVE, "Invalid EC curve name") \ diff --git a/test/parallel/test-crypto-argon2.js b/test/parallel/test-crypto-argon2.js new file mode 100644 index 00000000000000..aa9985b846c9d5 --- /dev/null +++ b/test/parallel/test-crypto-argon2.js @@ -0,0 +1,167 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('node:assert'); +const crypto = require('node:crypto'); + +function runArgon2(password, salt, keylen, options) { + const syncResult = + crypto.argon2Sync(password, salt, keylen, options); + + crypto.argon2(password, salt, keylen, options, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(asyncResult, syncResult); + })); + + return syncResult; +} + +const password = Buffer.alloc(32, 0x01); +const salt = Buffer.alloc(16, 0x02); +const secret = Buffer.alloc(8, 0x03); +const ad = Buffer.alloc(12, 0x04); +const defaults = { password, salt, keylen: 32, iter: 3, lanes: 4, memcost: 64 << 10 }; + +const good = [ + [ + { algorithm: "ARGON2D" }, + 'bf37a2a7530e053a8bd2784a9d50dc7de451e33bd581096922bc7f9ef66020ed', + ], + [ + { algorithm: "ARGON2I" }, + '96334882febdd85eb9b2cf367479fbad2d5b87cf79f9076f51b23589560a2e0a', + ], + [ + { algorithm: "ARGON2ID" }, + 'fedd802b86b17230843c6d3f025c81d3f472fbf9daaf26897fa88844732167ec', + ], + [ + { secret }, + '71d8a1361e3660f8e8158021a6870f223d42342c6a194429252c6d6ad19c25d6', + ], + [ + { ad }, + '7da97d5dd2ba433dbb1b26e00a496caa673501b9a437e863855201eb51cea6ef', + ], + [ + { secret, ad }, + '47a677ebc68412b8e17949c921fdca065352114ffb2a09eebe61adc451eecf1e', + ], + [ + { iter: 2 }, + '2139b90863e8bbffff1459c9f08f6460db08def592991e78f11b6c5765bd1fac', + ], + [ + { lanes: 3 }, + '8ba6f33b93c8219cd31385966898fd39c8e570671c6a3c180ee0109a27d62a4b', + ], + [ + { memcost: 32 << 10 }, + '2beec876bf5b1098e8349d094385eda1dd472733d103e0f1afad4d28f948e808', + ], +]; + +// Test vectors that should fail. +const bad = [ + [{ iter: 0 }, "iter"], // iter < 2 + [{ iter: 2 ** 32 }, "iter"], // iter > 2^(32)-1 + [{ lanes: 0 }, "lanes"], // lanes < 1 + [{ lanes: 2 ** 24 }, "lanes"], // lanes > 2^(24)-1 + [{ lanes: 4, memcost: 16 }, "memcost"], // memcost < 8 * lanes + [{ memcost: 2 ** 32 }, "memcost"], // memcost > 2^(32)-1 +]; + +const badargs = [ + { + args: [], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, + }, + { + args: [null], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, + }, + { + args: [password], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, + }, + { + args: [password, null], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, + }, + { + args: [password, salt.subarray(0, 7)], + expected: { code: 'ERR_CRYPTO_INVALID_SALT_LENGTH', message: /salt length/ }, + }, + { + args: [password, salt], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, + }, + { + args: [password, salt, null], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, + }, + { + args: [password, salt, .42], + expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, + }, + { + args: [password, salt, -42], + expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, + }, + { + args: [password, salt, 3], + expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, + }, + { + args: [password, salt, 2 ** 32], + expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, + }, +]; + +for (const [overrides, expected] of good) { + const { password, salt, keylen, ...options } = { ...defaults, ...overrides }; + const actual = runArgon2(password, salt, keylen, options); + assert.strictEqual(actual.toString('hex'), expected); +} + +for (const [options, param] of bad) { + const expected = { + code:'ERR_OUT_OF_RANGE', + message: new RegExp(`The value of "${param}" is out of range.`), + }; + assert.throws(() => crypto.argon2(password, salt, 32, options, () => {}), + expected); + assert.throws(() => crypto.argon2Sync(password, salt, 32, options), + expected); +} + +{ + const expected = '07a0f408fd6bb1190be8e1600f680304e4680f72af121c3399308f48aba67ce4'; + const actual = crypto.argon2Sync('', salt, 32, defaults); + assert.strictEqual(actual.toString('hex'), expected); +} + +{ + const expected = crypto.argon2Sync(password, salt, 32, defaults); + const actual = crypto.argon2Sync(password, salt, 32); + assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex')); + crypto.argon2(password, salt, 32, common.mustSucceed((actual) => { + assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex')); + })); +} + +for (const { args, expected } of badargs) { + assert.throws(() => crypto.argon2(...args), expected); + assert.throws(() => crypto.argon2Sync(...args), expected); +} + +{ + const expected = { code: 'ERR_INVALID_ARG_TYPE' }; + assert.throws(() => crypto.argon2(password, salt, 32, null), expected); + assert.throws(() => crypto.argon2(password, salt, 32, {}, null), expected); + assert.throws(() => crypto.argon2(password, salt, 32, {}), expected); + assert.throws(() => crypto.argon2(password, salt, 32, {}, {}), expected); +} From 0af7016e0e42200ad3b6ea618d3524d3442542b8 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Tue, 24 Oct 2023 01:59:48 +0200 Subject: [PATCH 02/17] doc: add docs for crypto.argon2{,Sync} --- doc/api/crypto.md | 150 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 194f3cb9bb1024..9180f710a0d81f 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -2902,6 +2902,155 @@ is currently in use. Setting to true requires a FIPS build of Node.js. This property is deprecated. Please use `crypto.setFips()` and `crypto.getFips()` instead. +### `crypto.argon2(password, salt, keylen[, options], callback)` + + + +* `password` {string|ArrayBuffer|Buffer|TypedArray|DataView} +* `salt` {string|ArrayBuffer|Buffer|TypedArray|DataView} +* `keylen` {number} +* `options` {Object} + * `algorithm` {string} Variant of Argon2, one of "ARGON2D", "ARGON2I" or + "ARGON2ID". **Default:** `"ARGON2ID`. + * `iter` {number} Number of iterations (passes). **Default:** `3`. + * `lanes` {number} Parallelization parameter. **Default:** `4`. + * `memcost` {number} Memory cost in 1KiB blocks. **Default:** `65536`. + * `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView} Random additional + input, similar to the salt, that should **NOT** be stored with the derived + key. Also known as a pepper. + * `ad` {string|ArrayBuffer|Buffer|TypedArray|DataView} Additional data to be + added to the hash, functionally equivalent to salt or secret, but meant for + non-random data. +* `callback` {Function} + * `err` {Error} + * `derivedKey` {Buffer} + +Provides an asynchronous [argon2][] implementation. Argon2 is a password-based +key derivation function that is designed to be expensive computationally and +memory-wise in order to make brute-force attacks unrewarding. + +The `salt` should be as unique as possible. It is recommended that a salt is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. + +When passing strings for `password`, `salt`, `secret` or `ad`, please consider +[caveats when using strings as inputs to cryptographic APIs][]. + +The `callback` function is called with two arguments: `err` and `derivedKey`. +`err` is an exception object when key derivation fails, otherwise `err` is +`null`. `derivedKey` is passed to the callback as a [`Buffer`][]. + +An exception is thrown when any of the input arguments specify invalid values +or types. + +```mjs +const { + argon2, + randomBytes, +} = await import('node:crypto'); + +const salt = randomBytes(16); +// Using the factory defaults. +argon2('password', salt, 64, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' +}); +// Using a custom iter parameter. +argon2('password', salt, 64, { iter: 3 }, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' +}); +``` + +```cjs +const { + argon2, + randomBytes, +} = require('node:crypto'); + +const salt = randomBytes(16); +// Using the factory defaults. +argon2('password', salt, 64, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' +}); +// Using a custom iter parameter. +argon2('password', salt, 64, { iter: 3 }, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' +}); +``` + +### `crypto.argon2Sync(password, salt, keylen[, options])` + + + +* `password` {string|Buffer|TypedArray|DataView} +* `salt` {string|Buffer|TypedArray|DataView} +* `keylen` {number} +* `options` {Object} + * `algorithm` {string} Variant of Argon2, one of "ARGON2D", "ARGON2I" or + "ARGON2ID". **Default:** `"ARGON2ID`. + * `iter` {number} Number of iterations (passes). **Default:** `3`. + * `lanes` {number} Parallelization parameter. **Default:** `4`. + * `memcost` {number} Memory cost in 1KiB blocks. **Default:** `65536`. + * `secret` {string|Buffer|TypedArray|DataView} Random additional input, + similar to the salt, that should **NOT** be stored with the derived key. + Also known as a pepper. + * `ad` {string|Buffer|TypedArray|DataView} Additional data to be added to the + hash, functionally equivalent to salt or secret, but meant for non-random + data. +* Returns: {Buffer} + +Provides a synchronous [argon2][] implementation. Argon2 is a password-based +key derivation function that is designed to be expensive computationally and +memory-wise in order to make brute-force attacks unrewarding. + +The `salt` should be as unique as possible. It is recommended that a salt is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. + +When passing strings for `password`, `salt`, `secret` or `ad`, please consider +[caveats when using strings as inputs to cryptographic APIs][]. + +An exception is thrown when key derivation fails, otherwise the derived key is +returned as a [`Buffer`][]. + +An exception is thrown when any of the input arguments specify invalid values +or types. + +```mjs +const { + argon2Sync, + randomBytes, +} = await import('node:crypto'); +// Using the factory defaults. + +const salt = randomBytes(16); +const key1 = argon2Sync('password', salt, 64); +console.log(key1.toString('hex')); // '3745e48...08d59ae' +// Using a custom iter parameter. +const key2 = argon2Sync('password', salt, 64, { iter: 3 }); +console.log(key2.toString('hex')); // '3745e48...aa39b34' +``` + +```cjs +const { + argon2Sync, + randomBytes, +} = require('node:crypto'); +// Using the factory defaults. + +const salt = randomBytes(16); +const key1 = argon2Sync('password', salt, 64); +console.log(key1.toString('hex')); // '3745e48...08d59ae' +// Using a custom iter parameter. +const key2 = argon2Sync('password', salt, 64, { iter: 3 }); +console.log(key2.toString('hex')); // '3745e48...aa39b34' +``` + ### `crypto.checkPrime(candidate[, options], callback)` * `password` {string|ArrayBuffer|Buffer|TypedArray|DataView} @@ -2969,23 +2969,28 @@ const { randomBytes, } = require('node:crypto'); -const salt = randomBytes(16); // Using the factory defaults. -argon2('password', salt, 64, (err, derivedKey) => { +randomBytes(16, (err, salt) => { if (err) throw err; - console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' + argon2('password', salt, 64, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' + }); }); // Using a custom iter parameter. -argon2('password', salt, 64, { iter: 3 }, (err, derivedKey) => { +randomBytes(16, (err, salt) => { if (err) throw err; - console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' + argon2('password', salt, 64, { iter: 3 }, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '0de3036...22afcc5' + }); }); ``` ### `crypto.argon2Sync(password, salt, keylen[, options])` * `password` {string|Buffer|TypedArray|DataView} From 0a3bb3653da9238dceac96378d05e68af1014d51 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sun, 29 Oct 2023 17:41:08 +0100 Subject: [PATCH 04/17] Store KDF pointer rather than converting algorithm name --- lib/internal/crypto/argon2.js | 3 +- src/crypto/crypto_argon2.cc | 144 ++++++++++++---------------------- src/crypto/crypto_argon2.h | 2 +- 3 files changed, 54 insertions(+), 95 deletions(-) diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js index 59ce1343935de4..ecb126bf3efce7 100644 --- a/lib/internal/crypto/argon2.js +++ b/lib/internal/crypto/argon2.js @@ -13,7 +13,6 @@ const { } = internalBinding('crypto'); const { getArrayBufferOrView } = require('internal/crypto/util'); -const { lazyDOMException } = require('internal/util'); const { validateFunction, validateInteger } = require('internal/validators'); const { @@ -129,7 +128,7 @@ function check(password, salt, keylen, options) { const has_lanes = options.lanes !== undefined; if (has_lanes) { lanes = options.lanes; - validateInteger(options.lanes, 'lanes', 1, (2 ** 24) - 1); + validateInteger(lanes, 'lanes', 1, (2 ** 24) - 1); } if (iter === 0) iter = defaults.iter; if (lanes === 0) lanes = defaults.lanes; diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 97ebcaa17a7ddf..0cedf838822479 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -7,7 +7,7 @@ #include "threadpoolwork-inl.h" #include "v8.h" -#include /* OSSL_KDF_* */ +#include /* OSSL_KDF_* */ namespace node { @@ -23,17 +23,17 @@ namespace crypto { #ifndef OPENSSL_NO_ARGON2 Argon2Config::Argon2Config(Argon2Config&& other) noexcept - : mode(other.mode), - pass(std::move(other.pass)), - salt(std::move(other.salt)), - secret(std::move(other.secret)), - ad(std::move(other.ad)), - algorithm(other.algorithm), - iter(other.iter), - // threads(other.threads), - lanes(other.lanes), - memcost(other.memcost), - keylen(other.keylen) {} + : mode(other.mode), + kdf(std::move(other.kdf)), + pass(std::move(other.pass)), + salt(std::move(other.salt)), + secret(std::move(other.secret)), + ad(std::move(other.ad)), + iter(other.iter), + // threads(other.threads), + lanes(other.lanes), + memcost(other.memcost), + keylen(other.keylen) {} Argon2Config& Argon2Config::operator=(Argon2Config&& other) noexcept { if (&other == this) return *this; @@ -50,51 +50,20 @@ void Argon2Config::MemoryInfo(MemoryTracker* tracker) const { } } -static Argon2Variant algorithmToVariant(std::string_view sv) { - if (sv == "ARGON2I") { - return kVariantArgon2_ARGON2I; - } - - if (sv == "ARGON2D") { - return kVariantArgon2_ARGON2D; - } - - if (sv == "ARGON2ID") { - return kVariantArgon2_ARGON2ID; - } - - return kVariantArgon2_UNKNOWN; -} - -static const char* variantToAlgorithm(Argon2Variant variant) { - switch (variant) { - case kVariantArgon2_ARGON2I: - return "ARGON2I"; - case kVariantArgon2_ARGON2D: - return "ARGON2D"; - case kVariantArgon2_ARGON2ID: - return "ARGON2ID"; - default: - return nullptr; - } -} - -static bool argon2_hash( - const char* pass, size_t passlen, - const char* salt, size_t saltlen, - Argon2Variant variant, - const char* secret, size_t secretlen, - const char* ad, size_t adlen, - uint32_t iter, uint32_t lanes, uint32_t memcost, - unsigned char* key, size_t keylen -) { - const auto algorithm = variantToAlgorithm(variant); - - auto kdf = EVPKdfPointer{EVP_KDF_fetch(nullptr, algorithm, nullptr)}; - if (!kdf) { - return false; - } - +static bool argon2_hash(const EVPKdfPointer& kdf, + const char* pass, + size_t passlen, + const char* salt, + size_t saltlen, + const char* secret, + size_t secretlen, + const char* ad, + size_t adlen, + uint32_t iter, + uint32_t lanes, + uint32_t memcost, + unsigned char* key, + size_t keylen) { auto kctx = EVPKdfCtxPointer{EVP_KDF_CTX_new(kdf.get())}; if (!kctx) { return false; @@ -102,46 +71,38 @@ static bool argon2_hash( std::vector params; - params.push_back(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, - const_cast(pass), - passlen)); + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_PASSWORD, const_cast(pass), passlen)); - params.push_back(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, - const_cast(salt), - saltlen)); + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_SALT, const_cast(salt), saltlen)); if (secretlen > 0) { - params.push_back( - OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, - const_cast(secret), - secretlen)); + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_SECRET, const_cast(secret), secretlen)); } params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); - params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, - &lanes)); + params.push_back( + OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes)); if (adlen > 0) { - params.push_back( - OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_ARGON2_AD, - const_cast(ad), - adlen)); + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_ARGON2_AD, const_cast(ad), adlen)); } - params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, - &memcost)); + params.push_back( + OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost)); params.push_back(OSSL_PARAM_construct_end()); - int result = EVP_KDF_derive(kctx.get(), key, keylen, params.data()); - return result == 1; + return EVP_KDF_derive(kctx.get(), key, keylen, params.data()); } -Maybe Argon2Traits::EncodeOutput( - Environment* env, - const Argon2Config& params, - ByteSource* out, - v8::Local* result) { +Maybe Argon2Traits::EncodeOutput(Environment* env, + const Argon2Config& params, + ByteSource* out, + v8::Local* result) { *result = out->ToArrayBuffer(env); return Just(!result->IsEmpty()); } @@ -181,8 +142,8 @@ Maybe Argon2Traits::AdditionalConfig( return Nothing(); } - params->algorithm = algorithmToVariant(algorithm.ToStringView()); - if (UNLIKELY(params->algorithm == kVariantArgon2_UNKNOWN)) { + params->kdf = EVPKdfPointer{EVP_KDF_fetch(nullptr, algorithm.out(), nullptr)}; + if (!params->kdf) { THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); return Nothing(); } @@ -203,11 +164,11 @@ Maybe Argon2Traits::AdditionalConfig( params->memcost = args[offset + 7].As()->Value(); params->keylen = args[offset + 8].As()->Value(); - if (!argon2_hash(params->pass.data(), + if (!argon2_hash(params->kdf, + params->pass.data(), params->pass.size(), params->salt.data(), params->salt.size(), - params->algorithm, params->secret.data(), params->secret.size(), params->ad.data(), @@ -224,18 +185,17 @@ Maybe Argon2Traits::AdditionalConfig( return Just(true); } -bool Argon2Traits::DeriveBits( - Environment* env, - const Argon2Config& params, - ByteSource* out) { +bool Argon2Traits::DeriveBits(Environment* env, + const Argon2Config& params, + ByteSource* out) { ByteSource::Builder buf(params.keylen); // Both the pass and salt may be zero-length at this point - if (!argon2_hash(params.pass.data(), + if (!argon2_hash(params.kdf, + params.pass.data(), params.pass.size(), params.salt.data(), params.salt.size(), - params.algorithm, params.secret.data(), params.secret.size(), params.ad.data(), diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index b3afc43f106fde..89d56a9e63cdbe 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -33,11 +33,11 @@ enum Argon2Variant { struct Argon2Config final : public MemoryRetainer { CryptoJobMode mode; + EVPKdfPointer kdf; ByteSource pass; ByteSource salt; ByteSource secret; ByteSource ad; - Argon2Variant algorithm; uint32_t iter; // TODO(ranisalt): uint32_t threads; uint32_t lanes; From 2e6a3e535a5748239fa9b5aba559d407cfd239f8 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sun, 29 Oct 2023 18:43:59 +0100 Subject: [PATCH 05/17] Fix double execution --- src/crypto/crypto_argon2.cc | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 0cedf838822479..143580b80a725a 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -164,24 +164,6 @@ Maybe Argon2Traits::AdditionalConfig( params->memcost = args[offset + 7].As()->Value(); params->keylen = args[offset + 8].As()->Value(); - if (!argon2_hash(params->kdf, - params->pass.data(), - params->pass.size(), - params->salt.data(), - params->salt.size(), - params->secret.data(), - params->secret.size(), - params->ad.data(), - params->ad.size(), - params->iter, - params->lanes, - params->memcost, - nullptr, - params->keylen)) { - THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); - return Nothing(); - } - return Just(true); } From ffa31bff540b3a6e56e252eee2f6119d1f871770 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sun, 29 Oct 2023 18:44:14 +0100 Subject: [PATCH 06/17] Add benchmark for Argon2 --- benchmark/crypto/argon2.js | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 benchmark/crypto/argon2.js diff --git a/benchmark/crypto/argon2.js b/benchmark/crypto/argon2.js new file mode 100644 index 00000000000000..ed40bd7c5a905b --- /dev/null +++ b/benchmark/crypto/argon2.js @@ -0,0 +1,46 @@ +'use strict'; + +const common = require('../common.js'); +const assert = require('node:assert'); +const { + argon2, + argon2Sync, + randomBytes, +} = require('node:crypto'); + +const bench = common.createBenchmark(main, { + sync: [0, 1], + algorithm: ['ARGON2D', 'ARGON2I', 'ARGON2ID'], + iter: [2, 3, 4], + memcost: [64 << 10, 256 << 10], + n: [50], +}); + +function measureSync(n, pass, salt, options) { + bench.start(); + for (let i = 0; i < n; ++i) + argon2Sync(pass, salt, 64, options); + bench.end(n); +} + +function measureAsync(n, pass, salt, options) { + let remaining = n; + function done(err) { + assert.ifError(err); + if (--remaining === 0) + bench.end(n); + } + bench.start(); + for (let i = 0; i < n; ++i) + argon2(pass, salt, 64, options, done); +} + +function main({ n, sync, ...options }) { + // pass, salt, secret, ad & output length does not affect performance + const pass = randomBytes(32); + const salt = randomBytes(16); + if (sync) + measureSync(n, pass, salt, options); + else + measureAsync(n, pass, salt, options); +} From 8ac31151c291c40d0a2d141c58ccb90e36250662 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Mon, 30 Oct 2023 01:38:46 +0100 Subject: [PATCH 07/17] Format & cleanup --- src/crypto/crypto_argon2.cc | 10 ++++------ src/crypto/crypto_argon2.h | 33 ++++++++++----------------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 143580b80a725a..048c2aba8d7c5b 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -9,17 +9,16 @@ #include /* OSSL_KDF_* */ -namespace node { +namespace node::crypto { using v8::FunctionCallbackInfo; -using v8::Int32; using v8::Just; +using v8::Local; using v8::Maybe; using v8::Nothing; using v8::Uint32; using v8::Value; -namespace crypto { #ifndef OPENSSL_NO_ARGON2 Argon2Config::Argon2Config(Argon2Config&& other) noexcept @@ -102,7 +101,7 @@ static bool argon2_hash(const EVPKdfPointer& kdf, Maybe Argon2Traits::EncodeOutput(Environment* env, const Argon2Config& params, ByteSource* out, - v8::Local* result) { + Local* result) { *result = out->ToArrayBuffer(env); return Just(!result->IsEmpty()); } @@ -195,5 +194,4 @@ bool Argon2Traits::DeriveBits(Environment* env, #endif // !OPENSSL_NO_ARGON2 -} // namespace crypto -} // namespace node +} // namespace node::crypto diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index 89d56a9e63cdbe..898bd7dd9219ed 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -8,8 +8,7 @@ #include "memory_tracker.h" #include "v8.h" -namespace node { -namespace crypto { +namespace node::crypto { #ifndef OPENSSL_NO_ARGON2 // Argon2 is a password-based key derivation algorithm @@ -24,13 +23,6 @@ namespace crypto { // The salt must be as random as possible and should be // at least 16 bytes in length. -enum Argon2Variant { - kVariantArgon2_ARGON2D, - kVariantArgon2_ARGON2I, - kVariantArgon2_ARGON2ID, - kVariantArgon2_UNKNOWN, -}; - struct Argon2Config final : public MemoryRetainer { CryptoJobMode mode; EVPKdfPointer kdf; @@ -67,16 +59,14 @@ struct Argon2Traits final { unsigned int offset, Argon2Config* params); - static bool DeriveBits( - Environment* env, - const Argon2Config& params, - ByteSource* out); + static bool DeriveBits(Environment* env, + const Argon2Config& params, + ByteSource* out); - static v8::Maybe EncodeOutput( - Environment* env, - const Argon2Config& params, - ByteSource* out, - v8::Local* result); + static v8::Maybe EncodeOutput(Environment* env, + const Argon2Config& params, + ByteSource* out, + v8::Local* result); }; using Argon2Job = DeriveBitsJob; @@ -84,14 +74,11 @@ using Argon2Job = DeriveBitsJob; #else // If there is no Argon2 support, Argon2Job becomes a non-op struct Argon2Job { - static void Initialize( - Environment* env, - v8::Local target) {} + static void Initialize(Environment* env, v8::Local target) {} }; #endif // !OPENSSL_NO_ARGON2 -} // namespace crypto -} // namespace node +} // namespace node::crypto #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_CRYPTO_CRYPTO_ARGON2_H_ From d9ad2c4854ff756efbd88a1d56264d009483b788 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Mon, 30 Oct 2023 03:00:06 +0100 Subject: [PATCH 08/17] Reduce branching and function calls --- src/crypto/crypto_argon2.cc | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 048c2aba8d7c5b..cb93058d4a1881 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -68,32 +68,27 @@ static bool argon2_hash(const EVPKdfPointer& kdf, return false; } - std::vector params; - - params.push_back(OSSL_PARAM_construct_octet_string( - OSSL_KDF_PARAM_PASSWORD, const_cast(pass), passlen)); - - params.push_back(OSSL_PARAM_construct_octet_string( - OSSL_KDF_PARAM_SALT, const_cast(salt), saltlen)); + auto params = std::vector{ + OSSL_PARAM_octet_string( + OSSL_KDF_PARAM_PASSWORD, const_cast(pass), passlen), + OSSL_PARAM_octet_string( + OSSL_KDF_PARAM_SALT, const_cast(salt), saltlen), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, &iter), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost), + }; if (secretlen > 0) { - params.push_back(OSSL_PARAM_construct_octet_string( + params.push_back(OSSL_PARAM_octet_string( OSSL_KDF_PARAM_SECRET, const_cast(secret), secretlen)); } - params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); - params.push_back( - OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes)); - if (adlen > 0) { - params.push_back(OSSL_PARAM_construct_octet_string( + params.push_back(OSSL_PARAM_octet_string( OSSL_KDF_PARAM_ARGON2_AD, const_cast(ad), adlen)); } - params.push_back( - OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost)); - - params.push_back(OSSL_PARAM_construct_end()); + params.push_back(OSSL_PARAM_END); return EVP_KDF_derive(kctx.get(), key, keylen, params.data()); } @@ -142,7 +137,7 @@ Maybe Argon2Traits::AdditionalConfig( } params->kdf = EVPKdfPointer{EVP_KDF_fetch(nullptr, algorithm.out(), nullptr)}; - if (!params->kdf) { + if (UNLIKELY(!params->kdf)) { THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); return Nothing(); } From 539ead1cfee6139cac354c495734aa3d8f89cbf7 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Mon, 30 Oct 2023 03:03:36 +0100 Subject: [PATCH 09/17] Cleanup includes --- src/crypto/crypto_argon2.cc | 7 ------- src/crypto/crypto_argon2.h | 3 --- 2 files changed, 10 deletions(-) diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index cb93058d4a1881..68dc0e787a7572 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -1,11 +1,4 @@ #include "crypto/crypto_argon2.h" -#include "async_wrap-inl.h" -#include "crypto/crypto_util.h" -#include "env-inl.h" -#include "memory_tracker-inl.h" -#include "node_buffer.h" -#include "threadpoolwork-inl.h" -#include "v8.h" #include /* OSSL_KDF_* */ diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index 898bd7dd9219ed..ab639bdb52517e 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -4,9 +4,6 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "crypto/crypto_util.h" -#include "env.h" -#include "memory_tracker.h" -#include "v8.h" namespace node::crypto { #ifndef OPENSSL_NO_ARGON2 From 1dba4e12dc74395a2e47cc79dd0d165466e6479a Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Mon, 30 Oct 2023 17:58:07 +0100 Subject: [PATCH 10/17] Inline argon2_hash --- src/crypto/crypto_argon2.cc | 98 +++++++++++++++---------------------- 1 file changed, 40 insertions(+), 58 deletions(-) diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 68dc0e787a7572..f9819454c990cd 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -42,50 +42,6 @@ void Argon2Config::MemoryInfo(MemoryTracker* tracker) const { } } -static bool argon2_hash(const EVPKdfPointer& kdf, - const char* pass, - size_t passlen, - const char* salt, - size_t saltlen, - const char* secret, - size_t secretlen, - const char* ad, - size_t adlen, - uint32_t iter, - uint32_t lanes, - uint32_t memcost, - unsigned char* key, - size_t keylen) { - auto kctx = EVPKdfCtxPointer{EVP_KDF_CTX_new(kdf.get())}; - if (!kctx) { - return false; - } - - auto params = std::vector{ - OSSL_PARAM_octet_string( - OSSL_KDF_PARAM_PASSWORD, const_cast(pass), passlen), - OSSL_PARAM_octet_string( - OSSL_KDF_PARAM_SALT, const_cast(salt), saltlen), - OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, &iter), - OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes), - OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost), - }; - - if (secretlen > 0) { - params.push_back(OSSL_PARAM_octet_string( - OSSL_KDF_PARAM_SECRET, const_cast(secret), secretlen)); - } - - if (adlen > 0) { - params.push_back(OSSL_PARAM_octet_string( - OSSL_KDF_PARAM_ARGON2_AD, const_cast(ad), adlen)); - } - - params.push_back(OSSL_PARAM_END); - - return EVP_KDF_derive(kctx.get(), key, keylen, params.data()); -} - Maybe Argon2Traits::EncodeOutput(Environment* env, const Argon2Config& params, ByteSource* out, @@ -159,21 +115,47 @@ bool Argon2Traits::DeriveBits(Environment* env, ByteSource* out) { ByteSource::Builder buf(params.keylen); + auto kctx = EVPKdfCtxPointer{EVP_KDF_CTX_new(params.kdf.get())}; + if (!kctx) { + return false; + } + + auto ossl_params = std::vector{ + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PASSWORD, + const_cast(params.pass.data()), + params.pass.size()), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, + const_cast(params.salt.data()), + params.salt.size()), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, + const_cast(¶ms.iter)), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, + const_cast(¶ms.lanes)), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, + const_cast(¶ms.memcost)), + }; + + if (params.secret.size() > 0) { + ossl_params.push_back( + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SECRET, + const_cast(params.secret.data()), + params.secret.size())); + } + + if (params.ad.size() > 0) { + ossl_params.push_back( + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_ARGON2_AD, + const_cast(params.ad.data()), + params.ad.size())); + } + + ossl_params.push_back(OSSL_PARAM_END); + // Both the pass and salt may be zero-length at this point - if (!argon2_hash(params.kdf, - params.pass.data(), - params.pass.size(), - params.salt.data(), - params.salt.size(), - params.secret.data(), - params.secret.size(), - params.ad.data(), - params.ad.size(), - params.iter, - params.lanes, - params.memcost, - buf.data(), - params.keylen)) { + if (EVP_KDF_derive(kctx.get(), + buf.data(), + params.keylen, + ossl_params.data()) != 1) { return false; } *out = std::move(buf).release(); From 7c05e529ef30f5451c3ea7c527795868430b3e45 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Mon, 30 Oct 2023 18:19:27 +0100 Subject: [PATCH 11/17] Better variable names --- src/crypto/crypto_argon2.cc | 68 ++++++++++++++++++------------------- src/crypto/crypto_argon2.h | 6 ++-- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index f9819454c990cd..5a1992af5ed46d 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -54,10 +54,10 @@ Maybe Argon2Traits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, unsigned int offset, - Argon2Config* params) { + Argon2Config* config) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; + config->mode = mode; ArrayBufferOrViewContents pass(args[offset]); ArrayBufferOrViewContents salt(args[offset + 1]); @@ -85,77 +85,77 @@ Maybe Argon2Traits::AdditionalConfig( return Nothing(); } - params->kdf = EVPKdfPointer{EVP_KDF_fetch(nullptr, algorithm.out(), nullptr)}; - if (UNLIKELY(!params->kdf)) { + config->kdf = EVPKdfPointer{EVP_KDF_fetch(nullptr, algorithm.out(), nullptr)}; + if (UNLIKELY(!config->kdf)) { THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); return Nothing(); } const bool isAsync = mode == kCryptoJobAsync; - params->pass = isAsync ? pass.ToCopy() : pass.ToByteSource(); - params->salt = isAsync ? salt.ToCopy() : salt.ToByteSource(); - params->secret = isAsync ? secret.ToCopy() : secret.ToByteSource(); - params->ad = isAsync ? ad.ToCopy() : ad.ToByteSource(); + config->pass = isAsync ? pass.ToCopy() : pass.ToByteSource(); + config->salt = isAsync ? salt.ToCopy() : salt.ToByteSource(); + config->secret = isAsync ? secret.ToCopy() : secret.ToByteSource(); + config->ad = isAsync ? ad.ToCopy() : ad.ToByteSource(); CHECK(args[offset + 5]->IsUint32()); // iter CHECK(args[offset + 6]->IsUint32()); // lanes CHECK(args[offset + 7]->IsUint32()); // memcost CHECK(args[offset + 8]->IsUint32()); // keylen - params->iter = args[offset + 5].As()->Value(); - params->lanes = args[offset + 6].As()->Value(); - params->memcost = args[offset + 7].As()->Value(); - params->keylen = args[offset + 8].As()->Value(); + config->iter = args[offset + 5].As()->Value(); + config->lanes = args[offset + 6].As()->Value(); + config->memcost = args[offset + 7].As()->Value(); + config->keylen = args[offset + 8].As()->Value(); return Just(true); } bool Argon2Traits::DeriveBits(Environment* env, - const Argon2Config& params, + const Argon2Config& config, ByteSource* out) { - ByteSource::Builder buf(params.keylen); + ByteSource::Builder buf(config.keylen); - auto kctx = EVPKdfCtxPointer{EVP_KDF_CTX_new(params.kdf.get())}; + auto kctx = EVPKdfCtxPointer{EVP_KDF_CTX_new(config.kdf.get())}; if (!kctx) { return false; } - auto ossl_params = std::vector{ + auto params = std::vector{ OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PASSWORD, - const_cast(params.pass.data()), - params.pass.size()), + const_cast(config.pass.data()), + config.pass.size()), OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, - const_cast(params.salt.data()), - params.salt.size()), + const_cast(config.salt.data()), + config.salt.size()), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, - const_cast(¶ms.iter)), + const_cast(&config.iter)), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, - const_cast(¶ms.lanes)), + const_cast(&config.lanes)), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, - const_cast(¶ms.memcost)), + const_cast(&config.memcost)), }; - if (params.secret.size() > 0) { - ossl_params.push_back( + if (config.secret.size() > 0) { + params.push_back( OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SECRET, - const_cast(params.secret.data()), - params.secret.size())); + const_cast(config.secret.data()), + config.secret.size())); } - if (params.ad.size() > 0) { - ossl_params.push_back( + if (config.ad.size() > 0) { + params.push_back( OSSL_PARAM_octet_string(OSSL_KDF_PARAM_ARGON2_AD, - const_cast(params.ad.data()), - params.ad.size())); + const_cast(config.ad.data()), + config.ad.size())); } - ossl_params.push_back(OSSL_PARAM_END); + params.push_back(OSSL_PARAM_END); // Both the pass and salt may be zero-length at this point if (EVP_KDF_derive(kctx.get(), buf.data(), - params.keylen, - ossl_params.data()) != 1) { + config.keylen, + params.data()) != 1) { return false; } *out = std::move(buf).release(); diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index ab639bdb52517e..2e61317168f02e 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -54,14 +54,14 @@ struct Argon2Traits final { CryptoJobMode mode, const v8::FunctionCallbackInfo& args, unsigned int offset, - Argon2Config* params); + Argon2Config* config); static bool DeriveBits(Environment* env, - const Argon2Config& params, + const Argon2Config& config, ByteSource* out); static v8::Maybe EncodeOutput(Environment* env, - const Argon2Config& params, + const Argon2Config& config, ByteSource* out, v8::Local* result); }; From 74b0237feddc52bf25074e9cf5678225b0648df2 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Tue, 31 Oct 2023 02:49:27 +0100 Subject: [PATCH 12/17] Add multithreading support --- benchmark/crypto/argon2.js | 6 ++++- lib/internal/crypto/argon2.js | 31 +++++++++++++++++-------- src/crypto/crypto_argon2.cc | 35 +++++++++++++++++++++-------- src/crypto/crypto_argon2.h | 3 ++- src/crypto/crypto_util.cc | 6 +++++ src/crypto/crypto_util.h | 9 ++++++++ test/parallel/test-crypto-argon2.js | 11 +++++++-- 7 files changed, 79 insertions(+), 22 deletions(-) diff --git a/benchmark/crypto/argon2.js b/benchmark/crypto/argon2.js index ed40bd7c5a905b..116c51f678656f 100644 --- a/benchmark/crypto/argon2.js +++ b/benchmark/crypto/argon2.js @@ -12,8 +12,12 @@ const bench = common.createBenchmark(main, { sync: [0, 1], algorithm: ['ARGON2D', 'ARGON2I', 'ARGON2ID'], iter: [2, 3, 4], - memcost: [64 << 10, 256 << 10], + threads: [2, 4, 8], + lanes: [2, 4, 8], + memcost: [16 << 10, 64 << 10], n: [50], +}, { + combinationFilter: ({ threads, lanes }) => threads <= lanes, }); function measureSync(n, pass, salt, options) { diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js index ecb126bf3efce7..3a57c8c97b762d 100644 --- a/lib/internal/crypto/argon2.js +++ b/lib/internal/crypto/argon2.js @@ -24,10 +24,11 @@ const { } = require('internal/errors'); const defaults = { - algorithm: "ARGON2ID", + algorithm: 'ARGON2ID', secret: Buffer.alloc(0), ad: Buffer.alloc(0), iter: 3, + threads: 4, lanes: 4, memcost: 64 << 10, // 64Ki * 1 KiB = 64 MiB keylen: 64, @@ -37,7 +38,14 @@ const defaults = { * @param {ArrayBufferLike} password * @param {ArrayBufferLike} salt * @param {number} keylen - * @param {*} [options] + * @param {object} [options] + * @param {'ARGON2D' | 'ARGON2I' | 'ARGON2ID'} [options.algorithm='ARGON2ID'] + * @param {ArrayBufferLike} [options.secret] + * @param {ArrayBufferLike} [options.ad] + * @param {number} [options.iter=3] + * @param {number} [options.threads=4] + * @param {number} [options.lanes=4] + * @param {number} [options.memcost=64 << 10] * @param {Function} callback */ function argon2(password, salt, keylen, options, callback = defaults) { @@ -47,13 +55,13 @@ function argon2(password, salt, keylen, options, callback = defaults) { } options = check(password, salt, keylen, options); - const { algorithm, secret, ad, iter, lanes, memcost } = options; + const { algorithm, secret, ad, iter, threads, lanes, memcost } = options; ({ password, salt, keylen } = options); validateFunction(callback, 'callback'); const job = new Argon2Job( - kCryptoJobAsync, password, salt, algorithm, secret, ad, iter, lanes, memcost, keylen); + kCryptoJobAsync, password, salt, algorithm, secret, ad, iter, threads, lanes, memcost, keylen); job.ondone = (error, result) => { if (error !== undefined) @@ -73,10 +81,10 @@ function argon2(password, salt, keylen, options, callback = defaults) { */ function argon2Sync(password, salt, keylen, options = defaults) { options = check(password, salt, keylen, options); - const { algorithm, secret, ad, iter, lanes, memcost } = options; + const { algorithm, secret, ad, iter, threads, lanes, memcost } = options; ({ password, salt, keylen } = options); const job = new Argon2Job( - kCryptoJobSync, password, salt, algorithm, secret, ad, iter, lanes, memcost, keylen); + kCryptoJobSync, password, salt, algorithm, secret, ad, iter, threads, lanes, memcost, keylen); const { 0: err, 1: result } = job.run(); if (err !== undefined) @@ -104,7 +112,7 @@ function check(password, salt, keylen, options) { validateInteger(keylen, 'keylen', 4, (2 ** 32) - 1); - let { algorithm, secret, ad, iter, lanes, memcost } = defaults; + let { algorithm, secret, ad, iter, threads, lanes, memcost } = defaults; if (options && options !== defaults) { const has_algorithm = options.algorithm !== undefined; if (has_algorithm) { @@ -125,10 +133,15 @@ function check(password, salt, keylen, options) { iter = options.iter; validateInteger(iter, 'iter', 1, (2 ** 32) - 1); } + const has_threads = options.threads !== undefined; + if (has_threads) { + threads = options.threads; + validateInteger(threads, 'threads', 1, (2 ** 24) - 1); + } const has_lanes = options.lanes !== undefined; if (has_lanes) { lanes = options.lanes; - validateInteger(lanes, 'lanes', 1, (2 ** 24) - 1); + validateInteger(lanes, 'lanes', threads, (2 ** 24) - 1); } if (iter === 0) iter = defaults.iter; if (lanes === 0) lanes = defaults.lanes; @@ -141,7 +154,7 @@ function check(password, salt, keylen, options) { if (memcost === 0) memcost = defaults.memcost; } - return { password, salt, algorithm, secret, ad, keylen, iter, lanes, memcost }; + return { password, salt, algorithm, secret, ad, keylen, iter, threads, lanes, memcost }; } module.exports = { diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 5a1992af5ed46d..cb79b4e06ff655 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -1,6 +1,6 @@ #include "crypto/crypto_argon2.h" -#include /* OSSL_KDF_* */ +#include namespace node::crypto { @@ -16,13 +16,14 @@ using v8::Value; Argon2Config::Argon2Config(Argon2Config&& other) noexcept : mode(other.mode), + ctx{std::move(other.ctx)}, kdf(std::move(other.kdf)), pass(std::move(other.pass)), salt(std::move(other.salt)), secret(std::move(other.secret)), ad(std::move(other.ad)), iter(other.iter), - // threads(other.threads), + threads(other.threads), lanes(other.lanes), memcost(other.memcost), keylen(other.keylen) {} @@ -85,7 +86,14 @@ Maybe Argon2Traits::AdditionalConfig( return Nothing(); } - config->kdf = EVPKdfPointer{EVP_KDF_fetch(nullptr, algorithm.out(), nullptr)}; + config->ctx = OsslLibCtxPointer{OSSL_LIB_CTX_new()}; + if (UNLIKELY(!config->ctx)) { + THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); + return Nothing(); + } + + config->kdf = + EVPKdfPointer{EVP_KDF_fetch(config->ctx.get(), algorithm.out(), nullptr)}; if (UNLIKELY(!config->kdf)) { THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); return Nothing(); @@ -98,14 +106,16 @@ Maybe Argon2Traits::AdditionalConfig( config->ad = isAsync ? ad.ToCopy() : ad.ToByteSource(); CHECK(args[offset + 5]->IsUint32()); // iter - CHECK(args[offset + 6]->IsUint32()); // lanes - CHECK(args[offset + 7]->IsUint32()); // memcost - CHECK(args[offset + 8]->IsUint32()); // keylen + CHECK(args[offset + 6]->IsUint32()); // threads + CHECK(args[offset + 7]->IsUint32()); // lanes + CHECK(args[offset + 8]->IsUint32()); // memcost + CHECK(args[offset + 9]->IsUint32()); // keylen config->iter = args[offset + 5].As()->Value(); - config->lanes = args[offset + 6].As()->Value(); - config->memcost = args[offset + 7].As()->Value(); - config->keylen = args[offset + 8].As()->Value(); + config->threads = args[offset + 6].As()->Value(); + config->lanes = args[offset + 7].As()->Value(); + config->memcost = args[offset + 8].As()->Value(); + config->keylen = args[offset + 9].As()->Value(); return Just(true); } @@ -129,6 +139,8 @@ bool Argon2Traits::DeriveBits(Environment* env, config.salt.size()), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, const_cast(&config.iter)), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_THREADS, + const_cast(&config.threads)), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, const_cast(&config.lanes)), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, @@ -151,6 +163,11 @@ bool Argon2Traits::DeriveBits(Environment* env, params.push_back(OSSL_PARAM_END); + MaxThreadsScope mts{config.ctx.get(), config.threads}; + if (!mts.success) { + return false; + } + // Both the pass and salt may be zero-length at this point if (EVP_KDF_derive(kctx.get(), buf.data(), diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index 2e61317168f02e..6f57b6bd4004e5 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -22,13 +22,14 @@ namespace node::crypto { struct Argon2Config final : public MemoryRetainer { CryptoJobMode mode; + OsslLibCtxPointer ctx; EVPKdfPointer kdf; ByteSource pass; ByteSource salt; ByteSource secret; ByteSource ad; uint32_t iter; - // TODO(ranisalt): uint32_t threads; + uint32_t threads; uint32_t lanes; uint32_t memcost; uint32_t keylen; diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 5734d8fdc5505e..eb8b4d978328f1 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -18,6 +18,7 @@ #endif #include +#include namespace node { @@ -705,6 +706,11 @@ CryptoJobMode GetCryptoJobMode(v8::Local args) { return static_cast(mode); } +MaxThreadsScope::MaxThreadsScope(OSSL_LIB_CTX* ctx, uint64_t threads) + : ctx{ctx}, success{OSSL_set_max_threads(ctx, threads) == 1} {} + +MaxThreadsScope::~MaxThreadsScope() { OSSL_set_max_threads(ctx, 0); } + namespace { // SecureBuffer uses OPENSSL_secure_malloc to allocate a Uint8Array. // Without --secure-heap, OpenSSL's secure heap is disabled, diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index f7ca7e8a541666..bbda63b6a0d846 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -56,6 +56,7 @@ constexpr size_t kSizeOf_HMAC_CTX = 32; // Define smart pointers for the most commonly used OpenSSL types: using X509Pointer = DeleteFnPtr; using BIOPointer = DeleteFnPtr; +using OsslLibCtxPointer = DeleteFnPtr; using SSLCtxPointer = DeleteFnPtr; using SSLSessionPointer = DeleteFnPtr; using SSLPointer = DeleteFnPtr; @@ -796,6 +797,14 @@ v8::Maybe SetEncodedValue( bool SetRsaOaepLabel(const EVPKeyCtxPointer& rsa, const ByteSource& label); +struct MaxThreadsScope final { + MaxThreadsScope(OSSL_LIB_CTX* ctx, uint64_t threads); + ~MaxThreadsScope(); + + OSSL_LIB_CTX* ctx; + bool success; +}; + namespace Util { void Initialize(Environment* env, v8::Local target); void RegisterExternalReferences(ExternalReferenceRegistry* registry); diff --git a/test/parallel/test-crypto-argon2.js b/test/parallel/test-crypto-argon2.js index aa9985b846c9d5..7890359585bfe8 100644 --- a/test/parallel/test-crypto-argon2.js +++ b/test/parallel/test-crypto-argon2.js @@ -23,7 +23,7 @@ const password = Buffer.alloc(32, 0x01); const salt = Buffer.alloc(16, 0x02); const secret = Buffer.alloc(8, 0x03); const ad = Buffer.alloc(12, 0x04); -const defaults = { password, salt, keylen: 32, iter: 3, lanes: 4, memcost: 64 << 10 }; +const defaults = { password, salt, keylen: 32, iter: 3, threads: 4, lanes: 4, memcost: 64 << 10 }; const good = [ [ @@ -54,8 +54,13 @@ const good = [ { iter: 2 }, '2139b90863e8bbffff1459c9f08f6460db08def592991e78f11b6c5765bd1fac', ], + // the number of threads does not affect the output [ - { lanes: 3 }, + { threads: 1, lanes: 3 }, + '8ba6f33b93c8219cd31385966898fd39c8e570671c6a3c180ee0109a27d62a4b', + ], + [ + { threads: 3, lanes: 3 }, '8ba6f33b93c8219cd31385966898fd39c8e570671c6a3c180ee0109a27d62a4b', ], [ @@ -68,7 +73,9 @@ const good = [ const bad = [ [{ iter: 0 }, "iter"], // iter < 2 [{ iter: 2 ** 32 }, "iter"], // iter > 2^(32)-1 + [{ threads: 0 }, "threads"], // threads < 1 [{ lanes: 0 }, "lanes"], // lanes < 1 + [{ lanes: 2, threads: 3 }, "lanes"], // lanes < threads [{ lanes: 2 ** 24 }, "lanes"], // lanes > 2^(24)-1 [{ lanes: 4, memcost: 16 }, "memcost"], // memcost < 8 * lanes [{ memcost: 2 ** 32 }, "memcost"], // memcost > 2^(32)-1 From 68409c12086d46bcf21a52e6ebe3ac5b468c5b93 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Tue, 31 Oct 2023 03:09:32 +0100 Subject: [PATCH 13/17] Merge threads and lanes --- benchmark/crypto/argon2.js | 7 ++----- doc/api/crypto.md | 8 ++++---- lib/internal/crypto/argon2.js | 21 +++++++-------------- src/crypto/crypto_argon2.cc | 19 ++++++++----------- src/crypto/crypto_argon2.h | 1 - test/parallel/test-crypto-argon2.js | 11 ++--------- 6 files changed, 23 insertions(+), 44 deletions(-) diff --git a/benchmark/crypto/argon2.js b/benchmark/crypto/argon2.js index 116c51f678656f..c55046f117c5dd 100644 --- a/benchmark/crypto/argon2.js +++ b/benchmark/crypto/argon2.js @@ -11,13 +11,10 @@ const { const bench = common.createBenchmark(main, { sync: [0, 1], algorithm: ['ARGON2D', 'ARGON2I', 'ARGON2ID'], - iter: [2, 3, 4], - threads: [2, 4, 8], + iter: [1, 3], lanes: [2, 4, 8], - memcost: [16 << 10, 64 << 10], + memcost: [2 ** 11, 2 ** 16, 2 ** 21], n: [50], -}, { - combinationFilter: ({ threads, lanes }) => threads <= lanes, }); function measureSync(n, pass, salt, options) { diff --git a/doc/api/crypto.md b/doc/api/crypto.md index d8c204cb4b1239..fdc8b15f61fbe7 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -2913,9 +2913,9 @@ added: REPLACEME * `keylen` {number} * `options` {Object} * `algorithm` {string} Variant of Argon2, one of "ARGON2D", "ARGON2I" or - "ARGON2ID". **Default:** `"ARGON2ID`. + "ARGON2ID". **Default:** `"ARGON2ID"`. * `iter` {number} Number of iterations (passes). **Default:** `3`. - * `lanes` {number} Parallelization parameter. **Default:** `4`. + * `lanes` {number} Parallelization parameter (number of threads). **Default:** `4`. * `memcost` {number} Memory cost in 1KiB blocks. **Default:** `65536`. * `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView} Random additional input, similar to the salt, that should **NOT** be stored with the derived @@ -2998,9 +2998,9 @@ added: REPLACEME * `keylen` {number} * `options` {Object} * `algorithm` {string} Variant of Argon2, one of "ARGON2D", "ARGON2I" or - "ARGON2ID". **Default:** `"ARGON2ID`. + "ARGON2ID". **Default:** `"ARGON2ID"`. * `iter` {number} Number of iterations (passes). **Default:** `3`. - * `lanes` {number} Parallelization parameter. **Default:** `4`. + * `lanes` {number} Parallelization parameter (number of threads). **Default:** `4`. * `memcost` {number} Memory cost in 1KiB blocks. **Default:** `65536`. * `secret` {string|Buffer|TypedArray|DataView} Random additional input, similar to the salt, that should **NOT** be stored with the derived key. diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js index 3a57c8c97b762d..7461eff85abef4 100644 --- a/lib/internal/crypto/argon2.js +++ b/lib/internal/crypto/argon2.js @@ -28,7 +28,6 @@ const defaults = { secret: Buffer.alloc(0), ad: Buffer.alloc(0), iter: 3, - threads: 4, lanes: 4, memcost: 64 << 10, // 64Ki * 1 KiB = 64 MiB keylen: 64, @@ -43,7 +42,6 @@ const defaults = { * @param {ArrayBufferLike} [options.secret] * @param {ArrayBufferLike} [options.ad] * @param {number} [options.iter=3] - * @param {number} [options.threads=4] * @param {number} [options.lanes=4] * @param {number} [options.memcost=64 << 10] * @param {Function} callback @@ -55,13 +53,13 @@ function argon2(password, salt, keylen, options, callback = defaults) { } options = check(password, salt, keylen, options); - const { algorithm, secret, ad, iter, threads, lanes, memcost } = options; + const { algorithm, secret, ad, iter, lanes, memcost } = options; ({ password, salt, keylen } = options); validateFunction(callback, 'callback'); const job = new Argon2Job( - kCryptoJobAsync, password, salt, algorithm, secret, ad, iter, threads, lanes, memcost, keylen); + kCryptoJobAsync, password, salt, algorithm, secret, ad, iter, lanes, memcost, keylen); job.ondone = (error, result) => { if (error !== undefined) @@ -81,10 +79,10 @@ function argon2(password, salt, keylen, options, callback = defaults) { */ function argon2Sync(password, salt, keylen, options = defaults) { options = check(password, salt, keylen, options); - const { algorithm, secret, ad, iter, threads, lanes, memcost } = options; + const { algorithm, secret, ad, iter, lanes, memcost } = options; ({ password, salt, keylen } = options); const job = new Argon2Job( - kCryptoJobSync, password, salt, algorithm, secret, ad, iter, threads, lanes, memcost, keylen); + kCryptoJobSync, password, salt, algorithm, secret, ad, iter, lanes, memcost, keylen); const { 0: err, 1: result } = job.run(); if (err !== undefined) @@ -112,7 +110,7 @@ function check(password, salt, keylen, options) { validateInteger(keylen, 'keylen', 4, (2 ** 32) - 1); - let { algorithm, secret, ad, iter, threads, lanes, memcost } = defaults; + let { algorithm, secret, ad, iter, lanes, memcost } = defaults; if (options && options !== defaults) { const has_algorithm = options.algorithm !== undefined; if (has_algorithm) { @@ -133,15 +131,10 @@ function check(password, salt, keylen, options) { iter = options.iter; validateInteger(iter, 'iter', 1, (2 ** 32) - 1); } - const has_threads = options.threads !== undefined; - if (has_threads) { - threads = options.threads; - validateInteger(threads, 'threads', 1, (2 ** 24) - 1); - } const has_lanes = options.lanes !== undefined; if (has_lanes) { lanes = options.lanes; - validateInteger(lanes, 'lanes', threads, (2 ** 24) - 1); + validateInteger(lanes, 'lanes', 1, (2 ** 24) - 1); } if (iter === 0) iter = defaults.iter; if (lanes === 0) lanes = defaults.lanes; @@ -154,7 +147,7 @@ function check(password, salt, keylen, options) { if (memcost === 0) memcost = defaults.memcost; } - return { password, salt, algorithm, secret, ad, keylen, iter, threads, lanes, memcost }; + return { password, salt, algorithm, secret, ad, keylen, iter, lanes, memcost }; } module.exports = { diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index cb79b4e06ff655..f78095de6d55ec 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -23,7 +23,6 @@ Argon2Config::Argon2Config(Argon2Config&& other) noexcept secret(std::move(other.secret)), ad(std::move(other.ad)), iter(other.iter), - threads(other.threads), lanes(other.lanes), memcost(other.memcost), keylen(other.keylen) {} @@ -106,16 +105,14 @@ Maybe Argon2Traits::AdditionalConfig( config->ad = isAsync ? ad.ToCopy() : ad.ToByteSource(); CHECK(args[offset + 5]->IsUint32()); // iter - CHECK(args[offset + 6]->IsUint32()); // threads - CHECK(args[offset + 7]->IsUint32()); // lanes - CHECK(args[offset + 8]->IsUint32()); // memcost - CHECK(args[offset + 9]->IsUint32()); // keylen + CHECK(args[offset + 6]->IsUint32()); // lanes + CHECK(args[offset + 7]->IsUint32()); // memcost + CHECK(args[offset + 8]->IsUint32()); // keylen config->iter = args[offset + 5].As()->Value(); - config->threads = args[offset + 6].As()->Value(); - config->lanes = args[offset + 7].As()->Value(); - config->memcost = args[offset + 8].As()->Value(); - config->keylen = args[offset + 9].As()->Value(); + config->lanes = args[offset + 6].As()->Value(); + config->memcost = args[offset + 7].As()->Value(); + config->keylen = args[offset + 8].As()->Value(); return Just(true); } @@ -140,7 +137,7 @@ bool Argon2Traits::DeriveBits(Environment* env, OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, const_cast(&config.iter)), OSSL_PARAM_uint32(OSSL_KDF_PARAM_THREADS, - const_cast(&config.threads)), + const_cast(&config.lanes)), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, const_cast(&config.lanes)), OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, @@ -163,7 +160,7 @@ bool Argon2Traits::DeriveBits(Environment* env, params.push_back(OSSL_PARAM_END); - MaxThreadsScope mts{config.ctx.get(), config.threads}; + MaxThreadsScope mts{config.ctx.get(), config.lanes}; if (!mts.success) { return false; } diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index 6f57b6bd4004e5..e6fe6d42b52559 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -29,7 +29,6 @@ struct Argon2Config final : public MemoryRetainer { ByteSource secret; ByteSource ad; uint32_t iter; - uint32_t threads; uint32_t lanes; uint32_t memcost; uint32_t keylen; diff --git a/test/parallel/test-crypto-argon2.js b/test/parallel/test-crypto-argon2.js index 7890359585bfe8..aa9985b846c9d5 100644 --- a/test/parallel/test-crypto-argon2.js +++ b/test/parallel/test-crypto-argon2.js @@ -23,7 +23,7 @@ const password = Buffer.alloc(32, 0x01); const salt = Buffer.alloc(16, 0x02); const secret = Buffer.alloc(8, 0x03); const ad = Buffer.alloc(12, 0x04); -const defaults = { password, salt, keylen: 32, iter: 3, threads: 4, lanes: 4, memcost: 64 << 10 }; +const defaults = { password, salt, keylen: 32, iter: 3, lanes: 4, memcost: 64 << 10 }; const good = [ [ @@ -54,13 +54,8 @@ const good = [ { iter: 2 }, '2139b90863e8bbffff1459c9f08f6460db08def592991e78f11b6c5765bd1fac', ], - // the number of threads does not affect the output [ - { threads: 1, lanes: 3 }, - '8ba6f33b93c8219cd31385966898fd39c8e570671c6a3c180ee0109a27d62a4b', - ], - [ - { threads: 3, lanes: 3 }, + { lanes: 3 }, '8ba6f33b93c8219cd31385966898fd39c8e570671c6a3c180ee0109a27d62a4b', ], [ @@ -73,9 +68,7 @@ const good = [ const bad = [ [{ iter: 0 }, "iter"], // iter < 2 [{ iter: 2 ** 32 }, "iter"], // iter > 2^(32)-1 - [{ threads: 0 }, "threads"], // threads < 1 [{ lanes: 0 }, "lanes"], // lanes < 1 - [{ lanes: 2, threads: 3 }, "lanes"], // lanes < threads [{ lanes: 2 ** 24 }, "lanes"], // lanes > 2^(24)-1 [{ lanes: 4, memcost: 16 }, "memcost"], // memcost < 8 * lanes [{ memcost: 2 ** 32 }, "memcost"], // memcost > 2^(32)-1 From 0075c775ce05fb8ecc1155609d000a0e2e0e1dd1 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 1 Aug 2024 00:06:43 +0200 Subject: [PATCH 14/17] Add missing pointer wrappers --- deps/ncrypto/ncrypto.h | 3 +++ src/crypto/crypto_util.h | 1 + 2 files changed, 4 insertions(+) diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 6fb66fe5234d85..617787d33be96c 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -180,9 +180,12 @@ using ECKeyPointer = DeleteFnPtr; using ECPointPointer = DeleteFnPtr; using EVPKeyCtxPointer = DeleteFnPtr; using EVPKeyPointer = DeleteFnPtr; +using EVPKdfCtxPointer = DeleteFnPtr; +using EVPKdfPointer = DeleteFnPtr; using EVPMDCtxPointer = DeleteFnPtr; using HMACCtxPointer = DeleteFnPtr; using NetscapeSPKIPointer = DeleteFnPtr; +using OsslLibCtxPointer = DeleteFnPtr; using PKCS8Pointer = DeleteFnPtr; using RSAPointer = DeleteFnPtr; using SSLCtxPointer = DeleteFnPtr; diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index e5d4e5a294011b..f3060d939e5a49 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -79,6 +79,7 @@ using HMACCtxPointer = ncrypto::HMACCtxPointer; using CipherCtxPointer = ncrypto::CipherCtxPointer; using DsaPointer = ncrypto::DSAPointer; using DsaSigPointer = ncrypto::DSASigPointer; +using OsslLibCtxPointer = ncrypto::OsslLibCtxPointer; using ClearErrorOnReturn = ncrypto::ClearErrorOnReturn; using MarkPopErrorOnReturn = ncrypto::MarkPopErrorOnReturn; From f82650d75fc713cab37f30fe2e07f01fdeccfc8e Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 1 Aug 2024 00:43:04 +0200 Subject: [PATCH 15/17] Add missing parameter documentation --- lib/internal/crypto/argon2.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js index 7461eff85abef4..3cda79e40d739f 100644 --- a/lib/internal/crypto/argon2.js +++ b/lib/internal/crypto/argon2.js @@ -75,7 +75,13 @@ function argon2(password, salt, keylen, options, callback = defaults) { * @param {ArrayBufferLike} password * @param {ArrayBufferLike} salt * @param {number} keylen - * @param {*} [options] + * @param {object} [options] + * @param {'ARGON2D' | 'ARGON2I' | 'ARGON2ID'} [options.algorithm='ARGON2ID'] + * @param {ArrayBufferLike} [options.secret] + * @param {ArrayBufferLike} [options.ad] + * @param {number} [options.iter=3] + * @param {number} [options.lanes=4] + * @param {number} [options.memcost=64 << 10] */ function argon2Sync(password, salt, keylen, options = defaults) { options = check(password, salt, keylen, options); @@ -95,7 +101,13 @@ function argon2Sync(password, salt, keylen, options = defaults) { * @param {ArrayBufferLike} password * @param {ArrayBufferLike} salt * @param {number} keylen - * @param {*} options + * @param {object} options + * @param {'ARGON2D' | 'ARGON2I' | 'ARGON2ID'} [options.algorithm='ARGON2ID'] + * @param {ArrayBufferLike} [options.secret] + * @param {ArrayBufferLike} [options.ad] + * @param {number} [options.iter=3] + * @param {number} [options.lanes=4] + * @param {number} [options.memcost=64 << 10] */ function check(password, salt, keylen, options) { if (Argon2Job === undefined) From 9d50046b94a9ffaf7faab117e64352d3af539af8 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 1 Aug 2024 00:44:58 +0200 Subject: [PATCH 16/17] Remove incorrect default option --- lib/internal/crypto/argon2.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js index 3cda79e40d739f..9b8a7d64fa764e 100644 --- a/lib/internal/crypto/argon2.js +++ b/lib/internal/crypto/argon2.js @@ -30,7 +30,6 @@ const defaults = { iter: 3, lanes: 4, memcost: 64 << 10, // 64Ki * 1 KiB = 64 MiB - keylen: 64, }; /** From aba1eb6c88c9543fe58414d00195bb5e597e9ce1 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 1 Aug 2024 00:56:02 +0200 Subject: [PATCH 17/17] Remove unnecessary checks --- lib/internal/crypto/argon2.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js index 9b8a7d64fa764e..2f134ad3cc0a79 100644 --- a/lib/internal/crypto/argon2.js +++ b/lib/internal/crypto/argon2.js @@ -147,15 +147,11 @@ function check(password, salt, keylen, options) { lanes = options.lanes; validateInteger(lanes, 'lanes', 1, (2 ** 24) - 1); } - if (iter === 0) iter = defaults.iter; - if (lanes === 0) lanes = defaults.lanes; - const has_memcost = options.memcost !== undefined; if (has_memcost) { memcost = options.memcost; validateInteger(memcost, 'memcost', 8 * lanes, (2 ** 32) - 1); } - if (memcost === 0) memcost = defaults.memcost; } return { password, salt, algorithm, secret, ad, keylen, iter, lanes, memcost };