Skip to content

Commit

Permalink
crypto: add sign/verify support for RSASSA-PSS
Browse files Browse the repository at this point in the history
Adds support for the PSS padding scheme. Until now, the sign/verify
functions used the old EVP_Sign*/EVP_Verify* OpenSSL API, making it
impossible to change the padding scheme. Fixed by first computing the
message digest and then signing/verifying with a custom EVP_PKEY_CTX,
allowing us to specify options such as the padding scheme and the PSS
salt length.

Fixes: #1127
PR-URL: #11705
Reviewed-By: Shigeki Ohtsu <ohtsu@ohtsu.org>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
  • Loading branch information
tniessen authored and addaleax committed Apr 1, 2017
1 parent c68da89 commit 0e710aa
Show file tree
Hide file tree
Showing 8 changed files with 530 additions and 19 deletions.
60 changes: 55 additions & 5 deletions doc/api/crypto.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -964,6 +964,10 @@ console.log(sign.sign(privateKey).toString('hex'));
### sign.sign(private_key[, output_format]) ### sign.sign(private_key[, output_format])
<!-- YAML <!-- YAML
added: v0.1.92 added: v0.1.92
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/11705
description: Support for RSASSA-PSS and additional options was added.
--> -->
- `private_key` {string | Object} - `private_key` {string | Object}
- `key` {string} - `key` {string}
Expand All @@ -975,10 +979,21 @@ Calculates the signature on all the data passed through using either


The `private_key` argument can be an object or a string. If `private_key` is a The `private_key` argument can be an object or a string. If `private_key` is a
string, it is treated as a raw key with no passphrase. If `private_key` is an string, it is treated as a raw key with no passphrase. If `private_key` is an
object, it is interpreted as a hash containing two properties: object, it must contain one or more of the following properties:


* `key`: {string} - PEM encoded private key * `key`: {string} - PEM encoded private key (required)
* `passphrase`: {string} - passphrase for the private key * `passphrase`: {string} - passphrase for the private key
* `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 `output_format` can specify one of `'latin1'`, `'hex'` or `'base64'`. If The `output_format` can specify one of `'latin1'`, `'hex'` or `'base64'`. If
`output_format` is provided a string is returned; otherwise a [`Buffer`][] is `output_format` is provided a string is returned; otherwise a [`Buffer`][] is
Expand Down Expand Up @@ -1073,14 +1088,33 @@ This can be called many times with new data as it is streamed.
### verifier.verify(object, signature[, signature_format]) ### verifier.verify(object, signature[, signature_format])
<!-- YAML <!-- YAML
added: v0.1.92 added: v0.1.92
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/11705
description: Support for RSASSA-PSS and additional options was added.
--> -->
- `object` {string} - `object` {string | Object}
- `signature` {string | Buffer | Uint8Array} - `signature` {string | Buffer | Uint8Array}
- `signature_format` {string} - `signature_format` {string}


Verifies the provided data using the given `object` and `signature`. Verifies the provided data using the given `object` and `signature`.
The `object` argument is a string containing a PEM encoded object, which can be The `object` argument can be either a string containing a PEM encoded object,
an RSA public key, a DSA public key, or an X.509 certificate. which can be an RSA public key, a DSA public key, or an X.509 certificate,
or an object with one or more of the following properties:

* `key`: {string} - PEM encoded private key (required)
* `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 verify 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_AUTO` (default) causes it to be
determined automatically.

The `signature` argument is the previously calculated signature for the data, in The `signature` argument is the previously calculated signature for the data, in
the `signature_format` which can be `'latin1'`, `'hex'` or `'base64'`. the `signature_format` which can be `'latin1'`, `'hex'` or `'base64'`.
If a `signature_format` is specified, the `signature` is expected to be a If a `signature_format` is specified, the `signature` is expected to be a
Expand Down Expand Up @@ -2047,6 +2081,21 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
<td><code>RSA_PKCS1_PSS_PADDING</code></td> <td><code>RSA_PKCS1_PSS_PADDING</code></td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td><code>RSA_PSS_SALTLEN_DIGEST</code></td>
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the digest size
when signing or verifying.</td>
</tr>
<tr>
<td><code>RSA_PSS_SALTLEN_MAX_SIGN</code></td>
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the maximum
permissible value when signing data.</td>
</tr>
<tr>
<td><code>RSA_PSS_SALTLEN_AUTO</code></td>
<td>Causes the salt length for `RSA_PKCS1_PSS_PADDING` to be determined
automatically when verifying a signature.</td>
</tr>
<tr> <tr>
<td><code>POINT_CONVERSION_COMPRESSED</code></td> <td><code>POINT_CONVERSION_COMPRESSED</code></td>
<td></td> <td></td>
Expand Down Expand Up @@ -2122,6 +2171,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[publicly trusted list of CAs]: https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt [publicly trusted list of CAs]: https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt
[RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt [RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt
[RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt [RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
[stream]: stream.html [stream]: stream.html
[stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback [stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
[Crypto Constants]: #crypto_crypto_constants_1 [Crypto Constants]: #crypto_crypto_constants_1
49 changes: 46 additions & 3 deletions lib/crypto.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -304,7 +304,28 @@ Sign.prototype.sign = function sign(options, encoding) {


var key = options.key || options; var key = options.key || options;
var passphrase = options.passphrase || null; var passphrase = options.passphrase || null;
var ret = this._handle.sign(toBuf(key), null, passphrase);
// Options specific to RSA
var rsaPadding = constants.RSA_PKCS1_PADDING;
if (options.hasOwnProperty('padding')) {
if (options.padding === options.padding >> 0) {
rsaPadding = options.padding;
} else {
throw new TypeError('padding must be an integer');
}
}

var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO;
if (options.hasOwnProperty('saltLength')) {
if (options.saltLength === options.saltLength >> 0) {
pssSaltLength = options.saltLength;
} else {
throw new TypeError('saltLength must be an integer');
}
}

var ret = this._handle.sign(toBuf(key), null, passphrase, rsaPadding,
pssSaltLength);


encoding = encoding || exports.DEFAULT_ENCODING; encoding = encoding || exports.DEFAULT_ENCODING;
if (encoding && encoding !== 'buffer') if (encoding && encoding !== 'buffer')
Expand All @@ -330,9 +351,31 @@ util.inherits(Verify, stream.Writable);
Verify.prototype._write = Sign.prototype._write; Verify.prototype._write = Sign.prototype._write;
Verify.prototype.update = Sign.prototype.update; Verify.prototype.update = Sign.prototype.update;


Verify.prototype.verify = function verify(object, signature, sigEncoding) { Verify.prototype.verify = function verify(options, signature, sigEncoding) {
var key = options.key || options;
sigEncoding = sigEncoding || exports.DEFAULT_ENCODING; sigEncoding = sigEncoding || exports.DEFAULT_ENCODING;
return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding));
// Options specific to RSA
var rsaPadding = constants.RSA_PKCS1_PADDING;
if (options.hasOwnProperty('padding')) {
if (options.padding === options.padding >> 0) {
rsaPadding = options.padding;
} else {
throw new TypeError('padding must be an integer');
}
}

var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO;
if (options.hasOwnProperty('saltLength')) {
if (options.saltLength === options.saltLength >> 0) {
pssSaltLength = options.saltLength;
} else {
throw new TypeError('saltLength must be an integer');
}
}

return this._handle.verify(toBuf(key), toBuf(signature, sigEncoding), null,
rsaPadding, pssSaltLength);
}; };


function rsaPublic(method, defaultPadding) { function rsaPublic(method, defaultPadding) {
Expand Down
12 changes: 12 additions & 0 deletions src/node_constants.cc
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -997,6 +997,18 @@ void DefineOpenSSLConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING); NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
#endif #endif


#ifdef RSA_PSS_SALTLEN_DIGEST
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_DIGEST);
#endif

#ifdef RSA_PSS_SALTLEN_MAX_SIGN
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_MAX_SIGN);
#endif

#ifdef RSA_PSS_SALTLEN_AUTO
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_AUTO);
#endif

#if HAVE_OPENSSL #if HAVE_OPENSSL
// NOTE: These are not defines // NOTE: These are not defines
NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED); NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED);
Expand Down
13 changes: 13 additions & 0 deletions src/node_constants.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@
#include "v8.h" #include "v8.h"


#if HAVE_OPENSSL #if HAVE_OPENSSL

#ifndef RSA_PSS_SALTLEN_DIGEST
#define RSA_PSS_SALTLEN_DIGEST -1
#endif

#ifndef RSA_PSS_SALTLEN_MAX_SIGN
#define RSA_PSS_SALTLEN_MAX_SIGN -2
#endif

#ifndef RSA_PSS_SALTLEN_AUTO
#define RSA_PSS_SALTLEN_AUTO -2
#endif

#define DEFAULT_CIPHER_LIST_CORE "ECDHE-RSA-AES128-GCM-SHA256:" \ #define DEFAULT_CIPHER_LIST_CORE "ECDHE-RSA-AES128-GCM-SHA256:" \
"ECDHE-ECDSA-AES128-GCM-SHA256:" \ "ECDHE-ECDSA-AES128-GCM-SHA256:" \
"ECDHE-RSA-AES256-GCM-SHA384:" \ "ECDHE-RSA-AES256-GCM-SHA384:" \
Expand Down
Loading

0 comments on commit 0e710aa

Please sign in to comment.