Skip to content

Commit a7d4cad

Browse files
tniessenMylesBorins
authored andcommitted
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>
1 parent e31ab7c commit a7d4cad

8 files changed

+537
-18
lines changed

doc/api/crypto.md

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

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

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

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

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

9961018
Verifies the provided data using the given `object` and `signature`.
997-
The `object` argument is a string containing a PEM encoded object, which can be
998-
one an RSA public key, a DSA public key, or an X.509 certificate.
1019+
The `object` argument can be either a string containing a PEM encoded object,
1020+
which can be an RSA public key, a DSA public key, or an X.509 certificate,
1021+
or an object with one or more of the following properties:
1022+
1023+
* `key`: {string} - PEM encoded public key (required)
1024+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
1025+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
1026+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
1027+
1028+
Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
1029+
used to verify the message as specified in section 3.1 of [RFC 4055][].
1030+
* `saltLength`: {integer} - salt length for when padding is
1031+
`RSA_PKCS1_PSS_PADDING`. The special value
1032+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
1033+
size, `crypto.constants.RSA_PSS_SALTLEN_AUTO` (default) causes it to be
1034+
determined automatically.
1035+
9991036
The `signature` argument is the previously calculated signature for the data, in
10001037
the `signature_format` which can be `'latin1'`, `'hex'` or `'base64'`.
10011038
If a `signature_format` is specified, the `signature` is expected to be a
@@ -1902,6 +1939,21 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
19021939
<td><code>RSA_PKCS1_PSS_PADDING</code></td>
19031940
<td></td>
19041941
</tr>
1942+
<tr>
1943+
<td><code>RSA_PSS_SALTLEN_DIGEST</code></td>
1944+
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the digest size
1945+
when signing or verifying.</td>
1946+
</tr>
1947+
<tr>
1948+
<td><code>RSA_PSS_SALTLEN_MAX_SIGN</code></td>
1949+
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the maximum
1950+
permissible value when signing data.</td>
1951+
</tr>
1952+
<tr>
1953+
<td><code>RSA_PSS_SALTLEN_AUTO</code></td>
1954+
<td>Causes the salt length for `RSA_PKCS1_PSS_PADDING` to be determined
1955+
automatically when verifying a signature.</td>
1956+
</tr>
19051957
<tr>
19061958
<td><code>POINT_CONVERSION_COMPRESSED</code></td>
19071959
<td></td>
@@ -1977,6 +2029,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
19772029
[publicly trusted list of CAs]: https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt
19782030
[RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt
19792031
[RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt
2032+
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
19802033
[stream]: stream.html
19812034
[stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
19822035
[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)