Skip to content
71 changes: 71 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3783,6 +3783,35 @@ 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

#ifdef OSSL_SIGNATURE_PARAM_MU
int EVPKeyCtxPointer::initForSignMessage(const OSSL_PARAM params[]) {
if (!ctx_) return 0;
return EVP_PKEY_sign_message_init(ctx_.get(), nullptr, params);
}

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

bool EVPKeyCtxPointer::initForEncrypt() {
if (!ctx_) return false;
return EVP_PKEY_encrypt_init(ctx_.get()) == 1;
Expand Down Expand Up @@ -4321,6 +4350,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 +4395,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
9 changes: 9 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,14 @@ 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
#ifdef OSSL_SIGNATURE_PARAM_MU
int initForSignMessage(const OSSL_PARAM params[]);
int initForVerifyMessage(const OSSL_PARAM params[]);
#endif

static EVPKeyCtxPointer New(const EVPKeyPointer& key);
static EVPKeyCtxPointer NewFromID(int id);
Expand Down
138 changes: 136 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,71 @@ 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.

The interpretation of `algorithm` and `digest` depends on the key type:

* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
`digest`.
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. `digest` must
be the output of the appropriate prehash function (SHA-512 for Ed25519ph,
SHAKE256 with 64-byte output for Ed448ph).
* ML-DSA: `algorithm` must be `null` or `undefined`. `digest` must be the
64-byte external mu value per FIPS 204.

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. Not supported for ML-DSA
keys because the context is already encoded into the mu value.

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

### `crypto.subtle`

<!-- YAML
Expand Down Expand Up @@ -5870,6 +5932,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 +6004,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 +6015,73 @@ 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.

The interpretation of `algorithm` and `digest` depends on the key type:

* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
`digest`.
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. `digest` must
be the output of the appropriate prehash function (SHA-512 for Ed25519ph,
SHAKE256 with 64-byte output for Ed448ph).
* ML-DSA: `algorithm` must be `null` or `undefined`. `digest` must be the
64-byte external mu value per FIPS 204.

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. Not supported for ML-DSA
keys because the context is already encoded into the mu value.

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`.

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

### `crypto.webcrypto`

<!-- YAML
Expand Down Expand Up @@ -6572,6 +6704,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
Loading
Loading