Skip to content

Commit 7d0e50d

Browse files
committed
crypto: add crypto.sign() and crypto.verify()
These methods are added primarily to allow signing and verifying using Ed25519 and Ed448 keys, which do not support streaming of input data. However, any key type can be used with these new APIs, to allow better performance when only signing/verifying a single chunk. Fixes: #26320 PR-URL: #26611 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rod Vagg <[email protected]> Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent 36e5fd2 commit 7d0e50d

File tree

5 files changed

+518
-89
lines changed

5 files changed

+518
-89
lines changed

doc/api/crypto.md

+64
Original file line numberDiff line numberDiff line change
@@ -2659,6 +2659,35 @@ added: v10.0.0
26592659
Enables the FIPS compliant crypto provider in a FIPS-enabled Node.js build.
26602660
Throws an error if FIPS mode is not available.
26612661

2662+
### crypto.sign(algorithm, data, key)
2663+
<!-- YAML
2664+
added: REPLACEME
2665+
-->
2666+
* `algorithm` {string | null | undefined}
2667+
* `data` {Buffer | TypedArray | DataView}
2668+
* `key` {Object | string | Buffer | KeyObject}
2669+
* Returns: {Buffer}
2670+
2671+
Calculates and returns the signature for `data` using the given private key and
2672+
algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is
2673+
dependent upon the key type (especially Ed25519 and Ed448).
2674+
2675+
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
2676+
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
2677+
additional properties can be passed:
2678+
2679+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
2680+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
2681+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
2682+
2683+
Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
2684+
used to sign the message as specified in section 3.1 of [RFC 4055][].
2685+
* `saltLength`: {integer} - salt length for when padding is
2686+
`RSA_PKCS1_PSS_PADDING`. The special value
2687+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
2688+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
2689+
maximum permissible value.
2690+
26622691
### crypto.timingSafeEqual(a, b)
26632692
<!-- YAML
26642693
added: v6.6.0
@@ -2680,6 +2709,41 @@ Use of `crypto.timingSafeEqual` does not guarantee that the *surrounding* code
26802709
is timing-safe. Care should be taken to ensure that the surrounding code does
26812710
not introduce timing vulnerabilities.
26822711

2712+
### crypto.verify(algorithm, data, key, signature)
2713+
<!-- YAML
2714+
added: REPLACEME
2715+
-->
2716+
* `algorithm` {string | null | undefined}
2717+
* `data` {Buffer | TypedArray | DataView}
2718+
* `key` {Object | string | Buffer | KeyObject}
2719+
* `signature` {Buffer | TypedArray | DataView}
2720+
* Returns: {boolean}
2721+
2722+
Verifies the given signature for `data` using the given key and algorithm. If
2723+
`algorithm` is `null` or `undefined`, then the algorithm is dependent upon the
2724+
key type (especially Ed25519 and Ed448).
2725+
2726+
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
2727+
passed to [`crypto.createPublicKey()`][]. If it is an object, the following
2728+
additional properties can be passed:
2729+
2730+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
2731+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
2732+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
2733+
2734+
Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
2735+
used to sign the message as specified in section 3.1 of [RFC 4055][].
2736+
* `saltLength`: {integer} - salt length for when padding is
2737+
`RSA_PKCS1_PSS_PADDING`. The special value
2738+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
2739+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
2740+
maximum permissible value.
2741+
2742+
The `signature` argument is the previously calculated signature for the `data`.
2743+
2744+
Because public keys can be derived from private keys, a private key or a public
2745+
key may be passed for `key`.
2746+
26832747
## Notes
26842748

26852749
### Legacy Streams API (pre Node.js v0.10)

lib/crypto.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ const {
8080
} = require('internal/crypto/cipher');
8181
const {
8282
Sign,
83-
Verify
83+
signOneShot,
84+
Verify,
85+
verifyOneShot
8486
} = require('internal/crypto/sig');
8587
const {
8688
Hash,
@@ -174,12 +176,14 @@ module.exports = exports = {
174176
randomFillSync,
175177
scrypt,
176178
scryptSync,
179+
sign: signOneShot,
177180
setEngine,
178181
timingSafeEqual,
179182
getFips: !fipsMode ? getFipsDisabled :
180183
fipsForced ? getFipsForced : getFipsCrypto,
181184
setFips: !fipsMode ? setFipsDisabled :
182185
fipsForced ? setFipsForced : setFipsCrypto,
186+
verify: verifyOneShot,
183187

184188
// Classes
185189
Certificate,

lib/internal/crypto/sig.js

+75-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
const {
44
ERR_CRYPTO_SIGN_KEY_REQUIRED,
5+
ERR_INVALID_ARG_TYPE,
56
ERR_INVALID_OPT_VALUE
67
} = require('internal/errors').codes;
78
const { validateString } = require('internal/validators');
8-
const { Sign: _Sign, Verify: _Verify } = internalBinding('crypto');
9+
const {
10+
Sign: _Sign,
11+
Verify: _Verify,
12+
signOneShot: _signOneShot,
13+
verifyOneShot: _verifyOneShot
14+
} = internalBinding('crypto');
915
const {
1016
RSA_PSS_SALTLEN_AUTO,
1117
RSA_PKCS1_PADDING
@@ -22,6 +28,7 @@ const {
2228
preparePublicOrPrivateKey
2329
} = require('internal/crypto/keys');
2430
const { Writable } = require('stream');
31+
const { isArrayBufferView } = require('internal/util/types');
2532

2633
function Sign(algorithm, options) {
2734
if (!(this instanceof Sign))
@@ -91,6 +98,35 @@ Sign.prototype.sign = function sign(options, encoding) {
9198
return ret;
9299
};
93100

101+
function signOneShot(algorithm, data, key) {
102+
if (algorithm != null)
103+
validateString(algorithm, 'algorithm');
104+
105+
if (!isArrayBufferView(data)) {
106+
throw new ERR_INVALID_ARG_TYPE(
107+
'data',
108+
['Buffer', 'TypedArray', 'DataView'],
109+
data
110+
);
111+
}
112+
113+
if (!key)
114+
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
115+
116+
const {
117+
data: keyData,
118+
format: keyFormat,
119+
type: keyType,
120+
passphrase: keyPassphrase
121+
} = preparePrivateKey(key);
122+
123+
// Options specific to RSA
124+
const rsaPadding = getPadding(key);
125+
const pssSaltLength = getSaltLength(key);
126+
127+
return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data,
128+
algorithm, rsaPadding, pssSaltLength);
129+
}
94130

95131
function Verify(algorithm, options) {
96132
if (!(this instanceof Verify))
@@ -132,7 +168,44 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
132168

133169
legacyNativeHandle(Verify);
134170

171+
function verifyOneShot(algorithm, data, key, signature) {
172+
if (algorithm != null)
173+
validateString(algorithm, 'algorithm');
174+
175+
if (!isArrayBufferView(data)) {
176+
throw new ERR_INVALID_ARG_TYPE(
177+
'data',
178+
['Buffer', 'TypedArray', 'DataView'],
179+
data
180+
);
181+
}
182+
183+
const {
184+
data: keyData,
185+
format: keyFormat,
186+
type: keyType,
187+
passphrase: keyPassphrase
188+
} = preparePublicOrPrivateKey(key);
189+
190+
// Options specific to RSA
191+
const rsaPadding = getPadding(key);
192+
const pssSaltLength = getSaltLength(key);
193+
194+
if (!isArrayBufferView(signature)) {
195+
throw new ERR_INVALID_ARG_TYPE(
196+
'signature',
197+
['Buffer', 'TypedArray', 'DataView'],
198+
signature
199+
);
200+
}
201+
202+
return _verifyOneShot(keyData, keyFormat, keyType, keyPassphrase, signature,
203+
data, algorithm, rsaPadding, pssSaltLength);
204+
}
205+
135206
module.exports = {
136207
Sign,
137-
Verify
208+
signOneShot,
209+
Verify,
210+
verifyOneShot
138211
};

0 commit comments

Comments
 (0)