Skip to content

Commit 8503f4d

Browse files
committed
crypto: add signDigest/verifyDigest and Ed25519ctx support
Resolves: #60263
1 parent abff716 commit 8503f4d

10 files changed

Lines changed: 1526 additions & 76 deletions

File tree

deps/ncrypto/ncrypto.cc

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3783,6 +3783,35 @@ bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) {
37833783
1;
37843784
}
37853785

3786+
bool EVPKeyCtxPointer::setSignatureMd(const Digest& md) {
3787+
if (!ctx_ || !md) return false;
3788+
return EVP_PKEY_CTX_set_signature_md(ctx_.get(), md.get()) == 1;
3789+
}
3790+
3791+
#if OPENSSL_VERSION_MAJOR >= 3
3792+
int EVPKeyCtxPointer::initForSignEx(const OSSL_PARAM params[]) {
3793+
if (!ctx_) return 0;
3794+
return EVP_PKEY_sign_init_ex(ctx_.get(), params);
3795+
}
3796+
3797+
int EVPKeyCtxPointer::initForVerifyEx(const OSSL_PARAM params[]) {
3798+
if (!ctx_) return 0;
3799+
return EVP_PKEY_verify_init_ex(ctx_.get(), params);
3800+
}
3801+
#endif
3802+
3803+
#ifdef OSSL_SIGNATURE_PARAM_MU
3804+
int EVPKeyCtxPointer::initForSignMessage(const OSSL_PARAM params[]) {
3805+
if (!ctx_) return 0;
3806+
return EVP_PKEY_sign_message_init(ctx_.get(), nullptr, params);
3807+
}
3808+
3809+
int EVPKeyCtxPointer::initForVerifyMessage(const OSSL_PARAM params[]) {
3810+
if (!ctx_) return 0;
3811+
return EVP_PKEY_verify_message_init(ctx_.get(), nullptr, params);
3812+
}
3813+
#endif
3814+
37863815
bool EVPKeyCtxPointer::initForEncrypt() {
37873816
if (!ctx_) return false;
37883817
return EVP_PKEY_encrypt_init(ctx_.get()) == 1;
@@ -4321,6 +4350,27 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::signInitWithContext(
43214350
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
43224351
EVP_PKEY_CTX* ctx = nullptr;
43234352

4353+
#ifdef OSSL_SIGNATURE_PARAM_INSTANCE
4354+
// Ed25519ctx requires the INSTANCE param to enable context string support.
4355+
// Ed25519 pure mode ignores context strings without this.
4356+
if (key.id() == EVP_PKEY_ED25519) {
4357+
const OSSL_PARAM params[] = {
4358+
OSSL_PARAM_construct_utf8_string(
4359+
OSSL_SIGNATURE_PARAM_INSTANCE, const_cast<char*>("Ed25519ctx"), 0),
4360+
OSSL_PARAM_construct_octet_string(
4361+
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
4362+
const_cast<unsigned char*>(context_string.data),
4363+
context_string.len),
4364+
OSSL_PARAM_END};
4365+
4366+
if (!EVP_DigestSignInit_ex(
4367+
ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) {
4368+
return std::nullopt;
4369+
}
4370+
return ctx;
4371+
}
4372+
#endif // OSSL_SIGNATURE_PARAM_INSTANCE
4373+
43244374
const OSSL_PARAM params[] = {
43254375
OSSL_PARAM_construct_octet_string(
43264376
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
@@ -4345,6 +4395,27 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInitWithContext(
43454395
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
43464396
EVP_PKEY_CTX* ctx = nullptr;
43474397

4398+
#ifdef OSSL_SIGNATURE_PARAM_INSTANCE
4399+
// Ed25519ctx requires the INSTANCE param to enable context string support.
4400+
// Ed25519 pure mode ignores context strings without this.
4401+
if (key.id() == EVP_PKEY_ED25519) {
4402+
const OSSL_PARAM params[] = {
4403+
OSSL_PARAM_construct_utf8_string(
4404+
OSSL_SIGNATURE_PARAM_INSTANCE, const_cast<char*>("Ed25519ctx"), 0),
4405+
OSSL_PARAM_construct_octet_string(
4406+
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
4407+
const_cast<unsigned char*>(context_string.data),
4408+
context_string.len),
4409+
OSSL_PARAM_END};
4410+
4411+
if (!EVP_DigestVerifyInit_ex(
4412+
ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) {
4413+
return std::nullopt;
4414+
}
4415+
return ctx;
4416+
}
4417+
#endif // OSSL_SIGNATURE_PARAM_INSTANCE
4418+
43484419
const OSSL_PARAM params[] = {
43494420
OSSL_PARAM_construct_octet_string(
43504421
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,

deps/ncrypto/ncrypto.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ class EVPKeyCtxPointer final {
798798
bool setRsaOaepLabel(DataPointer&& data);
799799

800800
bool setSignatureMd(const EVPMDCtxPointer& md);
801+
bool setSignatureMd(const Digest& md);
801802

802803
bool publicCheck() const;
803804
bool privateCheck() const;
@@ -821,6 +822,14 @@ class EVPKeyCtxPointer final {
821822
bool initForKeygen();
822823
int initForVerify();
823824
int initForSign();
825+
#if OPENSSL_VERSION_MAJOR >= 3
826+
int initForVerifyEx(const OSSL_PARAM params[]);
827+
int initForSignEx(const OSSL_PARAM params[]);
828+
#endif
829+
#ifdef OSSL_SIGNATURE_PARAM_MU
830+
int initForSignMessage(const OSSL_PARAM params[]);
831+
int initForVerifyMessage(const OSSL_PARAM params[]);
832+
#endif
824833

825834
static EVPKeyCtxPointer New(const EVPKeyPointer& key);
826835
static EVPKeyCtxPointer NewFromID(int id);

doc/api/crypto.md

Lines changed: 177 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5745,6 +5745,9 @@ Throws an error if FIPS mode is not available.
57455745
<!-- YAML
57465746
added: v12.0.0
57475747
changes:
5748+
- version: REPLACEME
5749+
pr-url: https://github.com/nodejs/node/pull/62345
5750+
description: Add support for Ed25519 context parameter.
57485751
- version: v24.8.0
57495752
pr-url: https://github.com/nodejs/node/pull/59570
57505753
description: Add support for ML-DSA, Ed448, and SLH-DSA context parameter.
@@ -5786,7 +5789,12 @@ algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is
57865789
dependent upon the key type.
57875790

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

57915799
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
57925800
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
@@ -5808,9 +5816,79 @@ additional properties can be passed:
58085816
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
58095817
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
58105818
maximum permissible value.
5811-
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed448, ML-DSA, and SLH-DSA,
5812-
this option specifies the optional context to differentiate signatures generated
5813-
for different purposes with the same key.
5819+
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519
5820+
(using Ed25519ctx from [RFC 8032][]), Ed448, ML-DSA, and SLH-DSA,
5821+
this option specifies the optional context to differentiate signatures
5822+
generated for different purposes with the same key.
5823+
5824+
If the `callback` function is provided this function uses libuv's threadpool.
5825+
5826+
### `crypto.signDigest(algorithm, digest, key[, callback])`
5827+
5828+
<!-- YAML
5829+
added: REPLACEME
5830+
-->
5831+
5832+
<!--lint disable maximum-line-length remark-lint-->
5833+
5834+
* `algorithm` {string | null | undefined}
5835+
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
5836+
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
5837+
* `callback` {Function}
5838+
* `err` {Error}
5839+
* `signature` {Buffer}
5840+
* Returns: {Buffer} if the `callback` function is not provided.
5841+
5842+
<!--lint enable maximum-line-length remark-lint-->
5843+
5844+
Calculates and returns the signature for `digest` using the given private key
5845+
and algorithm. Unlike [`crypto.sign()`][], this function does not hash the data
5846+
internally — `digest` is expected to be a pre-computed hash digest.
5847+
5848+
The interpretation of `algorithm` and `digest` depends on the key type:
5849+
5850+
* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
5851+
`digest`. The resulting signatures are compatible with [`crypto.verify()`][]
5852+
and signatures produced by [`crypto.sign()`][] can be verified with
5853+
[`crypto.verifyDigest()`][].
5854+
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
5855+
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
5856+
respectively. `digest` must be the output of the appropriate prehash
5857+
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
5858+
Ed448ph). The resulting signatures can only be verified with
5859+
[`crypto.verifyDigest()`][], not with [`crypto.verify()`][], because
5860+
the prehash variants have different domain separation from the pure
5861+
Ed25519/Ed448 (or Ed25519ctx with context) variants used by
5862+
[`crypto.sign()`][] and [`crypto.verify()`][].
5863+
* ML-DSA: `algorithm` must be `null` or `undefined`. `digest` must be the
5864+
64-byte external mu value per FIPS 204. The resulting signatures are
5865+
compatible with [`crypto.verify()`][] when the mu value is correctly computed
5866+
from the message per FIPS 204.
5867+
5868+
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
5869+
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
5870+
additional properties can be passed:
5871+
5872+
* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
5873+
format of the generated signature. It can be one of the following:
5874+
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
5875+
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
5876+
* `padding` {integer} Optional padding value for RSA, one of the following:
5877+
5878+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
5879+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
5880+
5881+
`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
5882+
used to create the digest as specified in section 3.1 of [RFC 4055][].
5883+
* `saltLength` {integer} Salt length for when padding is
5884+
`RSA_PKCS1_PSS_PADDING`. The special value
5885+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
5886+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
5887+
maximum permissible value.
5888+
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519ph and Ed448ph,
5889+
this option specifies the optional context to differentiate signatures
5890+
generated for different purposes with the same key. Not supported for ML-DSA
5891+
keys because the context is already encoded into the mu value.
58145892

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

@@ -5870,6 +5948,9 @@ not introduce timing vulnerabilities.
58705948
<!-- YAML
58715949
added: v12.0.0
58725950
changes:
5951+
- version: REPLACEME
5952+
pr-url: https://github.com/nodejs/node/pull/62345
5953+
description: Add support for Ed25519 context parameter.
58735954
- version: v24.8.0
58745955
pr-url: https://github.com/nodejs/node/pull/59570
58755956
description: Add support for ML-DSA, Ed448, and SLH-DSA context parameter.
@@ -5917,7 +5998,12 @@ Verifies the given signature for `data` using the given key and algorithm. If
59175998
key type.
59185999

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

59226008
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
59236009
passed to [`crypto.createPublicKey()`][]. If it is an object, the following
@@ -5939,9 +6025,10 @@ additional properties can be passed:
59396025
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
59406026
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
59416027
maximum permissible value.
5942-
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed448, ML-DSA, and SLH-DSA,
5943-
this option specifies the optional context to differentiate signatures generated
5944-
for different purposes with the same key.
6028+
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519
6029+
(using Ed25519ctx from [RFC 8032][]), Ed448, ML-DSA, and SLH-DSA,
6030+
this option specifies the optional context to differentiate signatures
6031+
generated for different purposes with the same key.
59456032

59466033
The `signature` argument is the previously calculated signature for the `data`.
59476034

@@ -5950,6 +6037,83 @@ key may be passed for `key`.
59506037

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

6040+
### `crypto.verifyDigest(algorithm, digest, key, signature[, callback])`
6041+
6042+
<!-- YAML
6043+
added: REPLACEME
6044+
-->
6045+
6046+
<!--lint disable maximum-line-length remark-lint-->
6047+
6048+
* `algorithm` {string|null|undefined}
6049+
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
6050+
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
6051+
* `signature` {ArrayBuffer|Buffer|TypedArray|DataView}
6052+
* `callback` {Function}
6053+
* `err` {Error}
6054+
* `result` {boolean}
6055+
* Returns: {boolean} `true` or `false` depending on the validity of the
6056+
signature for the digest and public key if the `callback` function is not
6057+
provided.
6058+
6059+
<!--lint enable maximum-line-length remark-lint-->
6060+
6061+
Verifies the given signature for `digest` using the given key and algorithm.
6062+
Unlike [`crypto.verify()`][], this function does not hash the data
6063+
internally — `digest` is expected to be a pre-computed hash digest.
6064+
6065+
The interpretation of `algorithm` and `digest` depends on the key type:
6066+
6067+
* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
6068+
`digest`. Signatures produced by [`crypto.sign()`][] can be verified with
6069+
this function, and signatures produced by [`crypto.signDigest()`][] can be
6070+
verified with [`crypto.verify()`][].
6071+
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
6072+
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
6073+
respectively. `digest` must be the output of the appropriate prehash
6074+
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
6075+
Ed448ph). Only signatures produced by [`crypto.signDigest()`][] can be
6076+
verified with this function, not those from [`crypto.sign()`][], because
6077+
the prehash variants have different domain separation from the pure
6078+
Ed25519/Ed448 (or Ed25519ctx with context) variants used by
6079+
[`crypto.sign()`][] and [`crypto.verify()`][].
6080+
* ML-DSA: `algorithm` must be `null` or `undefined`. `digest` must be the
6081+
64-byte external mu value per FIPS 204. Signatures produced by
6082+
[`crypto.sign()`][] can be verified with this function when the mu value is
6083+
correctly computed from the message per FIPS 204.
6084+
6085+
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
6086+
passed to [`crypto.createPublicKey()`][]. If it is an object, the following
6087+
additional properties can be passed:
6088+
6089+
* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
6090+
format of the signature. It can be one of the following:
6091+
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
6092+
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
6093+
* `padding` {integer} Optional padding value for RSA, one of the following:
6094+
6095+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
6096+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
6097+
6098+
`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
6099+
used to create the digest as specified in section 3.1 of [RFC 4055][].
6100+
* `saltLength` {integer} Salt length for when padding is
6101+
`RSA_PKCS1_PSS_PADDING`. The special value
6102+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
6103+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
6104+
maximum permissible value.
6105+
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519ph and Ed448ph,
6106+
this option specifies the optional context to differentiate signatures
6107+
generated for different purposes with the same key. Not supported for ML-DSA
6108+
keys because the context is already encoded into the mu value.
6109+
6110+
The `signature` argument is the previously calculated signature for the `digest`.
6111+
6112+
Because public keys can be derived from private keys, a private key or a public
6113+
key may be passed for `key`.
6114+
6115+
If the `callback` function is provided this function uses libuv's threadpool.
6116+
59536117
### `crypto.webcrypto`
59546118

59556119
<!-- YAML
@@ -6539,6 +6703,7 @@ See the [list of SSL OP Flags][] for details.
65396703
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
65406704
[RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt
65416705
[RFC 5280]: https://www.rfc-editor.org/rfc/rfc5280.txt
6706+
[RFC 8032]: https://www.rfc-editor.org/rfc/rfc8032.txt
65426707
[Web Crypto API documentation]: webcrypto.md
65436708
[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html
65446709
[`Buffer`]: buffer.md
@@ -6572,6 +6737,10 @@ See the [list of SSL OP Flags][] for details.
65726737
[`crypto.publicEncrypt()`]: #cryptopublicencryptkey-buffer
65736738
[`crypto.randomBytes()`]: #cryptorandombytessize-callback
65746739
[`crypto.randomFill()`]: #cryptorandomfillbuffer-offset-size-callback
6740+
[`crypto.sign()`]: #cryptosignalgorithm-data-key-callback
6741+
[`crypto.signDigest()`]: #cryptosigndigestalgorithm-digest-key-callback
6742+
[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback
6743+
[`crypto.verifyDigest()`]: #cryptoverifydigestalgorithm-digest-key-signature-callback
65756744
[`crypto.webcrypto.getRandomValues()`]: webcrypto.md#cryptogetrandomvaluestypedarray
65766745
[`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto
65776746
[`decipher.final()`]: #decipherfinaloutputencoding

lib/crypto.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ const {
103103
const {
104104
Sign,
105105
signOneShot,
106+
signDigestOneShot,
106107
Verify,
107108
verifyOneShot,
109+
verifyDigestOneShot,
108110
} = require('internal/crypto/sig');
109111
const {
110112
Hash,
@@ -223,11 +225,13 @@ module.exports = {
223225
scrypt,
224226
scryptSync,
225227
sign: signOneShot,
228+
signDigest: signDigestOneShot,
226229
setEngine,
227230
timingSafeEqual,
228231
getFips,
229232
setFips,
230233
verify: verifyOneShot,
234+
verifyDigest: verifyDigestOneShot,
231235
hash,
232236
encapsulate,
233237
decapsulate,

0 commit comments

Comments
 (0)