Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: introduce crypto/promises #37218

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 126 additions & 2 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2551,8 +2551,8 @@ added:
* Returns: {Buffer}

Computes the Diffie-Hellman secret based on a `privateKey` and a `publicKey`.
Both keys must have the same `asymmetricKeyType`, which must be one of `'dh'`
(for Diffie-Hellman), `'ec'` (for ECDH), `'x448'`, or `'x25519'` (for ECDH-ES).
Both keys must have the same `asymmetricKeyType`, which must be one of `'dh'`,
`'ec'`, `'x448'`, or `'x25519'`.

### `crypto.generateKey(type, options, callback)`
<!-- YAML
Expand Down Expand Up @@ -3895,6 +3895,130 @@ Type: {Crypto} An implementation of the Web Crypto API standard.

See the [Web Crypto API documentation][] for details.

## `crypto` Promises API
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

The `crypto.promises` API provides an alternative set of asynchronous crypto
methods that return `Promise` objects and execute operations in libuv's
threadpool.
The API is accessible via `require('crypto').promises` or `require('crypto/promises')`.

### `cryptoPromises.diffieHellman(options)`
jasnell marked this conversation as resolved.
Show resolved Hide resolved
<!-- YAML
added: REPLACEME
-->

* `options`: {Object}
* `privateKey`: {KeyObject|CryptoKey}
* `publicKey`: {KeyObject|CryptoKey}
* Returns: {Promise} containing {Buffer}

Computes the Diffie-Hellman secret based on a `privateKey` and a `publicKey`.
Both keys must have the same `asymmetricKeyType`, which must be one of `'dh'`,
`'ec'`, `'x448'`, or `'x25519'`.

### `cryptoPromises.digest(algorithm, data[, options])`
<!-- YAML
added: REPLACEME
-->

* `algorithm` {string}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* `options` {Object}
* `outputLength` {number} Used to specify the desired output length in bytes
for XOF hash functions such as `'shake256'`.
* Returns: {Promise} containing {Buffer}

Calculates the digest for the `data` using the given `algorithm`.

The `algorithm` is dependent on the available algorithms supported by the
version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
On recent releases of OpenSSL, `openssl list -digest-algorithms`
(`openssl list-message-digest-algorithms` for older versions of OpenSSL) will
display the available digest algorithms.

### `cryptoPromises.hmac(algorithm, data, key)`
<!-- YAML
added: REPLACEME
-->

* `algorithm` {string}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* `key` {KeyObject|CryptoKey}
* Returns: {Promise} containing {Buffer}

Calculates the HMAC digest for the `data` using the given `algorithm`.

The `algorithm` is dependent on the available algorithms supported by the
version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
On recent releases of OpenSSL, `openssl list -digest-algorithms`
(`openssl list-message-digest-algorithms` for older versions of OpenSSL) will
display the available digest algorithms.

### `cryptoPromises.sign(algorithm, data, key[, options])`
<!-- YAML
added: REPLACEME
-->

* `algorithm` {string|null|undefined}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* `key` {KeyObject|CryptoKey}
* `options` {Object}
* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
format of the generated signature. It can be one of the following:
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
* `padding` {integer} Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
* `saltLength` {integer} Salt length for when padding is
`RSA_PKCS1_PSS_PADDING`. The special value
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
maximum permissible value.
* Returns: {Promise} containing {Buffer}

Calculates the signature for `data` using the given private key and
algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is
dependent upon the key type (especially Ed25519 and Ed448).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest moving the details about the defaults for algorithm into the bullet point in the argument list above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tniessen is passing null|undefined used for anything but Ed25519 and Ed448?

Copy link
Member

@tniessen tniessen Feb 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't design this API. It would be used for SM2, too. But there's no decision yet whether SM2 will be added, see #37066.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, i thought you did. @mscdex can you pitch in?


### `cryptoPromises.verify(algorithm, data, key, signature[, options])`
<!-- YAML
added: REPLACEME
-->

* `algorithm` {string|null|undefined}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* `key` {KeyObject|CryptoKey}
* `signature` {ArrayBuffer|TypedArray|DataView|Buffer}
* `options` {Object}
* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
format of the generated signature. It can be one of the following:
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
* `padding` {integer} Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
* `saltLength` {integer} Salt length for when padding is
`RSA_PKCS1_PSS_PADDING`. The special value
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
maximum permissible value.
* Returns: {Promise} containing {boolean}

Verifies the given signature for `data` using the given key and algorithm. If
`algorithm` is `null` or `undefined`, then the algorithm is dependent upon the
key type (especially Ed25519 and Ed448).
panva marked this conversation as resolved.
Show resolved Hide resolved

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`.

## Notes

### Legacy streams API (prior to Node.js 0.10)
Expand Down
12 changes: 12 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ function createVerify(algorithm, options) {
return new Verify(algorithm, options);
}

// Lazy loaded
let promises = null;

module.exports = {
// Methods
checkPrime,
Expand Down Expand Up @@ -327,5 +330,14 @@ ObjectDefineProperties(module.exports, {
value: pendingDeprecation ?
deprecate(randomBytes, 'crypto.rng is deprecated.', 'DEP0115') :
randomBytes
},
promises: {
configurable: true,
enumerable: true,
get() {
if (promises === null)
promises = require('internal/crypto/promises').exports;
return promises;
}
}
});
3 changes: 3 additions & 0 deletions lib/crypto/promises.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('internal/crypto/promises').exports;
42 changes: 42 additions & 0 deletions lib/internal/crypto/diffiehellman.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,46 @@ function deriveBitsDH(publicKey, privateKey, callback) {
job.run();
}

async function asyncDiffieHellman(publicKey, privateKey) {
const { asymmetricKeyType } = privateKey;

if (asymmetricKeyType === 'dh') {
return new Promise((resolve, reject) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use util.promisify to avoid this new Promise dance

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this to be more efficient than the promisify implementation. Not a hill I want to die on tho.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's exactly as efficient since in recent version this code is what promisify generates - but not a hill to die on for me either was just trying to comment :]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/nodejs/node/blob/v15.8.0/lib/internal/util.js#L292-L326 sure it has the same outcome, but i think it's handling way more cases which we don't need.

deriveBitsDH(
publicKey[kHandle],
privateKey[kHandle],
(err, bits) => {
if (err) reject(err);
else resolve(bits);
});
});
}

if (asymmetricKeyType === 'x25519' || asymmetricKeyType === 'x448') {
return new Promise((resolve, reject) => {
deriveBitsECDH(
`NODE-${asymmetricKeyType.toUpperCase()}`,
publicKey[kHandle],
privateKey[kHandle],
(err, bits) => {
if (err) reject(err);
else resolve(bits);
});
});
}

return new Promise((resolve, reject) => {
deriveBitsECDH(
privateKey.asymmetricKeyDetails.namedCurve,
publicKey[kHandle],
privateKey[kHandle],
(err, bits) => {
if (err) reject(err);
else resolve(bits);
});
});
}

function verifyAcceptableDhKeyUse(name, type, usages) {
let checkSet;
switch (type) {
Expand Down Expand Up @@ -601,9 +641,11 @@ module.exports = {
DiffieHellman,
DiffieHellmanGroup,
ECDH,
asyncDiffieHellman,
diffieHellman,
deriveBitsECDH,
deriveBitsDH,
dhEnabledKeyTypes,
dhGenerateKey,
asyncDeriveBitsECDH,
asyncDeriveBitsDH,
Expand Down
Loading