mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +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
|
<!-- YAML
|
||||||
added: v12.0.0
|
added: v12.0.0
|
||||||
changes:
|
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
|
- version: REPLACEME
|
||||||
pr-url: https://github.com/nodejs/node/pull/59537
|
pr-url: https://github.com/nodejs/node/pull/59537
|
||||||
description: Add support for SLH-DSA signing.
|
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
|
`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
|
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
|
||||||
maximum permissible value.
|
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.
|
If the `callback` function is provided this function uses libuv's threadpool.
|
||||||
|
|
||||||
|
|
@ -5831,6 +5837,9 @@ not introduce timing vulnerabilities.
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v12.0.0
|
added: v12.0.0
|
||||||
changes:
|
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
|
- version: REPLACEME
|
||||||
pr-url: https://github.com/nodejs/node/pull/59537
|
pr-url: https://github.com/nodejs/node/pull/59537
|
||||||
description: Add support for SLH-DSA signature verification.
|
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
|
`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
|
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
|
||||||
maximum permissible value.
|
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`.
|
The `signature` argument is the previously calculated signature for the `data`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,19 @@ function getDSASignatureEncoding(options) {
|
||||||
return kSigEncDER;
|
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) {
|
function getIntOption(name, options) {
|
||||||
const value = options[name];
|
const value = options[name];
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
|
|
@ -153,6 +166,9 @@ function signOneShot(algorithm, data, key, callback) {
|
||||||
// Options specific to (EC)DSA
|
// Options specific to (EC)DSA
|
||||||
const dsaSigEnc = getDSASignatureEncoding(key);
|
const dsaSigEnc = getDSASignatureEncoding(key);
|
||||||
|
|
||||||
|
// Options specific to Ed448 and ML-DSA
|
||||||
|
const context = getContext(key);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: keyData,
|
data: keyData,
|
||||||
format: keyFormat,
|
format: keyFormat,
|
||||||
|
|
@ -172,7 +188,7 @@ function signOneShot(algorithm, data, key, callback) {
|
||||||
pssSaltLength,
|
pssSaltLength,
|
||||||
rsaPadding,
|
rsaPadding,
|
||||||
dsaSigEnc,
|
dsaSigEnc,
|
||||||
undefined,
|
context,
|
||||||
undefined);
|
undefined);
|
||||||
|
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
|
|
@ -251,6 +267,9 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
|
||||||
// Options specific to (EC)DSA
|
// Options specific to (EC)DSA
|
||||||
const dsaSigEnc = getDSASignatureEncoding(key);
|
const dsaSigEnc = getDSASignatureEncoding(key);
|
||||||
|
|
||||||
|
// Options specific to Ed448 and ML-DSA
|
||||||
|
const context = getContext(key);
|
||||||
|
|
||||||
if (!isArrayBufferView(signature)) {
|
if (!isArrayBufferView(signature)) {
|
||||||
throw new ERR_INVALID_ARG_TYPE(
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
'signature',
|
'signature',
|
||||||
|
|
@ -278,7 +297,7 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
|
||||||
pssSaltLength,
|
pssSaltLength,
|
||||||
rsaPadding,
|
rsaPadding,
|
||||||
dsaSigEnc,
|
dsaSigEnc,
|
||||||
undefined,
|
context,
|
||||||
signature);
|
signature);
|
||||||
|
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,18 @@ bool SupportsContextString(const EVPKeyPointer& key) {
|
||||||
case EVP_PKEY_ML_DSA_44:
|
case EVP_PKEY_ML_DSA_44:
|
||||||
case EVP_PKEY_ML_DSA_65:
|
case EVP_PKEY_ML_DSA_65:
|
||||||
case EVP_PKEY_ML_DSA_87:
|
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
|
#endif
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const exec = require('child_process').exec;
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const fixtures = require('../common/fixtures');
|
const fixtures = require('../common/fixtures');
|
||||||
const {
|
const {
|
||||||
hasOpenSSL3,
|
hasOpenSSL,
|
||||||
opensslCli,
|
opensslCli,
|
||||||
} = require('../common/crypto');
|
} = require('../common/crypto');
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ const keySize = 2048;
|
||||||
key: keyPem,
|
key: keyPem,
|
||||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
||||||
});
|
});
|
||||||
}, { message: hasOpenSSL3 ?
|
}, { message: hasOpenSSL(3) ?
|
||||||
'error:1C8000A5:Provider routines::illegal or unsupported padding mode' :
|
'error:1C8000A5:Provider routines::illegal or unsupported padding mode' :
|
||||||
'bye, bye, error stack' });
|
'bye, bye, error stack' });
|
||||||
|
|
||||||
|
|
@ -344,7 +344,7 @@ assert.throws(
|
||||||
key: keyPem,
|
key: keyPem,
|
||||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
||||||
});
|
});
|
||||||
}, hasOpenSSL3 ? {
|
}, hasOpenSSL(3) ? {
|
||||||
code: 'ERR_OSSL_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE',
|
code: 'ERR_OSSL_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE',
|
||||||
message: /illegal or unsupported padding mode/,
|
message: /illegal or unsupported padding mode/,
|
||||||
} : {
|
} : {
|
||||||
|
|
@ -426,6 +426,7 @@ assert.throws(
|
||||||
{ private: fixtures.readKey('ed448_private.pem', 'ascii'),
|
{ private: fixtures.readKey('ed448_private.pem', 'ascii'),
|
||||||
public: fixtures.readKey('ed448_public.pem', 'ascii'),
|
public: fixtures.readKey('ed448_public.pem', 'ascii'),
|
||||||
algo: null,
|
algo: null,
|
||||||
|
supportsContext: true,
|
||||||
sigLen: 114 },
|
sigLen: 114 },
|
||||||
{ private: fixtures.readKey('rsa_private_2048.pem', 'ascii'),
|
{ private: fixtures.readKey('rsa_private_2048.pem', 'ascii'),
|
||||||
public: fixtures.readKey('rsa_public_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),
|
assert.strictEqual(crypto.verify(algo, data, pair.private, sig),
|
||||||
true);
|
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) => {
|
[1, {}, [], true, Infinity].forEach((input) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user