mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
crypto: support Ed448 and ML-DSA context parameter in node:crypto
PR-URL: https://github.com/nodejs/node/pull/59570 Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
0124e0e0d7
commit
a3cd430ef8
|
|
@ -5712,6 +5712,9 @@ Throws an error if FIPS mode is not available.
|
|||
<!-- YAML
|
||||
added: v12.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59570
|
||||
description: Add support for ML-DSA, Ed448, and SLH-DSA context parameter.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59537
|
||||
description: Add support for SLH-DSA signing.
|
||||
|
|
@ -5772,6 +5775,9 @@ additional properties can be passed:
|
|||
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
|
||||
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
|
||||
maximum permissible value.
|
||||
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed448, ML-DSA, and SLH-DSA,
|
||||
this option specifies the optional context to differentiate signatures generated
|
||||
for different purposes with the same key.
|
||||
|
||||
If the `callback` function is provided this function uses libuv's threadpool.
|
||||
|
||||
|
|
@ -5831,6 +5837,9 @@ not introduce timing vulnerabilities.
|
|||
<!-- YAML
|
||||
added: v12.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59570
|
||||
description: Add support for ML-DSA, Ed448, and SLH-DSA context parameter.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59537
|
||||
description: Add support for SLH-DSA signature verification.
|
||||
|
|
@ -5897,6 +5906,9 @@ additional properties can be passed:
|
|||
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
|
||||
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
|
||||
maximum permissible value.
|
||||
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed448, ML-DSA, and SLH-DSA,
|
||||
this option specifies the optional context to differentiate signatures generated
|
||||
for different purposes with the same key.
|
||||
|
||||
The `signature` argument is the previously calculated signature for the `data`.
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,19 @@ function getDSASignatureEncoding(options) {
|
|||
return kSigEncDER;
|
||||
}
|
||||
|
||||
function getContext(options) {
|
||||
if (options?.context === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!isArrayBufferView(options.context)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'options.context', ['Buffer', 'TypedArray', 'DataView'], options.context);
|
||||
}
|
||||
|
||||
return options.context;
|
||||
}
|
||||
|
||||
function getIntOption(name, options) {
|
||||
const value = options[name];
|
||||
if (value !== undefined) {
|
||||
|
|
@ -153,6 +166,9 @@ function signOneShot(algorithm, data, key, callback) {
|
|||
// Options specific to (EC)DSA
|
||||
const dsaSigEnc = getDSASignatureEncoding(key);
|
||||
|
||||
// Options specific to Ed448 and ML-DSA
|
||||
const context = getContext(key);
|
||||
|
||||
const {
|
||||
data: keyData,
|
||||
format: keyFormat,
|
||||
|
|
@ -172,7 +188,7 @@ function signOneShot(algorithm, data, key, callback) {
|
|||
pssSaltLength,
|
||||
rsaPadding,
|
||||
dsaSigEnc,
|
||||
undefined,
|
||||
context,
|
||||
undefined);
|
||||
|
||||
if (!callback) {
|
||||
|
|
@ -251,6 +267,9 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
|
|||
// Options specific to (EC)DSA
|
||||
const dsaSigEnc = getDSASignatureEncoding(key);
|
||||
|
||||
// Options specific to Ed448 and ML-DSA
|
||||
const context = getContext(key);
|
||||
|
||||
if (!isArrayBufferView(signature)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'signature',
|
||||
|
|
@ -278,7 +297,7 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
|
|||
pssSaltLength,
|
||||
rsaPadding,
|
||||
dsaSigEnc,
|
||||
undefined,
|
||||
context,
|
||||
signature);
|
||||
|
||||
if (!callback) {
|
||||
|
|
|
|||
|
|
@ -246,6 +246,18 @@ bool SupportsContextString(const EVPKeyPointer& key) {
|
|||
case EVP_PKEY_ML_DSA_44:
|
||||
case EVP_PKEY_ML_DSA_65:
|
||||
case EVP_PKEY_ML_DSA_87:
|
||||
case EVP_PKEY_SLH_DSA_SHA2_128F:
|
||||
case EVP_PKEY_SLH_DSA_SHA2_128S:
|
||||
case EVP_PKEY_SLH_DSA_SHA2_192F:
|
||||
case EVP_PKEY_SLH_DSA_SHA2_192S:
|
||||
case EVP_PKEY_SLH_DSA_SHA2_256F:
|
||||
case EVP_PKEY_SLH_DSA_SHA2_256S:
|
||||
case EVP_PKEY_SLH_DSA_SHAKE_128F:
|
||||
case EVP_PKEY_SLH_DSA_SHAKE_128S:
|
||||
case EVP_PKEY_SLH_DSA_SHAKE_192F:
|
||||
case EVP_PKEY_SLH_DSA_SHAKE_192S:
|
||||
case EVP_PKEY_SLH_DSA_SHAKE_256F:
|
||||
case EVP_PKEY_SLH_DSA_SHAKE_256S:
|
||||
#endif
|
||||
return true;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const exec = require('child_process').exec;
|
|||
const crypto = require('crypto');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const {
|
||||
hasOpenSSL3,
|
||||
hasOpenSSL,
|
||||
opensslCli,
|
||||
} = require('../common/crypto');
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ const keySize = 2048;
|
|||
key: keyPem,
|
||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
||||
});
|
||||
}, { message: hasOpenSSL3 ?
|
||||
}, { message: hasOpenSSL(3) ?
|
||||
'error:1C8000A5:Provider routines::illegal or unsupported padding mode' :
|
||||
'bye, bye, error stack' });
|
||||
|
||||
|
|
@ -344,7 +344,7 @@ assert.throws(
|
|||
key: keyPem,
|
||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
||||
});
|
||||
}, hasOpenSSL3 ? {
|
||||
}, hasOpenSSL(3) ? {
|
||||
code: 'ERR_OSSL_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE',
|
||||
message: /illegal or unsupported padding mode/,
|
||||
} : {
|
||||
|
|
@ -426,6 +426,7 @@ assert.throws(
|
|||
{ private: fixtures.readKey('ed448_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('ed448_public.pem', 'ascii'),
|
||||
algo: null,
|
||||
supportsContext: true,
|
||||
sigLen: 114 },
|
||||
{ private: fixtures.readKey('rsa_private_2048.pem', 'ascii'),
|
||||
public: fixtures.readKey('rsa_public_2048.pem', 'ascii'),
|
||||
|
|
@ -473,6 +474,55 @@ assert.throws(
|
|||
assert.strictEqual(crypto.verify(algo, data, pair.private, sig),
|
||||
true);
|
||||
});
|
||||
|
||||
if (pair.supportsContext && hasOpenSSL(3, 2)) {
|
||||
const data = Buffer.from('Hello world');
|
||||
{
|
||||
const context = new Uint8Array();
|
||||
const sig = crypto.sign(algo, data, { key: pair.private, context });
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public }, sig), true);
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context }, sig), true);
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context: crypto.randomBytes(30) }, sig), false);
|
||||
}
|
||||
|
||||
{
|
||||
const context = new Uint8Array(32);
|
||||
const sig = crypto.sign(algo, data, { key: pair.private, context });
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public }, sig), false);
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context }, sig), true);
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context: crypto.randomBytes(30) }, sig), false);
|
||||
}
|
||||
|
||||
assert.throws(() => crypto.sign(algo, data, { key: pair.private, context: new Uint8Array(256) }), {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
message: 'context string must be at most 255 bytes',
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
crypto.verify(algo, data, { key: pair.public, context: new Uint8Array(256) }, new Uint8Array());
|
||||
}, {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
message: 'context string must be at most 255 bytes',
|
||||
});
|
||||
} else if (pair.supportsContext) {
|
||||
const data = Buffer.from('Hello world');
|
||||
{
|
||||
const context = new Uint8Array();
|
||||
const sig = crypto.sign(algo, data, { key: pair.private, context });
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public }, sig), true);
|
||||
assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context }, sig), true);
|
||||
}
|
||||
|
||||
{
|
||||
const context = new Uint8Array(32);
|
||||
assert.throws(() => {
|
||||
crypto.sign(algo, data, { key: pair.private, context });
|
||||
}, { message: 'Context parameter is unsupported' });
|
||||
assert.throws(() => {
|
||||
crypto.verify(algo, data, { key: pair.public, context: crypto.randomBytes(30) }, crypto.randomBytes(32));
|
||||
}, { message: 'Context parameter is unsupported' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
[1, {}, [], true, Infinity].forEach((input) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user