Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3783,6 +3783,23 @@ bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) {
1;
}

bool EVPKeyCtxPointer::setSignatureMd(const Digest& md) {
if (!ctx_ || !md) return false;
return EVP_PKEY_CTX_set_signature_md(ctx_.get(), md.get()) == 1;
}

#if OPENSSL_VERSION_MAJOR >= 3
int EVPKeyCtxPointer::initForSignEx(const OSSL_PARAM params[]) {
if (!ctx_) return 0;
return EVP_PKEY_sign_init_ex(ctx_.get(), params);
}

int EVPKeyCtxPointer::initForVerifyEx(const OSSL_PARAM params[]) {
if (!ctx_) return 0;
return EVP_PKEY_verify_init_ex(ctx_.get(), params);
}
#endif

bool EVPKeyCtxPointer::initForEncrypt() {
if (!ctx_) return false;
return EVP_PKEY_encrypt_init(ctx_.get()) == 1;
Expand Down Expand Up @@ -4321,6 +4338,27 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::signInitWithContext(
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
EVP_PKEY_CTX* ctx = nullptr;

#ifdef OSSL_SIGNATURE_PARAM_INSTANCE
// Ed25519ctx requires the INSTANCE param to enable context string support.
// Ed25519 pure mode ignores context strings without this.
if (key.id() == EVP_PKEY_ED25519) {
const OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(
OSSL_SIGNATURE_PARAM_INSTANCE, const_cast<char*>("Ed25519ctx"), 0),
OSSL_PARAM_construct_octet_string(
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
const_cast<unsigned char*>(context_string.data),
context_string.len),
OSSL_PARAM_END};

if (!EVP_DigestSignInit_ex(
ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) {
return std::nullopt;
}
return ctx;
}
#endif // OSSL_SIGNATURE_PARAM_INSTANCE

const OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
Expand All @@ -4345,6 +4383,27 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInitWithContext(
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
EVP_PKEY_CTX* ctx = nullptr;

#ifdef OSSL_SIGNATURE_PARAM_INSTANCE
// Ed25519ctx requires the INSTANCE param to enable context string support.
// Ed25519 pure mode ignores context strings without this.
if (key.id() == EVP_PKEY_ED25519) {
const OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(
OSSL_SIGNATURE_PARAM_INSTANCE, const_cast<char*>("Ed25519ctx"), 0),
OSSL_PARAM_construct_octet_string(
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
const_cast<unsigned char*>(context_string.data),
context_string.len),
OSSL_PARAM_END};

if (!EVP_DigestVerifyInit_ex(
ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) {
return std::nullopt;
}
return ctx;
}
#endif // OSSL_SIGNATURE_PARAM_INSTANCE

const OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
Expand Down
5 changes: 5 additions & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ class EVPKeyCtxPointer final {
bool setRsaOaepLabel(DataPointer&& data);

bool setSignatureMd(const EVPMDCtxPointer& md);
bool setSignatureMd(const Digest& md);

bool publicCheck() const;
bool privateCheck() const;
Expand All @@ -821,6 +822,10 @@ class EVPKeyCtxPointer final {
bool initForKeygen();
int initForVerify();
int initForSign();
#if OPENSSL_VERSION_MAJOR >= 3
int initForVerifyEx(const OSSL_PARAM params[]);
int initForSignEx(const OSSL_PARAM params[]);
#endif

static EVPKeyCtxPointer New(const EVPKeyPointer& key);
static EVPKeyCtxPointer NewFromID(int id);
Expand Down
132 changes: 130 additions & 2 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -5745,6 +5745,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/62345
description: Add support for Ed25519 context parameter.
- version: v24.8.0
pr-url: https://github.com/nodejs/node/pull/59570
description: Add support for ML-DSA, Ed448, and SLH-DSA context parameter.
Expand Down Expand Up @@ -5808,12 +5811,68 @@ 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,
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519, 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.

### `crypto.signDigest(algorithm, digest, key[, callback])`

<!-- YAML
added: REPLACEME
-->

<!--lint disable maximum-line-length remark-lint-->

* `algorithm` {string | null | undefined}
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
* `callback` {Function}
* `err` {Error}
* `signature` {Buffer}
* Returns: {Buffer} if the `callback` function is not provided.

<!--lint enable maximum-line-length remark-lint-->

Calculates and returns the signature for `digest` using the given private key
and algorithm. Unlike [`crypto.sign()`][], this function does not hash the data
internally — `digest` is expected to be a pre-computed hash digest.

For RSA, ECDSA, and DSA keys, `algorithm` identifies the hash function that was
used to create `digest`. For Ed25519 and Ed448 keys, `algorithm` must be `null`
or `undefined`, and `digest` must be the output of the appropriate prehash
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for Ed448ph).

If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
additional properties can be passed:

* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
format of the generated signature. It can be one of the following:
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
* `padding` {integer} Optional padding value for RSA, one of the following:

* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`

`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
used to create the digest as specified in section 3.1 of [RFC 4055][].
* `saltLength` {integer} Salt length for when padding is
`RSA_PKCS1_PSS_PADDING`. The special value
`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 Ed25519ph and Ed448ph,
this option specifies the optional context to differentiate signatures
generated for different purposes with the same key.

This function does not support key types that require one-shot signing without
prehash variants, such as ML-DSA and SLH-DSA.

If the `callback` function is provided this function uses libuv's threadpool.

### `crypto.subtle`

<!-- YAML
Expand Down Expand Up @@ -5870,6 +5929,9 @@ not introduce timing vulnerabilities.
<!-- YAML
added: v12.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/62345
description: Add support for Ed25519 context parameter.
- version: v24.8.0
pr-url: https://github.com/nodejs/node/pull/59570
description: Add support for ML-DSA, Ed448, and SLH-DSA context parameter.
Expand Down Expand Up @@ -5939,7 +6001,7 @@ 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,
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519, Ed448, ML-DSA, and SLH-DSA,
this option specifies the optional context to differentiate signatures generated
for different purposes with the same key.

Expand All @@ -5950,6 +6012,70 @@ key may be passed for `key`.

If the `callback` function is provided this function uses libuv's threadpool.

### `crypto.verifyDigest(algorithm, digest, key, signature[, callback])`

<!-- YAML
added: REPLACEME
-->

<!--lint disable maximum-line-length remark-lint-->

* `algorithm` {string|null|undefined}
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
* `signature` {ArrayBuffer|Buffer|TypedArray|DataView}
* `callback` {Function}
* `err` {Error}
* `result` {boolean}
* Returns: {boolean} `true` or `false` depending on the validity of the
signature for the digest and public key if the `callback` function is not
provided.

<!--lint enable maximum-line-length remark-lint-->

Verifies the given signature for `digest` using the given key and algorithm.
Unlike [`crypto.verify()`][], this function does not hash the data
internally — `digest` is expected to be a pre-computed hash digest.

For RSA, ECDSA, and DSA keys, `algorithm` identifies the hash function that was
used to create `digest`. For Ed25519 and Ed448 keys, `algorithm` must be `null`
or `undefined`, and `digest` must be the output of the appropriate prehash
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for Ed448ph).

If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
passed to [`crypto.createPublicKey()`][]. If it is an object, the following
additional properties can be passed:

* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
format of the signature. It can be one of the following:
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
* `padding` {integer} Optional padding value for RSA, one of the following:

* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`

`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
used to create the digest as specified in section 3.1 of [RFC 4055][].
* `saltLength` {integer} Salt length for when padding is
`RSA_PKCS1_PSS_PADDING`. The special value
`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 Ed25519ph and Ed448ph,
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 `digest`.

Because public keys can be derived from private keys, a private key or a public
key may be passed for `key`.

This function does not support key types that require one-shot verification
without prehash variants, such as ML-DSA and SLH-DSA.

If the `callback` function is provided this function uses libuv's threadpool.

### `crypto.webcrypto`

<!-- YAML
Expand Down Expand Up @@ -6572,6 +6698,8 @@ See the [list of SSL OP Flags][] for details.
[`crypto.publicEncrypt()`]: #cryptopublicencryptkey-buffer
[`crypto.randomBytes()`]: #cryptorandombytessize-callback
[`crypto.randomFill()`]: #cryptorandomfillbuffer-offset-size-callback
[`crypto.sign()`]: #cryptosignalgorithm-data-key-callback
[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback
[`crypto.webcrypto.getRandomValues()`]: webcrypto.md#cryptogetrandomvaluestypedarray
[`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto
[`decipher.final()`]: #decipherfinaloutputencoding
Expand Down
4 changes: 4 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ const {
const {
Sign,
signOneShot,
signDigestOneShot,
Verify,
verifyOneShot,
verifyDigestOneShot,
} = require('internal/crypto/sig');
const {
Hash,
Expand Down Expand Up @@ -223,11 +225,13 @@ module.exports = {
scrypt,
scryptSync,
sign: signOneShot,
signDigest: signDigestOneShot,
setEngine,
timingSafeEqual,
getFips,
setFips,
verify: verifyOneShot,
verifyDigest: verifyDigestOneShot,
hash,
encapsulate,
decapsulate,
Expand Down
35 changes: 28 additions & 7 deletions lib/internal/crypto/sig.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,14 @@ Sign.prototype.sign = function sign(options, encoding) {
return ret;
};

function signOneShot(algorithm, data, key, callback) {
function signOneShotImpl(algorithm, data, key, callback, prehashed) {
if (algorithm != null)
validateString(algorithm, 'algorithm');

if (callback !== undefined)
validateFunction(callback, 'callback');

data = getArrayBufferOrView(data, 'data');
data = getArrayBufferOrView(data, prehashed ? 'digest' : 'data');

if (!key)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
Expand Down Expand Up @@ -194,7 +194,8 @@ function signOneShot(algorithm, data, key, callback) {
rsaPadding,
dsaSigEnc,
context,
undefined);
undefined,
prehashed);

if (!callback) {
const { 0: err, 1: signature } = job.run();
Expand All @@ -211,6 +212,10 @@ function signOneShot(algorithm, data, key, callback) {
job.run();
}

function signOneShot(algorithm, data, key, callback) {
return signOneShotImpl(algorithm, data, key, callback, false);
}

function Verify(algorithm, options) {
if (!(this instanceof Verify))
return new Verify(algorithm, options);
Expand Down Expand Up @@ -248,18 +253,19 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
rsaPadding, pssSaltLength, dsaSigEnc);
};

function verifyOneShot(algorithm, data, key, signature, callback) {
function verifyOneShotImpl(algorithm, data, key, signature, callback, prehashed) {
if (algorithm != null)
validateString(algorithm, 'algorithm');

if (callback !== undefined)
validateFunction(callback, 'callback');

data = getArrayBufferOrView(data, 'data');
const dataName = prehashed ? 'digest' : 'data';
data = getArrayBufferOrView(data, dataName);

if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data',
dataName,
['Buffer', 'TypedArray', 'DataView'],
data,
);
Expand Down Expand Up @@ -303,7 +309,8 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
rsaPadding,
dsaSigEnc,
context,
signature);
signature,
prehashed);

if (!callback) {
const { 0: err, 1: result } = job.run();
Expand All @@ -320,9 +327,23 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
job.run();
}

function verifyOneShot(algorithm, data, key, signature, callback) {
return verifyOneShotImpl(algorithm, data, key, signature, callback, false);
}

function signDigestOneShot(algorithm, digest, key, callback) {
return signOneShotImpl(algorithm, digest, key, callback, true);
}

function verifyDigestOneShot(algorithm, digest, key, signature, callback) {
return verifyOneShotImpl(algorithm, digest, key, signature, callback, true);
}

module.exports = {
Sign,
signOneShot,
signDigestOneShot,
Verify,
verifyOneShot,
verifyDigestOneShot,
};
Loading
Loading