Skip to content

Commit

Permalink
crypto: add crypto.sign() and crypto.verify()
Browse files Browse the repository at this point in the history
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 <jasnell@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
  • Loading branch information
mscdex committed Mar 29, 2019
1 parent 36e5fd2 commit 7d0e50d
Show file tree
Hide file tree
Showing 5 changed files with 518 additions and 89 deletions.
64 changes: 64 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2659,6 +2659,35 @@ 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)
<!-- YAML
added: REPLACEME
-->
* `algorithm` {string | null | undefined}
* `data` {Buffer | TypedArray | DataView}
* `key` {Object | string | Buffer | KeyObject}
* Returns: {Buffer}

Calculates and returns 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).

If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
additional properties can be passed:

* `padding`: {integer} - Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`

Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
used to sign the message as specified in section 3.1 of [RFC 4055][].
* `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.

### crypto.timingSafeEqual(a, b)
<!-- YAML
added: v6.6.0
Expand All @@ -2680,6 +2709,41 @@ 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)
<!-- YAML
added: REPLACEME
-->
* `algorithm` {string | null | undefined}
* `data` {Buffer | TypedArray | DataView}
* `key` {Object | string | Buffer | KeyObject}
* `signature` {Buffer | TypedArray | DataView}
* Returns: {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).

If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
passed to [`crypto.createPublicKey()`][]. If it is an object, the following
additional properties can be passed:

* `padding`: {integer} - Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`

Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
used to sign the message as specified in section 3.1 of [RFC 4055][].
* `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.

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 (pre Node.js v0.10)
Expand Down
6 changes: 5 additions & 1 deletion lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ const {
} = require('internal/crypto/cipher');
const {
Sign,
Verify
signOneShot,
Verify,
verifyOneShot
} = require('internal/crypto/sig');
const {
Hash,
Expand Down Expand Up @@ -174,12 +176,14 @@ module.exports = exports = {
randomFillSync,
scrypt,
scryptSync,
sign: signOneShot,
setEngine,
timingSafeEqual,
getFips: !fipsMode ? getFipsDisabled :
fipsForced ? getFipsForced : getFipsCrypto,
setFips: !fipsMode ? setFipsDisabled :
fipsForced ? setFipsForced : setFipsCrypto,
verify: verifyOneShot,

// Classes
Certificate,
Expand Down
77 changes: 75 additions & 2 deletions lib/internal/crypto/sig.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

const {
ERR_CRYPTO_SIGN_KEY_REQUIRED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_OPT_VALUE
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
const { Sign: _Sign, Verify: _Verify } = internalBinding('crypto');
const {
Sign: _Sign,
Verify: _Verify,
signOneShot: _signOneShot,
verifyOneShot: _verifyOneShot
} = internalBinding('crypto');
const {
RSA_PSS_SALTLEN_AUTO,
RSA_PKCS1_PADDING
Expand All @@ -22,6 +28,7 @@ const {
preparePublicOrPrivateKey
} = require('internal/crypto/keys');
const { Writable } = require('stream');
const { isArrayBufferView } = require('internal/util/types');

function Sign(algorithm, options) {
if (!(this instanceof Sign))
Expand Down Expand Up @@ -91,6 +98,35 @@ Sign.prototype.sign = function sign(options, encoding) {
return ret;
};

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

if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data',
['Buffer', 'TypedArray', 'DataView'],
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);

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

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

legacyNativeHandle(Verify);

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

if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data',
['Buffer', 'TypedArray', 'DataView'],
data
);
}

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

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

if (!isArrayBufferView(signature)) {
throw new ERR_INVALID_ARG_TYPE(
'signature',
['Buffer', 'TypedArray', 'DataView'],
signature
);
}

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

module.exports = {
Sign,
Verify
signOneShot,
Verify,
verifyOneShot
};
Loading

0 comments on commit 7d0e50d

Please sign in to comment.