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
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
185 changes: 177 additions & 8 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 @@ -5786,7 +5789,12 @@ algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is
dependent upon the key type.

`algorithm` is required to be `null` or `undefined` for Ed25519, Ed448, and
ML-DSA.
ML-DSA. For Ed25519 and Ed448, this function uses the pure signature schemes
from [RFC 8032][] (or Ed25519ctx when a non-empty `context` is provided;
an empty or absent context uses pure Ed25519). Ed25519 and
Ed448 signatures produced by this function cannot be verified with
[`crypto.verifyDigest()`][] because it uses the Ed25519ph and Ed448ph prehash
variants which have different domain separation.

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
Expand All @@ -5808,9 +5816,79 @@ 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.
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519
(using Ed25519ctx from [RFC 8032][]), 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`. The resulting signatures are compatible with [`crypto.verify()`][]
and signatures produced by [`crypto.sign()`][] can be verified with
[`crypto.verifyDigest()`][].
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
respectively. `digest` must be the output of the appropriate prehash
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
Ed448ph). The resulting signatures can only be verified with
[`crypto.verifyDigest()`][], not with [`crypto.verify()`][], because
the prehash variants have different domain separation from the pure
Ed25519/Ed448 (or Ed25519ctx with context) variants used by
[`crypto.sign()`][] and [`crypto.verify()`][].
* ML-DSA: `algorithm` must be `null` or `undefined`. `digest` must be the
64-byte external mu value per FIPS 204. The resulting signatures are
compatible with [`crypto.verify()`][] when the mu value is correctly computed
from the message 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.

Expand Down Expand Up @@ -5870,6 +5948,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 @@ -5917,7 +5998,12 @@ Verifies the given signature for `data` using the given key and algorithm. If
key type.

`algorithm` is required to be `null` or `undefined` for Ed25519, Ed448, and
ML-DSA.
ML-DSA. For Ed25519 and Ed448, this function uses the pure signature schemes
from [RFC 8032][] (or Ed25519ctx when a non-empty `context` is provided;
an empty or absent context uses pure Ed25519). Ed25519 and
Ed448 signatures produced by [`crypto.signDigest()`][] cannot be verified with
this function because they use the Ed25519ph and Ed448ph prehash variants which
have different domain separation.

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
Expand All @@ -5939,9 +6025,10 @@ 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.
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519
(using Ed25519ctx from [RFC 8032][]), 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`.

Expand All @@ -5950,6 +6037,83 @@ 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`. Signatures produced by [`crypto.sign()`][] can be verified with
this function, and signatures produced by [`crypto.signDigest()`][] can be
verified with [`crypto.verify()`][].
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
respectively. `digest` must be the output of the appropriate prehash
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
Ed448ph). Only signatures produced by [`crypto.signDigest()`][] can be
verified with this function, not those from [`crypto.sign()`][], because
the prehash variants have different domain separation from the pure
Ed25519/Ed448 (or Ed25519ctx with context) variants used by
[`crypto.sign()`][] and [`crypto.verify()`][].
* ML-DSA: `algorithm` must be `null` or `undefined`. `digest` must be the
64-byte external mu value per FIPS 204. Signatures produced by
[`crypto.sign()`][] can be verified with this function when the mu value is
correctly computed from the message 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 @@ -6539,6 +6703,7 @@ See the [list of SSL OP Flags][] for details.
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
[RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt
[RFC 5280]: https://www.rfc-editor.org/rfc/rfc5280.txt
[RFC 8032]: https://www.rfc-editor.org/rfc/rfc8032.txt
[Web Crypto API documentation]: webcrypto.md
[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html
[`Buffer`]: buffer.md
Expand Down Expand Up @@ -6572,6 +6737,10 @@ 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.signDigest()`]: #cryptosigndigestalgorithm-digest-key-callback
[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback
[`crypto.verifyDigest()`]: #cryptoverifydigestalgorithm-digest-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