Skip to content

Commit

Permalink
crypto: add optional callback to crypto.sign and crypto.verify
Browse files Browse the repository at this point in the history
PR-URL: #37500
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
  • Loading branch information
panva authored and danielleadams committed Mar 16, 2021
1 parent a86334f commit 922f2f0
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 74 deletions.
26 changes: 22 additions & 4 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -3822,10 +3822,13 @@ added: v10.0.0
Enables the FIPS compliant crypto provider in a FIPS-enabled Node.js build.
Throws an error if FIPS mode is not available.

### `crypto.sign(algorithm, data, key)`
### `crypto.sign(algorithm, data, key[, callback])`
<!-- YAML
added: v12.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37500
description: Optional callback argument added.
- version:
- v13.2.0
- v12.16.0
Expand All @@ -3837,7 +3840,10 @@ changes:
* `algorithm` {string | null | undefined}
* `data` {ArrayBuffer|Buffer|TypedArray|DataView}
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
* Returns: {Buffer}
* `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 `data` using the given private key and
Expand All @@ -3864,6 +3870,8 @@ additional properties can be passed:
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
maximum permissible value.

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

### `crypto.timingSafeEqual(a, b)`
<!-- YAML
added: v6.6.0
Expand Down Expand Up @@ -3894,10 +3902,13 @@ Use of `crypto.timingSafeEqual` does not guarantee that the *surrounding* code
is timing-safe. Care should be taken to ensure that the surrounding code does
not introduce timing vulnerabilities.

### `crypto.verify(algorithm, data, key, signature)`
### `crypto.verify(algorithm, data, key, signature[, callback])`
<!-- YAML
added: v12.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37500
description: Optional callback argument added.
- version: v15.0.0
pr-url: https://github.com/nodejs/node/pull/35093
description: The data, key, and signature arguments can also be ArrayBuffer.
Expand All @@ -3913,7 +3924,12 @@ changes:
* `data` {ArrayBuffer| Buffer|TypedArray|DataView}
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|CryptoKey}
* `signature` {ArrayBuffer|Buffer|TypedArray|DataView}
* Returns: {boolean}
* `callback` {Function}
* `err` {Error}
* `result` {boolean}
* Returns: {boolean} `true` or `false` depending on the validity of the
signature for the data and public key if the `callback` function is not
provided.
<!--lint enable maximum-line-length remark-lint-->

Verifies the given signature for `data` using the given key and algorithm. If
Expand Down Expand Up @@ -3945,6 +3961,8 @@ The `signature` argument is the previously calculated signature for the `data`.
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
added: v15.0.0
Expand Down
4 changes: 3 additions & 1 deletion lib/internal/crypto/dsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kSigEncDER,
kKeyTypePrivate,
kSignJobModeSign,
kSignJobModeVerify,
Expand Down Expand Up @@ -254,7 +255,8 @@ function dsaSignVerify(key, data, algorithm, signature) {
normalizeHashName(key.algorithm.hash.name),
undefined, // Salt-length is not used in DSA
undefined, // Padding is not used in DSA
signature));
signature,
kSigEncDER));
}

module.exports = {
Expand Down
4 changes: 3 additions & 1 deletion lib/internal/crypto/ec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
kSigEncP1363,
} = internalBinding('crypto');

const {
Expand Down Expand Up @@ -470,7 +471,8 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) {
hashname,
undefined, // Salt length, not used with ECDSA
undefined, // PSS Padding, not used with ECDSA
signature));
signature,
kSigEncP1363));
}

module.exports = {
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/crypto/rsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const {
} = require('internal/errors');

const {
validateInt32,
validateUint32,
} = require('internal/validators');

Expand Down Expand Up @@ -342,7 +343,7 @@ function rsaSignVerify(key, data, { saltLength }, signature) {
// TODO(@jasnell): Validate maximum size of saltLength
// based on the key size:
// Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2
validateUint32(saltLength, 'algorithm.saltLength');
validateInt32(saltLength, 'algorithm.saltLength', -2);
}

const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
Expand Down
117 changes: 96 additions & 21 deletions lib/internal/crypto/sig.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
FunctionPrototypeCall,
ObjectSetPrototypeOf,
ReflectApply,
} = primordials;
Expand All @@ -14,17 +15,22 @@ const {
} = require('internal/errors');

const {
validateCallback,
validateEncoding,
validateString,
} = require('internal/validators');

const {
Sign: _Sign,
SignJob,
Verify: _Verify,
signOneShot: _signOneShot,
verifyOneShot: _verifyOneShot,
kCryptoJobAsync,
kSigEncDER,
kSigEncP1363,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');

const {
Expand All @@ -34,12 +40,18 @@ const {
} = require('internal/crypto/util');

const {
preparePublicOrPrivateKey,
createPrivateKey,
createPublicKey,
isCryptoKey,
isKeyObject,
preparePrivateKey,
preparePublicOrPrivateKey,
} = require('internal/crypto/keys');

const { Writable } = require('stream');

const { Buffer } = require('buffer');

const {
isArrayBufferView,
} = require('internal/util/types');
Expand Down Expand Up @@ -131,31 +143,62 @@ Sign.prototype.sign = function sign(options, encoding) {
return ret;
};

function signOneShot(algorithm, data, key) {
function signOneShot(algorithm, data, key, callback) {
if (algorithm != null)
validateString(algorithm, 'algorithm');

if (callback !== undefined)
validateCallback(callback);

data = getArrayBufferOrView(data, 'data');

if (!key)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();

const {
data: keyData,
format: keyFormat,
type: keyType,
passphrase: keyPassphrase
} = preparePrivateKey(key);

// Options specific to RSA
const rsaPadding = getPadding(key);
const pssSaltLength = getSaltLength(key);

// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(key);

return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data,
algorithm, rsaPadding, pssSaltLength, dsaSigEnc);
if (!callback) {
const {
data: keyData,
format: keyFormat,
type: keyType,
passphrase: keyPassphrase
} = preparePrivateKey(key);

return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data,
algorithm, rsaPadding, pssSaltLength, dsaSigEnc);
}

let keyData;
if (isKeyObject(key) || isCryptoKey(key)) {
({ data: keyData } = preparePrivateKey(key));
} else if (key != null && (isKeyObject(key.key) || isCryptoKey(key.key))) {
({ data: keyData } = preparePrivateKey(key.key));
} else {
keyData = createPrivateKey(key)[kHandle];
}

const job = new SignJob(
kCryptoJobAsync,
kSignJobModeSign,
keyData,
data,
algorithm,
pssSaltLength,
rsaPadding,
undefined,
dsaSigEnc);

job.ondone = (error, signature) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, Buffer.from(signature));
};
job.run();
}

function Verify(algorithm, options) {
Expand Down Expand Up @@ -197,10 +240,13 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
rsaPadding, pssSaltLength, dsaSigEnc);
};

function verifyOneShot(algorithm, data, key, signature) {
function verifyOneShot(algorithm, data, key, signature, callback) {
if (algorithm != null)
validateString(algorithm, 'algorithm');

if (callback !== undefined)
validateCallback(callback);

data = getArrayBufferOrView(data, 'data');

if (!isArrayBufferView(data)) {
Expand All @@ -211,13 +257,6 @@ function verifyOneShot(algorithm, data, key, signature) {
);
}

const {
data: keyData,
format: keyFormat,
type: keyType,
passphrase: keyPassphrase
} = preparePublicOrPrivateKey(key);

// Options specific to RSA
const rsaPadding = getPadding(key);
const pssSaltLength = getSaltLength(key);
Expand All @@ -233,8 +272,44 @@ function verifyOneShot(algorithm, data, key, signature) {
);
}

return _verifyOneShot(keyData, keyFormat, keyType, keyPassphrase, signature,
data, algorithm, rsaPadding, pssSaltLength, dsaSigEnc);
if (!callback) {
const {
data: keyData,
format: keyFormat,
type: keyType,
passphrase: keyPassphrase
} = preparePublicOrPrivateKey(key);

return _verifyOneShot(keyData, keyFormat, keyType, keyPassphrase,
signature, data, algorithm, rsaPadding,
pssSaltLength, dsaSigEnc);
}

let keyData;
if (isKeyObject(key) || isCryptoKey(key)) {
({ data: keyData } = preparePublicOrPrivateKey(key));
} else if (key != null && (isKeyObject(key.key) || isCryptoKey(key.key))) {
({ data: keyData } = preparePublicOrPrivateKey(key.key));
} else {
keyData = createPublicKey(key)[kHandle];
}

const job = new SignJob(
kCryptoJobAsync,
kSignJobModeVerify,
keyData,
data,
algorithm,
pssSaltLength,
rsaPadding,
signature,
dsaSigEnc);

job.ondone = (error, result) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, result);
};
job.run();
}

module.exports = {
Expand Down
Loading

0 comments on commit 922f2f0

Please sign in to comment.