Skip to content

Commit 3d278ba

Browse files
committed
crypto: support deterministic ECDSA/DSA signatures
Add dsaNonceType option to sign/verify node:crypto APIs. When set to 'deterministic', uses deterministic digital signature generation procedure per RFC 6979.
1 parent 65b521f commit 3d278ba

File tree

14 files changed

+2072
-16
lines changed

14 files changed

+2072
-16
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3877,6 +3877,18 @@ bool EVPKeyCtxPointer::signInto(const Buffer<const unsigned char>& data,
38773877
return true;
38783878
}
38793879

3880+
bool EVPKeyCtxPointer::setNonceType(unsigned int type) {
3881+
#ifdef OSSL_SIGNATURE_PARAM_NONCE_TYPE
3882+
if (!ctx_) return false;
3883+
OSSL_PARAM params[] = {
3884+
OSSL_PARAM_construct_uint(OSSL_SIGNATURE_PARAM_NONCE_TYPE, &type),
3885+
OSSL_PARAM_END};
3886+
return EVP_PKEY_CTX_set_params(ctx_.get(), params) == 1;
3887+
#else
3888+
return false;
3889+
#endif
3890+
}
3891+
38803892
// ============================================================================
38813893

38823894
namespace {
@@ -4362,6 +4374,50 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInitWithContext(
43624374
#endif
43634375
}
43644376

4377+
std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::signInitDeterministic(
4378+
const EVPKeyPointer& key, const Digest& digest) {
4379+
#ifdef OSSL_SIGNATURE_PARAM_NONCE_TYPE
4380+
EVP_PKEY_CTX* ctx = nullptr;
4381+
unsigned int nonce_type = 1;
4382+
4383+
const OSSL_PARAM params[] = {
4384+
OSSL_PARAM_construct_uint(OSSL_SIGNATURE_PARAM_NONCE_TYPE, &nonce_type),
4385+
OSSL_PARAM_END};
4386+
4387+
const char* md_name = digest ? EVP_MD_get0_name(digest) : nullptr;
4388+
4389+
if (!EVP_DigestSignInit_ex(
4390+
ctx_.get(), &ctx, md_name, nullptr, nullptr, key.get(), params)) {
4391+
return std::nullopt;
4392+
}
4393+
return ctx;
4394+
#else
4395+
return std::nullopt;
4396+
#endif
4397+
}
4398+
4399+
std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInitDeterministic(
4400+
const EVPKeyPointer& key, const Digest& digest) {
4401+
#ifdef OSSL_SIGNATURE_PARAM_NONCE_TYPE
4402+
EVP_PKEY_CTX* ctx = nullptr;
4403+
unsigned int nonce_type = 1;
4404+
4405+
const OSSL_PARAM params[] = {
4406+
OSSL_PARAM_construct_uint(OSSL_SIGNATURE_PARAM_NONCE_TYPE, &nonce_type),
4407+
OSSL_PARAM_END};
4408+
4409+
const char* md_name = digest ? EVP_MD_get0_name(digest) : nullptr;
4410+
4411+
if (!EVP_DigestVerifyInit_ex(
4412+
ctx_.get(), &ctx, md_name, nullptr, nullptr, key.get(), params)) {
4413+
return std::nullopt;
4414+
}
4415+
return ctx;
4416+
#else
4417+
return std::nullopt;
4418+
#endif
4419+
}
4420+
43654421
DataPointer EVPMDCtxPointer::signOneShot(
43664422
const Buffer<const unsigned char>& buf) const {
43674423
if (!ctx_) return {};

deps/ncrypto/ncrypto.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ class EVPKeyCtxPointer final {
807807
DataPointer sign(const Buffer<const unsigned char>& data);
808808
bool signInto(const Buffer<const unsigned char>& data,
809809
Buffer<unsigned char>* sig);
810+
bool setNonceType(unsigned int type);
810811

811812
static constexpr int kDefaultRsaExponent = 0x10001;
812813

@@ -1423,6 +1424,11 @@ class EVPMDCtxPointer final {
14231424
const Digest& digest,
14241425
const Buffer<const unsigned char>& context_string);
14251426

1427+
std::optional<EVP_PKEY_CTX*> signInitDeterministic(const EVPKeyPointer& key,
1428+
const Digest& digest);
1429+
std::optional<EVP_PKEY_CTX*> verifyInitDeterministic(const EVPKeyPointer& key,
1430+
const Digest& digest);
1431+
14261432
DataPointer signOneShot(const Buffer<const unsigned char>& buf) const;
14271433
DataPointer sign(const Buffer<const unsigned char>& buf) const;
14281434
bool verify(const Buffer<const unsigned char>& buf,

doc/api/crypto.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,7 @@ changes:
23582358

23592359
* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
23602360
* `dsaEncoding` {string}
2361+
* `dsaNonceType` {string}
23612362
* `padding` {integer}
23622363
* `saltLength` {integer}
23632364
* `outputEncoding` {string} The [encoding][] of the return value.
@@ -2376,6 +2377,10 @@ object, the following additional properties can be passed:
23762377
format of the generated signature. It can be one of the following:
23772378
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
23782379
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
2380+
* `dsaNonceType` {string} For DSA and ECDSA, this option specifies the
2381+
nonce generation method. It can be one of the following:
2382+
* `'random'` (default): Use a random nonce.
2383+
* `'deterministic'`[^openssl32]: Use a deterministic nonce as defined in [RFC 6979][].
23792384
* `padding` {integer} Optional padding value for RSA, one of the following:
23802385

23812386
* `crypto.constants.RSA_PKCS1_PADDING` (default)
@@ -2488,6 +2493,7 @@ changes:
24882493

24892494
* `object` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
24902495
* `dsaEncoding` {string}
2496+
* `dsaNonceType` {string}
24912497
* `padding` {integer}
24922498
* `saltLength` {integer}
24932499
* `signature` {string|ArrayBuffer|Buffer|TypedArray|DataView}
@@ -2507,6 +2513,10 @@ object, the following additional properties can be passed:
25072513
format of the signature. It can be one of the following:
25082514
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
25092515
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
2516+
* `dsaNonceType` {string} For DSA and ECDSA, this option specifies the
2517+
nonce generation method used during signing. It can be one of the following:
2518+
* `'random'` (default): Use a random nonce.
2519+
* `'deterministic'`[^openssl32]: Use a deterministic nonce as defined in [RFC 6979][].
25102520
* `padding` {integer} Optional padding value for RSA, one of the following:
25112521

25122522
* `crypto.constants.RSA_PKCS1_PADDING` (default)
@@ -5796,6 +5806,10 @@ additional properties can be passed:
57965806
format of the generated signature. It can be one of the following:
57975807
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
57985808
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
5809+
* `dsaNonceType` {string} For DSA and ECDSA, this option specifies the
5810+
nonce generation method. It can be one of the following:
5811+
* `'random'` (default): Use a random nonce.
5812+
* `'deterministic'`[^openssl32]: Use a deterministic nonce as defined in [RFC 6979][].
57995813
* `padding` {integer} Optional padding value for RSA, one of the following:
58005814

58015815
* `crypto.constants.RSA_PKCS1_PADDING` (default)
@@ -5927,6 +5941,10 @@ additional properties can be passed:
59275941
format of the signature. It can be one of the following:
59285942
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
59295943
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
5944+
* `dsaNonceType` {string} For DSA and ECDSA, this option specifies the
5945+
nonce generation method used during signing. It can be one of the following:
5946+
* `'random'` (default): Use a random nonce.
5947+
* `'deterministic'`[^openssl32]: Use a deterministic nonce as defined in [RFC 6979][].
59305948
* `padding` {integer} Optional padding value for RSA, one of the following:
59315949

59325950
* `crypto.constants.RSA_PKCS1_PADDING` (default)
@@ -6539,6 +6557,7 @@ See the [list of SSL OP Flags][] for details.
65396557
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
65406558
[RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt
65416559
[RFC 5280]: https://www.rfc-editor.org/rfc/rfc5280.txt
6560+
[RFC 6979]: https://www.rfc-editor.org/rfc/rfc6979.txt
65426561
[Web Crypto API documentation]: webcrypto.md
65436562
[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html
65446563
[`Buffer`]: buffer.md

lib/internal/crypto/cfrg.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ async function eddsaSignVerify(key, data, algorithm, signature) {
363363
undefined,
364364
undefined,
365365
undefined,
366+
undefined, // dsaNonceType, not used with EdDSA
366367
algorithm.name === 'Ed448' ? algorithm.context : undefined,
367368
signature));
368369
}

lib/internal/crypto/ec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ async function ecdsaSignVerify(key, data, { name, hash }, signature) {
305305
undefined, // Salt length, not used with ECDSA
306306
undefined, // PSS Padding, not used with ECDSA
307307
kSigEncP1363,
308+
undefined, // dsaNonceType, not used with WebCrypto
308309
undefined,
309310
signature));
310311
}

lib/internal/crypto/ml_dsa.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ async function mlDsaSignVerify(key, data, algorithm, signature) {
320320
undefined,
321321
undefined,
322322
undefined,
323+
undefined, // dsaNonceType, not used with ML-DSA
323324
algorithm.context,
324325
signature));
325326
}

lib/internal/crypto/rsa.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ async function rsaSignVerify(key, data, { saltLength }, signature) {
358358
saltLength,
359359
key[kAlgorithm].name === 'RSA-PSS' ? RSA_PKCS1_PSS_PADDING : undefined,
360360
undefined,
361+
undefined, // dsaNonceType, not used with RSA
361362
undefined,
362363
signature);
363364
});

lib/internal/crypto/sig.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ function getDSASignatureEncoding(options) {
106106
return kSigEncDER;
107107
}
108108

109+
function getDSANonceType(options) {
110+
if (typeof options === 'object') {
111+
const { dsaNonceType } = options;
112+
if (dsaNonceType === undefined || dsaNonceType === 'random')
113+
return undefined;
114+
if (dsaNonceType === 'deterministic')
115+
return true;
116+
throw new ERR_INVALID_ARG_VALUE('options.dsaNonceType', dsaNonceType);
117+
}
118+
119+
return undefined;
120+
}
121+
109122
function getContext(options) {
110123
if (options?.context === undefined) {
111124
return undefined;
@@ -143,8 +156,11 @@ Sign.prototype.sign = function sign(options, encoding) {
143156
// Options specific to (EC)DSA
144157
const dsaSigEnc = getDSASignatureEncoding(options);
145158

159+
// Options specific to (EC)DSA nonce generation
160+
const dsaNonceType = getDSANonceType(options);
161+
146162
const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,
147-
pssSaltLength, dsaSigEnc);
163+
pssSaltLength, dsaSigEnc, dsaNonceType);
148164

149165
if (encoding && encoding !== 'buffer')
150166
return ret.toString(encoding);
@@ -171,6 +187,9 @@ function signOneShot(algorithm, data, key, callback) {
171187
// Options specific to (EC)DSA
172188
const dsaSigEnc = getDSASignatureEncoding(key);
173189

190+
// Options specific to (EC)DSA nonce generation
191+
const dsaNonceType = getDSANonceType(key);
192+
174193
// Options specific to Ed448 and ML-DSA
175194
const context = getContext(key);
176195

@@ -193,6 +212,7 @@ function signOneShot(algorithm, data, key, callback) {
193212
pssSaltLength,
194213
rsaPadding,
195214
dsaSigEnc,
215+
dsaNonceType,
196216
context,
197217
undefined);
198218

@@ -242,10 +262,14 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
242262
// Options specific to (EC)DSA
243263
const dsaSigEnc = getDSASignatureEncoding(options);
244264

265+
// Options specific to (EC)DSA nonce generation
266+
const dsaNonceType = getDSANonceType(options);
267+
245268
signature = getArrayBufferOrView(signature, 'signature', sigEncoding);
246269

247270
return this[kHandle].verify(data, format, type, passphrase, signature,
248-
rsaPadding, pssSaltLength, dsaSigEnc);
271+
rsaPadding, pssSaltLength, dsaSigEnc,
272+
dsaNonceType);
249273
};
250274

251275
function verifyOneShot(algorithm, data, key, signature, callback) {
@@ -272,6 +296,9 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
272296
// Options specific to (EC)DSA
273297
const dsaSigEnc = getDSASignatureEncoding(key);
274298

299+
// Options specific to (EC)DSA nonce generation
300+
const dsaNonceType = getDSANonceType(key);
301+
275302
// Options specific to Ed448 and ML-DSA
276303
const context = getContext(key);
277304

@@ -302,6 +329,7 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
302329
pssSaltLength,
303330
rsaPadding,
304331
dsaSigEnc,
332+
dsaNonceType,
305333
context,
306334
signature);
307335

0 commit comments

Comments
 (0)