Permalink
Browse files

crypto: add sign/verify support for RSASSA-PSS

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 Mar 5, 2017
1 parent c68da89 commit 0e710aada401b1cf89b284d8469d112ddf277fe0
Showing with 530 additions and 19 deletions.
  1. +55 −5 doc/api/crypto.md
  2. +46 −3 lib/crypto.js
  3. +12 −0 src/node_constants.cc
  4. +13 −0 src/node_constants.h
  5. +113 −10 src/node_crypto.cc
  6. +5 −1 src/node_crypto.h
  7. +89 −0 test/fixtures/pss-vectors.json
  8. +197 −0 test/parallel/test-crypto-sign-verify.js
View
@@ -964,6 +964,10 @@ console.log(sign.sign(privateKey).toString('hex'));
### sign.sign(private_key[, output_format])
<!-- YAML
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}
- `key` {string}
@@ -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
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
* `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
`output_format` is provided a string is returned; otherwise a [`Buffer`][] is
@@ -1073,14 +1088,33 @@ This can be called many times with new data as it is streamed.
### verifier.verify(object, signature[, signature_format])
<!-- YAML
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_format` {string}
Verifies the provided data using the given `object` and `signature`.
The `object` argument is a string containing a PEM encoded object, which can be
an RSA public key, a DSA public key, or an X.509 certificate.
The `object` argument can be either a string containing a PEM encoded object,
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_format` which can be `'latin1'`, `'hex'` or `'base64'`.
If a `signature_format` is specified, the `signature` is expected to be a
@@ -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></td>
</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>
<td><code>POINT_CONVERSION_COMPRESSED</code></td>
<td></td>
@@ -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
[RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.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-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
[Crypto Constants]: #crypto_crypto_constants_1
View
@@ -304,7 +304,28 @@ Sign.prototype.sign = function sign(options, encoding) {
var key = options.key || options;
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;
if (encoding && encoding !== 'buffer')
@@ -330,9 +351,31 @@ util.inherits(Verify, stream.Writable);
Verify.prototype._write = Sign.prototype._write;
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;
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) {
View
@@ -997,6 +997,18 @@ void DefineOpenSSLConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
#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
// NOTE: These are not defines
NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED);
View
@@ -28,6 +28,19 @@
#include "v8.h"
#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:" \
"ECDHE-ECDSA-AES128-GCM-SHA256:" \
"ECDHE-RSA-AES256-GCM-SHA384:" \
Oops, something went wrong.

0 comments on commit 0e710aa

Please sign in to comment.