mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
crypto: add argon2() and argon2Sync() methods
Co-authored-by: Filip Skokan <panva.ip@gmail.com> Co-authored-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/50353 Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
parent
ef58be6f0c
commit
bdcab711b8
|
|
@ -81,6 +81,9 @@ class Benchmark {
|
|||
if (typeof value === 'number') {
|
||||
if (key === 'dur' || key === 'duration') {
|
||||
value = 0.05;
|
||||
} else if (key === 'memory') {
|
||||
// minimum Argon2 memcost with 1 lane is 8
|
||||
value = 8;
|
||||
} else if (value > 1) {
|
||||
value = 1;
|
||||
}
|
||||
|
|
|
|||
53
benchmark/crypto/argon2.js
Normal file
53
benchmark/crypto/argon2.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
'use strict';
|
||||
|
||||
const common = require('../common.js');
|
||||
const { hasOpenSSL } = require('../../test/common/crypto.js');
|
||||
const assert = require('node:assert');
|
||||
const {
|
||||
argon2,
|
||||
argon2Sync,
|
||||
randomBytes,
|
||||
} = require('node:crypto');
|
||||
|
||||
if (!hasOpenSSL(3, 2)) {
|
||||
console.log('Skipping: Argon2 requires OpenSSL >= 3.2');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
mode: ['sync', 'async'],
|
||||
algorithm: ['argon2d', 'argon2i', 'argon2id'],
|
||||
passes: [1, 3],
|
||||
parallelism: [2, 4, 8],
|
||||
memory: [2 ** 11, 2 ** 16, 2 ** 21],
|
||||
n: [50],
|
||||
});
|
||||
|
||||
function measureSync(n, algorithm, message, nonce, options) {
|
||||
bench.start();
|
||||
for (let i = 0; i < n; ++i)
|
||||
argon2Sync(algorithm, { ...options, message, nonce, tagLength: 64 });
|
||||
bench.end(n);
|
||||
}
|
||||
|
||||
function measureAsync(n, algorithm, message, nonce, 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(algorithm, { ...options, message, nonce, tagLength: 64 }, done);
|
||||
}
|
||||
|
||||
function main({ n, mode, algorithm, ...options }) {
|
||||
// Message, nonce, secret, associated data & tag length do not affect performance
|
||||
const message = randomBytes(32);
|
||||
const nonce = randomBytes(16);
|
||||
if (mode === 'sync')
|
||||
measureSync(n, algorithm, message, nonce, options);
|
||||
else
|
||||
measureAsync(n, algorithm, message, nonce, options);
|
||||
}
|
||||
101
deps/ncrypto/ncrypto.cc
vendored
101
deps/ncrypto/ncrypto.cc
vendored
|
|
@ -10,7 +10,12 @@
|
|||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/params.h>
|
||||
#include <openssl/provider.h>
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
|
||||
#include <openssl/thread.h>
|
||||
#endif
|
||||
#endif
|
||||
#if OPENSSL_WITH_PQC
|
||||
struct PQCMapping {
|
||||
|
|
@ -1868,6 +1873,102 @@ DataPointer pbkdf2(const Digest& md,
|
|||
return {};
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
|
||||
#ifndef OPENSSL_NO_ARGON2
|
||||
DataPointer argon2(const Buffer<const char>& pass,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
uint32_t lanes,
|
||||
size_t length,
|
||||
uint32_t memcost,
|
||||
uint32_t iter,
|
||||
uint32_t version,
|
||||
const Buffer<const unsigned char>& secret,
|
||||
const Buffer<const unsigned char>& ad,
|
||||
Argon2Type type) {
|
||||
ClearErrorOnReturn clearErrorOnReturn;
|
||||
|
||||
std::string_view algorithm;
|
||||
switch (type) {
|
||||
case Argon2Type::ARGON2I:
|
||||
algorithm = "ARGON2I";
|
||||
break;
|
||||
case Argon2Type::ARGON2D:
|
||||
algorithm = "ARGON2D";
|
||||
break;
|
||||
case Argon2Type::ARGON2ID:
|
||||
algorithm = "ARGON2ID";
|
||||
break;
|
||||
default:
|
||||
// Invalid Argon2 type
|
||||
return {};
|
||||
}
|
||||
|
||||
// creates a new library context to avoid locking when running concurrently
|
||||
auto ctx = DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free>{OSSL_LIB_CTX_new()};
|
||||
if (!ctx) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// required if threads > 1
|
||||
if (lanes > 1 && OSSL_set_max_threads(ctx.get(), lanes) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto kdf = DeleteFnPtr<EVP_KDF, EVP_KDF_free>{
|
||||
EVP_KDF_fetch(ctx.get(), algorithm.data(), nullptr)};
|
||||
if (!kdf) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto kctx =
|
||||
DeleteFnPtr<EVP_KDF_CTX, EVP_KDF_CTX_free>{EVP_KDF_CTX_new(kdf.get())};
|
||||
if (!kctx) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<OSSL_PARAM> params;
|
||||
params.reserve(9);
|
||||
|
||||
params.push_back(OSSL_PARAM_construct_octet_string(
|
||||
OSSL_KDF_PARAM_PASSWORD,
|
||||
const_cast<char*>(pass.len > 0 ? pass.data : ""),
|
||||
pass.len));
|
||||
params.push_back(OSSL_PARAM_construct_octet_string(
|
||||
OSSL_KDF_PARAM_SALT, const_cast<unsigned char*>(salt.data), salt.len));
|
||||
params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &lanes));
|
||||
params.push_back(
|
||||
OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes));
|
||||
params.push_back(
|
||||
OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost));
|
||||
params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter));
|
||||
|
||||
if (ad.len != 0) {
|
||||
params.push_back(OSSL_PARAM_construct_octet_string(
|
||||
OSSL_KDF_PARAM_ARGON2_AD, const_cast<unsigned char*>(ad.data), ad.len));
|
||||
}
|
||||
|
||||
if (secret.len != 0) {
|
||||
params.push_back(OSSL_PARAM_construct_octet_string(
|
||||
OSSL_KDF_PARAM_SECRET,
|
||||
const_cast<unsigned char*>(secret.data),
|
||||
secret.len));
|
||||
}
|
||||
|
||||
params.push_back(OSSL_PARAM_construct_end());
|
||||
|
||||
auto dp = DataPointer::Alloc(length);
|
||||
if (dp && EVP_KDF_derive(kctx.get(),
|
||||
reinterpret_cast<unsigned char*>(dp.get()),
|
||||
length,
|
||||
params.data()) == 1) {
|
||||
return dp;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
|
||||
EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig(
|
||||
|
|
|
|||
17
deps/ncrypto/ncrypto.h
vendored
17
deps/ncrypto/ncrypto.h
vendored
|
|
@ -1557,6 +1557,23 @@ DataPointer pbkdf2(const Digest& md,
|
|||
uint32_t iterations,
|
||||
size_t length);
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
|
||||
#ifndef OPENSSL_NO_ARGON2
|
||||
enum class Argon2Type { ARGON2D, ARGON2I, ARGON2ID };
|
||||
|
||||
DataPointer argon2(const Buffer<const char>& pass,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
uint32_t lanes,
|
||||
size_t length,
|
||||
uint32_t memcost,
|
||||
uint32_t iter,
|
||||
uint32_t version,
|
||||
const Buffer<const unsigned char>& secret,
|
||||
const Buffer<const unsigned char>& ad,
|
||||
Argon2Type type);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Version metadata
|
||||
#define NCRYPTO_VERSION "0.0.1"
|
||||
|
|
|
|||
|
|
@ -2970,6 +2970,171 @@ Does not perform any other validation checks on the certificate.
|
|||
|
||||
## `node:crypto` module methods and properties
|
||||
|
||||
### `crypto.argon2(algorithm, parameters, callback)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.2 - Release candidate
|
||||
|
||||
* `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`.
|
||||
* `parameters` {Object}
|
||||
* `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password
|
||||
hashing applications of Argon2.
|
||||
* `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at
|
||||
least 8 bytes long. This is the salt for password hashing applications of Argon2.
|
||||
* `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes)
|
||||
can be run. Must be greater than 1 and less than `2**24-1`.
|
||||
* `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and
|
||||
less than `2**32-1`.
|
||||
* `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than
|
||||
`8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded
|
||||
down to the nearest multiple of `4 * parallelism`.
|
||||
* `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less
|
||||
than `2**32-1`.
|
||||
* `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input,
|
||||
similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in
|
||||
password hashing applications. If used, must have a length not greater than `2**32-1` bytes.
|
||||
* `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to
|
||||
be added to the hash, functionally equivalent to salt or secret, but meant for
|
||||
non-random data. If used, must have a length not greater than `2**32-1` bytes.
|
||||
* `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 `nonce` should be as unique as possible. It is recommended that a nonce is
|
||||
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
|
||||
|
||||
When passing strings for `message`, `nonce`, `secret` or `associatedData`, 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 parameters = {
|
||||
message: 'password',
|
||||
nonce: randomBytes(16),
|
||||
parallelism: 4,
|
||||
tagLength: 64,
|
||||
memory: 65536,
|
||||
passes: 3,
|
||||
};
|
||||
|
||||
argon2('argon2id', parameters, (err, derivedKey) => {
|
||||
if (err) throw err;
|
||||
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const { argon2, randomBytes } = require('node:crypto');
|
||||
|
||||
const parameters = {
|
||||
message: 'password',
|
||||
nonce: randomBytes(16),
|
||||
parallelism: 4,
|
||||
tagLength: 64,
|
||||
memory: 65536,
|
||||
passes: 3,
|
||||
};
|
||||
|
||||
argon2('argon2id', parameters, (err, derivedKey) => {
|
||||
if (err) throw err;
|
||||
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
|
||||
});
|
||||
```
|
||||
|
||||
### `crypto.argon2Sync(algorithm, parameters)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.2 - Release candidate
|
||||
|
||||
* `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`.
|
||||
* `parameters` {Object}
|
||||
* `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password
|
||||
hashing applications of Argon2.
|
||||
* `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at
|
||||
least 8 bytes long. This is the salt for password hashing applications of Argon2.
|
||||
* `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes)
|
||||
can be run. Must be greater than 1 and less than `2**24-1`.
|
||||
* `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and
|
||||
less than `2**32-1`.
|
||||
* `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than
|
||||
`8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded
|
||||
down to the nearest multiple of `4 * parallelism`.
|
||||
* `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less
|
||||
than `2**32-1`.
|
||||
* `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input,
|
||||
similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in
|
||||
password hashing applications. If used, must have a length not greater than `2**32-1` bytes.
|
||||
* `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to
|
||||
be added to the hash, functionally equivalent to salt or secret, but meant for
|
||||
non-random data. If used, must have a length not greater than `2**32-1` bytes.
|
||||
* 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 `nonce` should be as unique as possible. It is recommended that a nonce is
|
||||
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
|
||||
|
||||
When passing strings for `message`, `nonce`, `secret` or `associatedData`, 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');
|
||||
|
||||
const parameters = {
|
||||
message: 'password',
|
||||
nonce: randomBytes(16),
|
||||
parallelism: 4,
|
||||
tagLength: 64,
|
||||
memory: 65536,
|
||||
passes: 3,
|
||||
};
|
||||
|
||||
const derivedKey = argon2Sync('argon2id', parameters);
|
||||
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
|
||||
```
|
||||
|
||||
```cjs
|
||||
const { argon2Sync, randomBytes } = require('node:crypto');
|
||||
|
||||
const parameters = {
|
||||
message: 'password',
|
||||
nonce: randomBytes(16),
|
||||
parallelism: 4,
|
||||
tagLength: 64,
|
||||
memory: 65536,
|
||||
passes: 3,
|
||||
};
|
||||
|
||||
const derivedKey = argon2Sync('argon2id', parameters);
|
||||
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
|
||||
```
|
||||
|
||||
### `crypto.checkPrime(candidate[, options], callback)`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -6284,6 +6449,7 @@ See the [list of SSL OP Flags][] for details.
|
|||
[`verify.verify()`]: #verifyverifyobject-signature-signatureencoding
|
||||
[`x509.fingerprint256`]: #x509fingerprint256
|
||||
[`x509.verify(publicKey)`]: #x509verifypublickey
|
||||
[argon2]: https://www.rfc-editor.org/rfc/rfc9106.html
|
||||
[asymmetric key types]: #asymmetric-key-types
|
||||
[caveats when using strings as inputs to cryptographic APIs]: #using-strings-as-inputs-to-cryptographic-apis
|
||||
[certificate object]: tls.md#certificate-object
|
||||
|
|
|
|||
|
|
@ -826,6 +826,12 @@ when an error occurs (and is caught) during the creation of the
|
|||
context, for example, when the allocation fails or the maximum call stack
|
||||
size is reached when the context is created.
|
||||
|
||||
<a id="ERR_CRYPTO_ARGON2_NOT_SUPPORTED"></a>
|
||||
|
||||
### `ERR_CRYPTO_ARGON2_NOT_SUPPORTED`
|
||||
|
||||
Argon2 is not supported by the current version of OpenSSL being used.
|
||||
|
||||
<a id="ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED"></a>
|
||||
|
||||
### `ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED`
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ const {
|
|||
randomInt,
|
||||
randomUUID,
|
||||
} = require('internal/crypto/random');
|
||||
const {
|
||||
argon2,
|
||||
argon2Sync,
|
||||
} = require('internal/crypto/argon2');
|
||||
const {
|
||||
pbkdf2,
|
||||
pbkdf2Sync,
|
||||
|
|
@ -171,6 +175,8 @@ function createVerify(algorithm, options) {
|
|||
|
||||
module.exports = {
|
||||
// Methods
|
||||
argon2,
|
||||
argon2Sync,
|
||||
checkPrime,
|
||||
checkPrimeSync,
|
||||
createCipheriv,
|
||||
|
|
|
|||
185
lib/internal/crypto/argon2.js
Normal file
185
lib/internal/crypto/argon2.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
FunctionPrototypeCall,
|
||||
MathPow,
|
||||
Uint8Array,
|
||||
} = primordials;
|
||||
|
||||
const { Buffer } = require('buffer');
|
||||
|
||||
const {
|
||||
Argon2Job,
|
||||
kCryptoJobAsync,
|
||||
kCryptoJobSync,
|
||||
kTypeArgon2d,
|
||||
kTypeArgon2i,
|
||||
kTypeArgon2id,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const { getArrayBufferOrView } = 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 };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
argon2,
|
||||
argon2Sync,
|
||||
};
|
||||
|
|
@ -1163,6 +1163,7 @@ E('ERR_CONSOLE_WRITABLE_STREAM',
|
|||
'Console expects a writable stream instance for %s', TypeError);
|
||||
E('ERR_CONSTRUCT_CALL_REQUIRED', 'Class constructor %s cannot be invoked without `new`', 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);
|
||||
|
|
|
|||
12
node.gyp
12
node.gyp
|
|
@ -332,6 +332,7 @@
|
|||
],
|
||||
'node_crypto_sources': [
|
||||
'src/crypto/crypto_aes.cc',
|
||||
'src/crypto/crypto_argon2.cc',
|
||||
'src/crypto/crypto_bio.cc',
|
||||
'src/crypto/crypto_chacha20_poly1305.cc',
|
||||
'src/crypto/crypto_common.cc',
|
||||
|
|
@ -357,6 +358,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',
|
||||
|
|
@ -978,11 +980,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.
|
||||
|
|
|
|||
|
|
@ -86,20 +86,21 @@ namespace node {
|
|||
V(ZLIB)
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
|
||||
V(CHECKPRIMEREQUEST) \
|
||||
V(PBKDF2REQUEST) \
|
||||
V(KEYPAIRGENREQUEST) \
|
||||
V(KEYGENREQUEST) \
|
||||
V(KEYEXPORTREQUEST) \
|
||||
V(CIPHERREQUEST) \
|
||||
V(DERIVEBITSREQUEST) \
|
||||
V(HASHREQUEST) \
|
||||
V(RANDOMBYTESREQUEST) \
|
||||
V(RANDOMPRIMEREQUEST) \
|
||||
V(SCRYPTREQUEST) \
|
||||
V(SIGNREQUEST) \
|
||||
V(TLSWRAP) \
|
||||
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
|
||||
V(CHECKPRIMEREQUEST) \
|
||||
V(PBKDF2REQUEST) \
|
||||
V(KEYPAIRGENREQUEST) \
|
||||
V(KEYGENREQUEST) \
|
||||
V(KEYEXPORTREQUEST) \
|
||||
V(ARGON2REQUEST) \
|
||||
V(CIPHERREQUEST) \
|
||||
V(DERIVEBITSREQUEST) \
|
||||
V(HASHREQUEST) \
|
||||
V(RANDOMBYTESREQUEST) \
|
||||
V(RANDOMPRIMEREQUEST) \
|
||||
V(SCRYPTREQUEST) \
|
||||
V(SIGNREQUEST) \
|
||||
V(TLSWRAP) \
|
||||
V(VERIFYREQUEST)
|
||||
#else
|
||||
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
172
src/crypto/crypto_argon2.cc
Normal file
172
src/crypto/crypto_argon2.cc
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#include "crypto/crypto_argon2.h"
|
||||
#include "async_wrap-inl.h"
|
||||
#include "threadpoolwork-inl.h"
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
|
||||
#ifndef OPENSSL_NO_ARGON2
|
||||
#include <openssl/core_names.h>
|
||||
|
||||
namespace node::crypto {
|
||||
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::JustVoid;
|
||||
using v8::Local;
|
||||
using v8::Maybe;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Nothing;
|
||||
using v8::Object;
|
||||
using v8::Uint32;
|
||||
using v8::Value;
|
||||
|
||||
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)},
|
||||
type{other.type},
|
||||
iter{other.iter},
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
MaybeLocal<Value> Argon2Traits::EncodeOutput(Environment* env,
|
||||
const Argon2Config& config,
|
||||
ByteSource* out) {
|
||||
return out->ToArrayBuffer(env);
|
||||
}
|
||||
|
||||
Maybe<void> Argon2Traits::AdditionalConfig(
|
||||
CryptoJobMode mode,
|
||||
const FunctionCallbackInfo<Value>& args,
|
||||
unsigned int offset,
|
||||
Argon2Config* config) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
config->mode = mode;
|
||||
|
||||
ArrayBufferOrViewContents<char> pass(args[offset]);
|
||||
ArrayBufferOrViewContents<char> salt(args[offset + 1]);
|
||||
ArrayBufferOrViewContents<char> secret(args[offset + 6]);
|
||||
ArrayBufferOrViewContents<char> ad(args[offset + 7]);
|
||||
|
||||
if (!pass.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "pass is too large");
|
||||
return Nothing<void>();
|
||||
}
|
||||
|
||||
if (!salt.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "salt is too large");
|
||||
return Nothing<void>();
|
||||
}
|
||||
|
||||
if (!secret.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "secret is too large");
|
||||
return Nothing<void>();
|
||||
}
|
||||
|
||||
if (!ad.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "ad is too large");
|
||||
return Nothing<void>();
|
||||
}
|
||||
|
||||
const bool isAsync = mode == kCryptoJobAsync;
|
||||
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 + 2]->IsUint32()); // lanes
|
||||
CHECK(args[offset + 3]->IsUint32()); // keylen
|
||||
CHECK(args[offset + 4]->IsUint32()); // memcost
|
||||
CHECK(args[offset + 5]->IsUint32()); // iter
|
||||
CHECK(args[offset + 8]->IsUint32()); // type
|
||||
|
||||
config->lanes = args[offset + 2].As<Uint32>()->Value();
|
||||
config->keylen = args[offset + 3].As<Uint32>()->Value();
|
||||
config->memcost = args[offset + 4].As<Uint32>()->Value();
|
||||
config->iter = args[offset + 5].As<Uint32>()->Value();
|
||||
config->type =
|
||||
static_cast<ncrypto::Argon2Type>(args[offset + 8].As<Uint32>()->Value());
|
||||
|
||||
if (!ncrypto::argon2(config->pass,
|
||||
config->salt,
|
||||
config->lanes,
|
||||
config->keylen,
|
||||
config->memcost,
|
||||
config->iter,
|
||||
config->version,
|
||||
config->secret,
|
||||
config->ad,
|
||||
config->type)) {
|
||||
THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env);
|
||||
return Nothing<void>();
|
||||
}
|
||||
|
||||
return JustVoid();
|
||||
}
|
||||
|
||||
bool Argon2Traits::DeriveBits(Environment* env,
|
||||
const Argon2Config& config,
|
||||
ByteSource* out,
|
||||
CryptoJobMode mode) {
|
||||
// If the config.length is zero-length, just return an empty buffer.
|
||||
// It's useless, yes, but allowed via the API.
|
||||
if (config.keylen == 0) {
|
||||
*out = ByteSource();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Both the pass and salt may be zero-length at this point
|
||||
auto dp = ncrypto::argon2(config.pass,
|
||||
config.salt,
|
||||
config.lanes,
|
||||
config.keylen,
|
||||
config.memcost,
|
||||
config.iter,
|
||||
config.version,
|
||||
config.secret,
|
||||
config.ad,
|
||||
config.type);
|
||||
|
||||
if (!dp) return false;
|
||||
DCHECK(!dp.isSecure());
|
||||
*out = ByteSource::Allocated(dp.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr auto kTypeArgon2d = ncrypto::Argon2Type::ARGON2D;
|
||||
static constexpr auto kTypeArgon2i = ncrypto::Argon2Type::ARGON2I;
|
||||
static constexpr auto kTypeArgon2id = ncrypto::Argon2Type::ARGON2ID;
|
||||
|
||||
void Argon2::Initialize(Environment* env, Local<Object> target) {
|
||||
Argon2Job::Initialize(env, target);
|
||||
|
||||
NODE_DEFINE_CONSTANT(target, kTypeArgon2d);
|
||||
NODE_DEFINE_CONSTANT(target, kTypeArgon2i);
|
||||
NODE_DEFINE_CONSTANT(target, kTypeArgon2id);
|
||||
}
|
||||
|
||||
void Argon2::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
Argon2Job::RegisterExternalReferences(registry);
|
||||
}
|
||||
|
||||
} // namespace node::crypto
|
||||
|
||||
#endif
|
||||
#endif
|
||||
86
src/crypto/crypto_argon2.h
Normal file
86
src/crypto/crypto_argon2.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#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"
|
||||
|
||||
namespace node::crypto {
|
||||
#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_NUMBER >= 0x30200000L
|
||||
|
||||
// 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.
|
||||
|
||||
struct Argon2Config final : public MemoryRetainer {
|
||||
CryptoJobMode mode;
|
||||
ByteSource pass;
|
||||
ByteSource salt;
|
||||
ByteSource secret;
|
||||
ByteSource ad;
|
||||
ncrypto::Argon2Type type;
|
||||
uint32_t iter;
|
||||
uint32_t lanes;
|
||||
uint32_t memcost;
|
||||
uint32_t version = 0x13;
|
||||
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<void> AdditionalConfig(
|
||||
CryptoJobMode mode,
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
unsigned int offset,
|
||||
Argon2Config* config);
|
||||
|
||||
static bool DeriveBits(Environment* env,
|
||||
const Argon2Config& config,
|
||||
ByteSource* out,
|
||||
CryptoJobMode mode);
|
||||
|
||||
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
|
||||
const Argon2Config& config,
|
||||
ByteSource* out);
|
||||
};
|
||||
|
||||
using Argon2Job = DeriveBitsJob<Argon2Traits>;
|
||||
|
||||
namespace Argon2 {
|
||||
void Initialize(Environment* env, v8::Local<v8::Object> target);
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
|
||||
} // namespace Argon2
|
||||
|
||||
#else
|
||||
// If there is no Argon2 support, Argon2Job becomes a non-op
|
||||
struct Argon2Job {
|
||||
static void Initialize(Environment* env, v8::Local<v8::Object> target) {}
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace node::crypto
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
#endif // SRC_CRYPTO_CRYPTO_ARGON2_H_
|
||||
|
|
@ -60,6 +60,12 @@ namespace crypto {
|
|||
V(Verify) \
|
||||
V(X509Certificate)
|
||||
|
||||
#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_NUMBER >= 0x30200000L
|
||||
#define ARGON2_NAMESPACE_LIST(V) V(Argon2)
|
||||
#else
|
||||
#define ARGON2_NAMESPACE_LIST(V)
|
||||
#endif // !OPENSSL_NO_ARGON2 && OpenSSL >= 3.2
|
||||
|
||||
#ifdef OPENSSL_NO_SCRYPT
|
||||
#define SCRYPT_NAMESPACE_LIST(V)
|
||||
#else
|
||||
|
|
@ -68,6 +74,7 @@ namespace crypto {
|
|||
|
||||
#define CRYPTO_NAMESPACE_LIST(V) \
|
||||
CRYPTO_NAMESPACE_LIST_BASE(V) \
|
||||
ARGON2_NAMESPACE_LIST(V) \
|
||||
SCRYPT_NAMESPACE_LIST(V)
|
||||
|
||||
void Initialize(Local<Object> target,
|
||||
|
|
|
|||
|
|
@ -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_chacha20_poly1305.h"
|
||||
#include "crypto/crypto_cipher.h"
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
|
|||
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, TypeError) \
|
||||
V(ERR_CRYPTO_INVALID_AUTH_TAG, TypeError) \
|
||||
V(ERR_CRYPTO_INVALID_COUNTER, TypeError) \
|
||||
V(ERR_CRYPTO_INVALID_CURVE, TypeError) \
|
||||
|
|
@ -184,6 +185,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") \
|
||||
|
|
|
|||
14
test/parallel/test-crypto-argon2-unsupported.js
Normal file
14
test/parallel/test-crypto-argon2-unsupported.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
|
||||
if (hasOpenSSL(3, 2))
|
||||
common.skip('requires OpenSSL < 3.2');
|
||||
|
||||
const assert = require('node:assert');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
assert.throws(() => crypto.argon2(), { code: 'ERR_CRYPTO_ARGON2_NOT_SUPPORTED' });
|
||||
139
test/parallel/test-crypto-argon2.js
Normal file
139
test/parallel/test-crypto-argon2.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
'use strict';
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
|
||||
if (!hasOpenSSL(3, 2))
|
||||
common.skip('requires OpenSSL >= 3.2');
|
||||
|
||||
const assert = require('node:assert');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
function runArgon2(algorithm, options) {
|
||||
const syncResult = crypto.argon2Sync(algorithm, options);
|
||||
|
||||
crypto.argon2(algorithm, options,
|
||||
common.mustSucceed((asyncResult) => {
|
||||
assert.deepStrictEqual(asyncResult, syncResult);
|
||||
}));
|
||||
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
const message = Buffer.alloc(32, 0x01);
|
||||
const nonce = Buffer.alloc(16, 0x02);
|
||||
const secret = Buffer.alloc(8, 0x03);
|
||||
const associatedData = Buffer.alloc(12, 0x04);
|
||||
const defaults = { message, nonce, parallelism: 1, tagLength: 64, memory: 8, passes: 3 };
|
||||
|
||||
const good = [
|
||||
// Test vectors from RFC 9106 https://www.rfc-editor.org/rfc/rfc9106.html#name-test-vectors
|
||||
// and OpenSSL 3.2 https://github.com/openssl/openssl/blob/6dfa998f7ea150f9c6d4e4727cf6d5c82a68a8da/test/recipes/30-test_evp_data/evpkdf_argon2.txt
|
||||
//
|
||||
// OpenSSL defaults are:
|
||||
// - outlen: 64
|
||||
// - passes: 3
|
||||
// - parallelism: 1
|
||||
// - memory: 8
|
||||
// https://github.com/openssl/openssl/blob/6dfa998f7ea150f9c6d4e4727cf6d5c82a68a8da/providers/implementations/kdfs/argon2.c#L77-L82
|
||||
[
|
||||
'argon2d',
|
||||
{ secret, associatedData, parallelism: 4, tagLength: 32, memory: 32 },
|
||||
'512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb',
|
||||
],
|
||||
[
|
||||
'argon2i',
|
||||
{ secret, associatedData, parallelism: 4, tagLength: 32, memory: 32 },
|
||||
'c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8',
|
||||
],
|
||||
[
|
||||
'argon2id',
|
||||
{ secret, associatedData, parallelism: 4, tagLength: 32, memory: 32 },
|
||||
'0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659',
|
||||
],
|
||||
[
|
||||
'argon2d',
|
||||
{ message: '1234567890', nonce: 'saltsalt' },
|
||||
'd16ad773b1c6400d3193bc3e66271603e9de72bace20af3f89c236f5434cdec9' +
|
||||
'9072ddfc6b9c77ea9f386c0e8d7cb0c37cec6ec3277a22c92d5be58ef67c7eaa',
|
||||
],
|
||||
[
|
||||
'argon2id',
|
||||
{ message: '', parallelism: 4, tagLength: 32, memory: 32 },
|
||||
'0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a',
|
||||
],
|
||||
[
|
||||
'argon2d',
|
||||
{ message: '1234567890', nonce: 'saltsalt', parallelism: 2, memory: 65536 },
|
||||
'5ca0ab135de1241454840172696c301c7b8fd99a788cd11cf9699044cadf7fca' +
|
||||
'0a6e3762cb3043a71adf6553db3fd7925101b0ccf8868b098492a4addb2486bc',
|
||||
],
|
||||
[
|
||||
'argon2i',
|
||||
{ parallelism: 4, tagLength: 32, memory: 32 },
|
||||
'a9a7510e6db4d588ba3414cd0e094d480d683f97b9ccb612a544fe8ef65ba8e0',
|
||||
],
|
||||
[
|
||||
'argon2id',
|
||||
{ parallelism: 4, tagLength: 32, memory: 32 },
|
||||
'03aab965c12001c9d7d0d2de33192c0494b684bb148196d73c1df1acaf6d0c2e',
|
||||
],
|
||||
[
|
||||
'argon2d',
|
||||
{ message: '1234567890', nonce: 'saltsalt', parallelism: 2, tagLength: 128, memory: 65536 },
|
||||
'a86c83a19f0b234ecba8c275d16d059153f961e4c39ec9b1be98b3e73d791789' +
|
||||
'363682443ad594334048634e91c493affed0bc29fd329a0e553c00149d6db19a' +
|
||||
'f4e4a354aec14dbd575d78ba87d4a4bc4746666e7a4e6ee1572bbffc2eba308a' +
|
||||
'2d825cb7b41fde3a95d5cff0dfa2d0fdd636b32aea8b4a3c532742d330bd1b90',
|
||||
],
|
||||
];
|
||||
|
||||
// Test vectors that should fail.
|
||||
const bad = [
|
||||
['argon2id', { nonce: nonce.subarray(0, 7) }, 'parameters.nonce.byteLength'], // nonce.byteLength < 8
|
||||
['argon2id', { tagLength: 3 }, 'parameters.tagLength'], // tagLength < 4
|
||||
['argon2id', { tagLength: 2 ** 32 }, 'parameters.tagLength'], // tagLength > 2^(32)-1
|
||||
['argon2id', { passes: 0 }, 'parameters.passes'], // passes < 2
|
||||
['argon2id', { passes: 2 ** 32 }, 'parameters.passes'], // passes > 2^(32)-1
|
||||
['argon2id', { parallelism: 0 }, 'parameters.parallelism'], // parallelism < 1
|
||||
['argon2id', { parallelism: 2 ** 24 }, 'parameters.parallelism'], // Parallelism > 2^(24)-1
|
||||
['argon2id', { parallelism: 4, memory: 16 }, 'parameters.memory'], // Memory < 8 * parallelism
|
||||
['argon2id', { memory: 2 ** 32 }, 'parameters.memory'], // memory > 2^(32)-1
|
||||
];
|
||||
|
||||
for (const [algorithm, overrides, expected] of good) {
|
||||
const parameters = { ...defaults, ...overrides };
|
||||
const actual = runArgon2(algorithm, parameters);
|
||||
assert.strictEqual(actual.toString('hex'), expected);
|
||||
}
|
||||
|
||||
for (const [algorithm, overrides, param] of bad) {
|
||||
const expected = {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
message: new RegExp(`The value of "${param}" is out of range`),
|
||||
};
|
||||
const parameters = { ...defaults, ...overrides };
|
||||
assert.throws(() => crypto.argon2(algorithm, parameters, () => {}), expected);
|
||||
assert.throws(() => crypto.argon2Sync(algorithm, parameters), expected);
|
||||
}
|
||||
|
||||
for (const key of Object.keys(defaults)) {
|
||||
const expected = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: new RegExp(`"parameters\\.${key}"`),
|
||||
};
|
||||
const parameters = { ...defaults };
|
||||
delete parameters[key];
|
||||
assert.throws(() => crypto.argon2('argon2id', parameters, () => {}), expected);
|
||||
assert.throws(() => crypto.argon2Sync('argon2id', parameters), expected);
|
||||
}
|
||||
|
||||
{
|
||||
const expected = { code: 'ERR_INVALID_ARG_TYPE' };
|
||||
assert.throws(() => crypto.argon2(), expected);
|
||||
assert.throws(() => crypto.argon2('argon2id', null), expected);
|
||||
assert.throws(() => crypto.argon2('argon2id', defaults, null), expected);
|
||||
assert.throws(() => crypto.argon2('argon2id', defaults, {}), expected);
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ const { getSystemErrorName } = require('util');
|
|||
delete providers.MESSAGEPORT;
|
||||
delete providers.WORKER;
|
||||
// TODO(danbev): Test for these
|
||||
delete providers.ARGON2REQUEST;
|
||||
delete providers.JSUDPWRAP;
|
||||
delete providers.KEYPAIRGENREQUEST;
|
||||
delete providers.KEYGENREQUEST;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user