Skip to content

Commit ba4e566

Browse files
committed
crypto: add crypto.signDigest() and crypto.verifyDigest() methods
Resolves: #60263 Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent 7a14757 commit ba4e566

File tree

11 files changed

+1005
-25
lines changed

11 files changed

+1005
-25
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3825,6 +3825,23 @@ bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) {
38253825
1;
38263826
}
38273827

3828+
bool EVPKeyCtxPointer::setSignatureMd(const Digest& md) {
3829+
if (!ctx_ || !md) return false;
3830+
return EVP_PKEY_CTX_set_signature_md(ctx_.get(), md.get()) == 1;
3831+
}
3832+
3833+
#if OPENSSL_VERSION_MAJOR >= 3
3834+
int EVPKeyCtxPointer::initForSignEx(const OSSL_PARAM params[]) {
3835+
if (!ctx_) return 0;
3836+
return EVP_PKEY_sign_init_ex(ctx_.get(), params);
3837+
}
3838+
3839+
int EVPKeyCtxPointer::initForVerifyEx(const OSSL_PARAM params[]) {
3840+
if (!ctx_) return 0;
3841+
return EVP_PKEY_verify_init_ex(ctx_.get(), params);
3842+
}
3843+
#endif
3844+
38283845
bool EVPKeyCtxPointer::initForEncrypt() {
38293846
if (!ctx_) return false;
38303847
return EVP_PKEY_encrypt_init(ctx_.get()) == 1;

deps/ncrypto/ncrypto.h

Lines changed: 5 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,10 @@ 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
824829

825830
static EVPKeyCtxPointer New(const EVPKeyPointer& key);
826831
static EVPKeyCtxPointer NewFromID(int id);

doc/api/crypto.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6133,6 +6133,68 @@ additional properties can be passed:
61336133

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

6136+
### `crypto.signDigest(algorithm, digest, key[, callback])`
6137+
6138+
<!-- YAML
6139+
added: REPLACEME
6140+
-->
6141+
6142+
<!--lint disable maximum-line-length remark-lint-->
6143+
6144+
* `algorithm` {string | null | undefined}
6145+
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
6146+
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
6147+
* `callback` {Function}
6148+
* `err` {Error}
6149+
* `signature` {Buffer}
6150+
* Returns: {Buffer} if the `callback` function is not provided.
6151+
6152+
<!--lint enable maximum-line-length remark-lint-->
6153+
6154+
Calculates and returns the signature for `digest` using the given private key
6155+
and algorithm. Unlike [`crypto.sign()`][], this function does not hash the data
6156+
internally — `digest` is expected to be a pre-computed hash digest.
6157+
6158+
The interpretation of `algorithm` and `digest` depends on the key type:
6159+
6160+
* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
6161+
`digest`. The resulting signatures are compatible with [`crypto.verify()`][]
6162+
and signatures produced by [`crypto.sign()`][] can be verified with
6163+
[`crypto.verifyDigest()`][].
6164+
6165+
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
6166+
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
6167+
respectively. `digest` must be the output of the appropriate prehash
6168+
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
6169+
Ed448ph). The resulting signatures can only be verified with
6170+
[`crypto.verifyDigest()`][], not with [`crypto.verify()`][].
6171+
6172+
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
6173+
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
6174+
additional properties can be passed:
6175+
6176+
* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
6177+
format of the generated signature. It can be one of the following:
6178+
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
6179+
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
6180+
* `padding` {integer} Optional padding value for RSA, one of the following:
6181+
6182+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
6183+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
6184+
6185+
`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
6186+
used to create the digest as specified in section 3.1 of [RFC 4055][].
6187+
* `saltLength` {integer} Salt length for when padding is
6188+
`RSA_PKCS1_PSS_PADDING`. The special value
6189+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
6190+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
6191+
maximum permissible value.
6192+
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519ph and Ed448ph,
6193+
this option specifies the optional context to differentiate signatures
6194+
generated for different purposes with the same key.
6195+
6196+
If the `callback` function is provided this function uses libuv's threadpool.
6197+
61366198
### `crypto.subtle`
61376199

61386200
<!-- YAML
@@ -6273,6 +6335,75 @@ key may be passed for `key`.
62736335

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

6338+
### `crypto.verifyDigest(algorithm, digest, key, signature[, callback])`
6339+
6340+
<!-- YAML
6341+
added: REPLACEME
6342+
-->
6343+
6344+
<!--lint disable maximum-line-length remark-lint-->
6345+
6346+
* `algorithm` {string|null|undefined}
6347+
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
6348+
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
6349+
* `signature` {ArrayBuffer|Buffer|TypedArray|DataView}
6350+
* `callback` {Function}
6351+
* `err` {Error}
6352+
* `result` {boolean}
6353+
* Returns: {boolean} `true` or `false` depending on the validity of the
6354+
signature for the digest and public key if the `callback` function is not
6355+
provided.
6356+
6357+
<!--lint enable maximum-line-length remark-lint-->
6358+
6359+
Verifies the given signature for `digest` using the given key and algorithm.
6360+
Unlike [`crypto.verify()`][], this function does not hash the data
6361+
internally — `digest` is expected to be a pre-computed hash digest.
6362+
6363+
The interpretation of `algorithm` and `digest` depends on the key type:
6364+
6365+
* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
6366+
`digest`. Signatures produced by [`crypto.sign()`][] can be verified with
6367+
this function, and signatures produced by [`crypto.signDigest()`][] can be
6368+
verified with [`crypto.verify()`][].
6369+
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
6370+
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
6371+
respectively. `digest` must be the output of the appropriate prehash
6372+
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
6373+
Ed448ph). Only signatures produced by [`crypto.signDigest()`][] can be
6374+
verified with this function, not those from [`crypto.sign()`][].
6375+
6376+
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
6377+
passed to [`crypto.createPublicKey()`][]. If it is an object, the following
6378+
additional properties can be passed:
6379+
6380+
* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
6381+
format of the signature. It can be one of the following:
6382+
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
6383+
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
6384+
* `padding` {integer} Optional padding value for RSA, one of the following:
6385+
6386+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
6387+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
6388+
6389+
`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
6390+
used to create the digest as specified in section 3.1 of [RFC 4055][].
6391+
* `saltLength` {integer} Salt length for when padding is
6392+
`RSA_PKCS1_PSS_PADDING`. The special value
6393+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
6394+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
6395+
maximum permissible value.
6396+
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519ph and Ed448ph,
6397+
this option specifies the optional context to differentiate signatures
6398+
generated for different purposes with the same key.
6399+
6400+
The `signature` argument is the previously calculated signature for the `digest`.
6401+
6402+
Because public keys can be derived from private keys, a private key or a public
6403+
key may be passed for `key`.
6404+
6405+
If the `callback` function is provided this function uses libuv's threadpool.
6406+
62766407
### `crypto.webcrypto`
62776408

62786409
<!-- YAML
@@ -6899,7 +7030,9 @@ See the [list of SSL OP Flags][] for details.
68997030
[`crypto.randomBytes()`]: #cryptorandombytessize-callback
69007031
[`crypto.randomFill()`]: #cryptorandomfillbuffer-offset-size-callback
69017032
[`crypto.sign()`]: #cryptosignalgorithm-data-key-callback
7033+
[`crypto.signDigest()`]: #cryptosigndigestalgorithm-digest-key-callback
69027034
[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback
7035+
[`crypto.verifyDigest()`]: #cryptoverifydigestalgorithm-digest-key-signature-callback
69037036
[`crypto.webcrypto.getRandomValues()`]: webcrypto.md#cryptogetrandomvaluestypedarray
69047037
[`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto
69057038
[`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,

lib/internal/crypto/sig.js

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const { Writable } = require('stream');
4646
const { Buffer } = require('buffer');
4747

4848
const {
49+
isAnyArrayBuffer,
4950
isArrayBufferView,
5051
} = require('internal/util/types');
5152

@@ -152,14 +153,29 @@ Sign.prototype.sign = function sign(options, encoding) {
152153
return ret;
153154
};
154155

155-
function signOneShot(algorithm, data, key, callback) {
156+
function validateDigestOrData(data, prehashed) {
157+
if (prehashed) {
158+
if (!isArrayBufferView(data) && !isAnyArrayBuffer(data)) {
159+
throw new ERR_INVALID_ARG_TYPE(
160+
'digest',
161+
['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
162+
data,
163+
);
164+
}
165+
return data;
166+
}
167+
168+
return getArrayBufferOrView(data, 'data');
169+
}
170+
171+
function signOneShotImpl(algorithm, data, key, callback, prehashed) {
156172
if (algorithm != null)
157173
validateString(algorithm, 'algorithm');
158174

159175
if (callback !== undefined)
160176
validateFunction(callback, 'callback');
161177

162-
data = getArrayBufferOrView(data, 'data');
178+
data = validateDigestOrData(data, prehashed);
163179

164180
if (!key)
165181
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
@@ -194,7 +210,8 @@ function signOneShot(algorithm, data, key, callback) {
194210
rsaPadding,
195211
dsaSigEnc,
196212
context,
197-
undefined);
213+
undefined,
214+
prehashed);
198215

199216
if (!callback) {
200217
const { 0: err, 1: signature } = job.run();
@@ -248,22 +265,14 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
248265
rsaPadding, pssSaltLength, dsaSigEnc);
249266
};
250267

251-
function verifyOneShot(algorithm, data, key, signature, callback) {
268+
function verifyOneShotImpl(algorithm, data, key, signature, callback, prehashed) {
252269
if (algorithm != null)
253270
validateString(algorithm, 'algorithm');
254271

255272
if (callback !== undefined)
256273
validateFunction(callback, 'callback');
257274

258-
data = getArrayBufferOrView(data, 'data');
259-
260-
if (!isArrayBufferView(data)) {
261-
throw new ERR_INVALID_ARG_TYPE(
262-
'data',
263-
['Buffer', 'TypedArray', 'DataView'],
264-
data,
265-
);
266-
}
275+
data = validateDigestOrData(data, prehashed);
267276

268277
// Options specific to RSA
269278
const rsaPadding = getPadding(key);
@@ -303,7 +312,8 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
303312
rsaPadding,
304313
dsaSigEnc,
305314
context,
306-
signature);
315+
signature,
316+
prehashed);
307317

308318
if (!callback) {
309319
const { 0: err, 1: result } = job.run();
@@ -320,9 +330,27 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
320330
job.run();
321331
}
322332

333+
function signOneShot(algorithm, data, key, callback) {
334+
return signOneShotImpl(algorithm, data, key, callback, false /* not prehashed */);
335+
}
336+
337+
function verifyOneShot(algorithm, data, key, signature, callback) {
338+
return verifyOneShotImpl(algorithm, data, key, signature, callback, false /* not prehashed */);
339+
}
340+
341+
function signDigestOneShot(algorithm, digest, key, callback) {
342+
return signOneShotImpl(algorithm, digest, key, callback, true /* prehashed */);
343+
}
344+
345+
function verifyDigestOneShot(algorithm, digest, key, signature, callback) {
346+
return verifyOneShotImpl(algorithm, digest, key, signature, callback, true /* prehashed */);
347+
}
348+
323349
module.exports = {
324350
Sign,
325351
signOneShot,
352+
signDigestOneShot,
326353
Verify,
327354
verifyOneShot,
355+
verifyDigestOneShot,
328356
};

0 commit comments

Comments
 (0)