Skip to content

Commit 61d1d20

Browse files
committed
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: nodejs#1127 PR-URL: nodejs#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>
1 parent 57beeb8 commit 61d1d20

8 files changed

+537
-18
lines changed

doc/api/crypto.md

+57-4
Original file line numberDiff line numberDiff line change
@@ -896,17 +896,32 @@ console.log(sign.sign(privateKey).toString('hex'));
896896
### sign.sign(private_key[, output_format])
897897
<!-- YAML
898898
added: v0.1.92
899+
changes:
900+
- version: REPLACEME
901+
pr-url: https://github.com/nodejs/node/pull/11705
902+
description: Support for RSASSA-PSS and additional options was added.
899903
-->
900904

901905
Calculates the signature on all the data passed through using either
902906
[`sign.update()`][] or [`sign.write()`][stream-writable-write].
903907

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

908-
* `key`: {string} - PEM encoded private key
912+
* `key`: {string} - PEM encoded private key (required)
909913
* `passphrase`: {string} - passphrase for the private key
914+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
915+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
916+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
917+
918+
Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
919+
used to sign the message as specified in section 3.1 of [RFC 4055][].
920+
* `saltLength`: {integer} - salt length for when padding is
921+
`RSA_PKCS1_PSS_PADDING`. The special value
922+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
923+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
924+
maximum permissible value.
910925

911926
The `output_format` can specify one of `'latin1'`, `'hex'` or `'base64'`. If
912927
`output_format` is provided a string is returned; otherwise a [`Buffer`][] is
@@ -989,11 +1004,33 @@ This can be called many times with new data as it is streamed.
9891004
### verifier.verify(object, signature[, signature_format])
9901005
<!-- YAML
9911006
added: v0.1.92
1007+
changes:
1008+
- version: REPLACEME
1009+
pr-url: https://github.com/nodejs/node/pull/11705
1010+
description: Support for RSASSA-PSS and additional options was added.
9921011
-->
1012+
- `object` {string | Object}
1013+
- `signature` {string | Buffer | Uint8Array}
1014+
- `signature_format` {string}
9931015

9941016
Verifies the provided data using the given `object` and `signature`.
995-
The `object` argument is a string containing a PEM encoded object, which can be
996-
one an RSA public key, a DSA public key, or an X.509 certificate.
1017+
The `object` argument can be either a string containing a PEM encoded object,
1018+
which can be an RSA public key, a DSA public key, or an X.509 certificate,
1019+
or an object with one or more of the following properties:
1020+
1021+
* `key`: {string} - PEM encoded public key (required)
1022+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
1023+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
1024+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
1025+
1026+
Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
1027+
used to verify the message as specified in section 3.1 of [RFC 4055][].
1028+
* `saltLength`: {integer} - salt length for when padding is
1029+
`RSA_PKCS1_PSS_PADDING`. The special value
1030+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
1031+
size, `crypto.constants.RSA_PSS_SALTLEN_AUTO` (default) causes it to be
1032+
determined automatically.
1033+
9971034
The `signature` argument is the previously calculated signature for the data, in
9981035
the `signature_format` which can be `'latin1'`, `'hex'` or `'base64'`.
9991036
If a `signature_format` is specified, the `signature` is expected to be a
@@ -1900,6 +1937,21 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
19001937
<td><code>RSA_PKCS1_PSS_PADDING</code></td>
19011938
<td></td>
19021939
</tr>
1940+
<tr>
1941+
<td><code>RSA_PSS_SALTLEN_DIGEST</code></td>
1942+
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the digest size
1943+
when signing or verifying.</td>
1944+
</tr>
1945+
<tr>
1946+
<td><code>RSA_PSS_SALTLEN_MAX_SIGN</code></td>
1947+
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the maximum
1948+
permissible value when signing data.</td>
1949+
</tr>
1950+
<tr>
1951+
<td><code>RSA_PSS_SALTLEN_AUTO</code></td>
1952+
<td>Causes the salt length for `RSA_PKCS1_PSS_PADDING` to be determined
1953+
automatically when verifying a signature.</td>
1954+
</tr>
19031955
<tr>
19041956
<td><code>POINT_CONVERSION_COMPRESSED</code></td>
19051957
<td></td>
@@ -1975,6 +2027,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
19752027
[publicly trusted list of CAs]: https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt
19762028
[RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt
19772029
[RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt
2030+
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
19782031
[stream]: stream.html
19792032
[stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
19802033
[Crypto Constants]: #crypto_crypto_constants_1

lib/crypto.js

+46-3
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,28 @@ Sign.prototype.sign = function sign(options, encoding) {
283283

284284
var key = options.key || options;
285285
var passphrase = options.passphrase || null;
286-
var ret = this._handle.sign(toBuf(key), null, passphrase);
286+
287+
// Options specific to RSA
288+
var rsaPadding = constants.RSA_PKCS1_PADDING;
289+
if (options.hasOwnProperty('padding')) {
290+
if (options.padding === options.padding >> 0) {
291+
rsaPadding = options.padding;
292+
} else {
293+
throw new TypeError('padding must be an integer');
294+
}
295+
}
296+
297+
var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO;
298+
if (options.hasOwnProperty('saltLength')) {
299+
if (options.saltLength === options.saltLength >> 0) {
300+
pssSaltLength = options.saltLength;
301+
} else {
302+
throw new TypeError('saltLength must be an integer');
303+
}
304+
}
305+
306+
var ret = this._handle.sign(toBuf(key), null, passphrase, rsaPadding,
307+
pssSaltLength);
287308

288309
encoding = encoding || exports.DEFAULT_ENCODING;
289310
if (encoding && encoding !== 'buffer')
@@ -309,9 +330,31 @@ util.inherits(Verify, stream.Writable);
309330
Verify.prototype._write = Sign.prototype._write;
310331
Verify.prototype.update = Sign.prototype.update;
311332

312-
Verify.prototype.verify = function verify(object, signature, sigEncoding) {
333+
Verify.prototype.verify = function verify(options, signature, sigEncoding) {
334+
var key = options.key || options;
313335
sigEncoding = sigEncoding || exports.DEFAULT_ENCODING;
314-
return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding));
336+
337+
// Options specific to RSA
338+
var rsaPadding = constants.RSA_PKCS1_PADDING;
339+
if (options.hasOwnProperty('padding')) {
340+
if (options.padding === options.padding >> 0) {
341+
rsaPadding = options.padding;
342+
} else {
343+
throw new TypeError('padding must be an integer');
344+
}
345+
}
346+
347+
var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO;
348+
if (options.hasOwnProperty('saltLength')) {
349+
if (options.saltLength === options.saltLength >> 0) {
350+
pssSaltLength = options.saltLength;
351+
} else {
352+
throw new TypeError('saltLength must be an integer');
353+
}
354+
}
355+
356+
return this._handle.verify(toBuf(key), toBuf(signature, sigEncoding), null,
357+
rsaPadding, pssSaltLength);
315358
};
316359

317360
function rsaPublic(method, defaultPadding) {

src/node_constants.cc

+12
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,18 @@ void DefineOpenSSLConstants(Local<Object> target) {
974974
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
975975
#endif
976976

977+
#ifdef RSA_PSS_SALTLEN_DIGEST
978+
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_DIGEST);
979+
#endif
980+
981+
#ifdef RSA_PSS_SALTLEN_MAX_SIGN
982+
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_MAX_SIGN);
983+
#endif
984+
985+
#ifdef RSA_PSS_SALTLEN_AUTO
986+
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_AUTO);
987+
#endif
988+
977989
#if HAVE_OPENSSL
978990
// NOTE: These are not defines
979991
NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED);

src/node_constants.h

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
#include "v8.h"
88

99
#if HAVE_OPENSSL
10+
11+
#ifndef RSA_PSS_SALTLEN_DIGEST
12+
#define RSA_PSS_SALTLEN_DIGEST -1
13+
#endif
14+
15+
#ifndef RSA_PSS_SALTLEN_MAX_SIGN
16+
#define RSA_PSS_SALTLEN_MAX_SIGN -2
17+
#endif
18+
19+
#ifndef RSA_PSS_SALTLEN_AUTO
20+
#define RSA_PSS_SALTLEN_AUTO -2
21+
#endif
22+
1023
#define DEFAULT_CIPHER_LIST_CORE "ECDHE-RSA-AES128-GCM-SHA256:" \
1124
"ECDHE-ECDSA-AES128-GCM-SHA256:" \
1225
"ECDHE-RSA-AES256-GCM-SHA384:" \

0 commit comments

Comments
 (0)