Skip to content

Commit 0e710aa

Browse files
tniessenaddaleax
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 <[email protected]> Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent c68da89 commit 0e710aa

8 files changed

+530
-19
lines changed

doc/api/crypto.md

+55-5
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,10 @@ console.log(sign.sign(privateKey).toString('hex'));
964964
### sign.sign(private_key[, output_format])
965965
<!-- YAML
966966
added: v0.1.92
967+
changes:
968+
- version: REPLACEME
969+
pr-url: https://github.com/nodejs/node/pull/11705
970+
description: Support for RSASSA-PSS and additional options was added.
967971
-->
968972
- `private_key` {string | Object}
969973
- `key` {string}
@@ -975,10 +979,21 @@ Calculates the signature on all the data passed through using either
975979

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

980-
* `key`: {string} - PEM encoded private key
984+
* `key`: {string} - PEM encoded private key (required)
981985
* `passphrase`: {string} - passphrase for the private key
986+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
987+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
988+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
989+
990+
Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
991+
used to sign the message as specified in section 3.1 of [RFC 4055][].
992+
* `saltLength`: {integer} - salt length for when padding is
993+
`RSA_PKCS1_PSS_PADDING`. The special value
994+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
995+
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the
996+
maximum permissible value.
982997

983998
The `output_format` can specify one of `'latin1'`, `'hex'` or `'base64'`. If
984999
`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.
10731088
### verifier.verify(object, signature[, signature_format])
10741089
<!-- YAML
10751090
added: v0.1.92
1091+
changes:
1092+
- version: REPLACEME
1093+
pr-url: https://github.com/nodejs/node/pull/11705
1094+
description: Support for RSASSA-PSS and additional options was added.
10761095
-->
1077-
- `object` {string}
1096+
- `object` {string | Object}
10781097
- `signature` {string | Buffer | Uint8Array}
10791098
- `signature_format` {string}
10801099

10811100
Verifies the provided data using the given `object` and `signature`.
1082-
The `object` argument is a string containing a PEM encoded object, which can be
1083-
an RSA public key, a DSA public key, or an X.509 certificate.
1101+
The `object` argument can be either a string containing a PEM encoded object,
1102+
which can be an RSA public key, a DSA public key, or an X.509 certificate,
1103+
or an object with one or more of the following properties:
1104+
1105+
* `key`: {string} - PEM encoded private key (required)
1106+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
1107+
* `crypto.constants.RSA_PKCS1_PADDING` (default)
1108+
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
1109+
1110+
Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
1111+
used to verify the message as specified in section 3.1 of [RFC 4055][].
1112+
* `saltLength`: {integer} - salt length for when padding is
1113+
`RSA_PKCS1_PSS_PADDING`. The special value
1114+
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest
1115+
size, `crypto.constants.RSA_PSS_SALTLEN_AUTO` (default) causes it to be
1116+
determined automatically.
1117+
10841118
The `signature` argument is the previously calculated signature for the data, in
10851119
the `signature_format` which can be `'latin1'`, `'hex'` or `'base64'`.
10861120
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.
20472081
<td><code>RSA_PKCS1_PSS_PADDING</code></td>
20482082
<td></td>
20492083
</tr>
2084+
<tr>
2085+
<td><code>RSA_PSS_SALTLEN_DIGEST</code></td>
2086+
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the digest size
2087+
when signing or verifying.</td>
2088+
</tr>
2089+
<tr>
2090+
<td><code>RSA_PSS_SALTLEN_MAX_SIGN</code></td>
2091+
<td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the maximum
2092+
permissible value when signing data.</td>
2093+
</tr>
2094+
<tr>
2095+
<td><code>RSA_PSS_SALTLEN_AUTO</code></td>
2096+
<td>Causes the salt length for `RSA_PKCS1_PSS_PADDING` to be determined
2097+
automatically when verifying a signature.</td>
2098+
</tr>
20502099
<tr>
20512100
<td><code>POINT_CONVERSION_COMPRESSED</code></td>
20522101
<td></td>
@@ -2122,6 +2171,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
21222171
[publicly trusted list of CAs]: https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt
21232172
[RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt
21242173
[RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt
2174+
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
21252175
[stream]: stream.html
21262176
[stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
21272177
[Crypto Constants]: #crypto_crypto_constants_1

lib/crypto.js

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

305305
var key = options.key || options;
306306
var passphrase = options.passphrase || null;
307-
var ret = this._handle.sign(toBuf(key), null, passphrase);
307+
308+
// Options specific to RSA
309+
var rsaPadding = constants.RSA_PKCS1_PADDING;
310+
if (options.hasOwnProperty('padding')) {
311+
if (options.padding === options.padding >> 0) {
312+
rsaPadding = options.padding;
313+
} else {
314+
throw new TypeError('padding must be an integer');
315+
}
316+
}
317+
318+
var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO;
319+
if (options.hasOwnProperty('saltLength')) {
320+
if (options.saltLength === options.saltLength >> 0) {
321+
pssSaltLength = options.saltLength;
322+
} else {
323+
throw new TypeError('saltLength must be an integer');
324+
}
325+
}
326+
327+
var ret = this._handle.sign(toBuf(key), null, passphrase, rsaPadding,
328+
pssSaltLength);
308329

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

333-
Verify.prototype.verify = function verify(object, signature, sigEncoding) {
354+
Verify.prototype.verify = function verify(options, signature, sigEncoding) {
355+
var key = options.key || options;
334356
sigEncoding = sigEncoding || exports.DEFAULT_ENCODING;
335-
return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding));
357+
358+
// Options specific to RSA
359+
var rsaPadding = constants.RSA_PKCS1_PADDING;
360+
if (options.hasOwnProperty('padding')) {
361+
if (options.padding === options.padding >> 0) {
362+
rsaPadding = options.padding;
363+
} else {
364+
throw new TypeError('padding must be an integer');
365+
}
366+
}
367+
368+
var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO;
369+
if (options.hasOwnProperty('saltLength')) {
370+
if (options.saltLength === options.saltLength >> 0) {
371+
pssSaltLength = options.saltLength;
372+
} else {
373+
throw new TypeError('saltLength must be an integer');
374+
}
375+
}
376+
377+
return this._handle.verify(toBuf(key), toBuf(signature, sigEncoding), null,
378+
rsaPadding, pssSaltLength);
336379
};
337380

338381
function rsaPublic(method, defaultPadding) {

src/node_constants.cc

+12
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,18 @@ void DefineOpenSSLConstants(Local<Object> target) {
997997
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
998998
#endif
999999

1000+
#ifdef RSA_PSS_SALTLEN_DIGEST
1001+
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_DIGEST);
1002+
#endif
1003+
1004+
#ifdef RSA_PSS_SALTLEN_MAX_SIGN
1005+
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_MAX_SIGN);
1006+
#endif
1007+
1008+
#ifdef RSA_PSS_SALTLEN_AUTO
1009+
NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_AUTO);
1010+
#endif
1011+
10001012
#if HAVE_OPENSSL
10011013
// NOTE: These are not defines
10021014
NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED);

src/node_constants.h

+13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@
2828
#include "v8.h"
2929

3030
#if HAVE_OPENSSL
31+
32+
#ifndef RSA_PSS_SALTLEN_DIGEST
33+
#define RSA_PSS_SALTLEN_DIGEST -1
34+
#endif
35+
36+
#ifndef RSA_PSS_SALTLEN_MAX_SIGN
37+
#define RSA_PSS_SALTLEN_MAX_SIGN -2
38+
#endif
39+
40+
#ifndef RSA_PSS_SALTLEN_AUTO
41+
#define RSA_PSS_SALTLEN_AUTO -2
42+
#endif
43+
3144
#define DEFAULT_CIPHER_LIST_CORE "ECDHE-RSA-AES128-GCM-SHA256:" \
3245
"ECDHE-ECDSA-AES128-GCM-SHA256:" \
3346
"ECDHE-RSA-AES256-GCM-SHA384:" \

0 commit comments

Comments
 (0)