summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Nießen <tniessen@tnie.de>2017-03-06 00:41:26 +0100
committerAnna Henningsen <anna@addaleax.net>2017-04-01 13:27:52 +0200
commit0e710aada401b1cf89b284d8469d112ddf277fe0 (patch)
tree446fc73f722084de1ea667273f5a2c1d8612be1f
parentc68da89694b1ff4682131ed6b825e596188cc4ed (diff)
downloadandroid-node-v8-0e710aada401b1cf89b284d8469d112ddf277fe0.tar.gz
android-node-v8-0e710aada401b1cf89b284d8469d112ddf277fe0.tar.bz2
android-node-v8-0e710aada401b1cf89b284d8469d112ddf277fe0.zip
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: https://github.com/nodejs/node/issues/1127 PR-URL: https://github.com/nodejs/node/pull/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>
-rw-r--r--doc/api/crypto.md60
-rw-r--r--lib/crypto.js49
-rw-r--r--src/node_constants.cc12
-rw-r--r--src/node_constants.h13
-rw-r--r--src/node_crypto.cc123
-rw-r--r--src/node_crypto.h6
-rw-r--r--test/fixtures/pss-vectors.json89
-rw-r--r--test/parallel/test-crypto-sign-verify.js197
8 files changed, 530 insertions, 19 deletions
diff --git a/doc/api/crypto.md b/doc/api/crypto.md
index f3584cb5a5..fcbdcb6bfc 100644
--- a/doc/api/crypto.md
+++ b/doc/api/crypto.md
@@ -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
@@ -2048,6 +2082,21 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
<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>
</tr>
@@ -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
diff --git a/lib/crypto.js b/lib/crypto.js
index 662ddef60e..3e7ed5e9c8 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -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) {
diff --git a/src/node_constants.cc b/src/node_constants.cc
index 5bde53fcdf..8bc95392f2 100644
--- a/src/node_constants.cc
+++ b/src/node_constants.cc
@@ -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);
diff --git a/src/node_constants.h b/src/node_constants.h
index 047d8fc5e7..1de420e2de 100644
--- a/src/node_constants.h
+++ b/src/node_constants.h
@@ -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:" \
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 5d93184ba6..45b06eaff5 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -21,6 +21,7 @@
#include "node.h"
#include "node_buffer.h"
+#include "node_constants.h"
#include "node_crypto.h"
#include "node_crypto_bio.h"
#include "node_crypto_groups.h"
@@ -101,6 +102,7 @@ using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
+using v8::Maybe;
using v8::Null;
using v8::Object;
using v8::Persistent;
@@ -3976,6 +3978,19 @@ void SignBase::CheckThrow(SignBase::Error error) {
}
}
+static bool ApplyRSAOptions(EVP_PKEY* pkey, EVP_PKEY_CTX* pkctx, int padding,
+ int salt_len) {
+ if (pkey->type == EVP_PKEY_RSA || pkey->type == EVP_PKEY_RSA2) {
+ if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0)
+ return false;
+ if (padding == RSA_PKCS1_PSS_PADDING) {
+ if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, salt_len) <= 0)
+ return false;
+ }
+ }
+
+ return true;
+}
@@ -4005,7 +4020,7 @@ SignBase::Error Sign::SignInit(const char* sign_type) {
return kSignUnknownDigest;
EVP_MD_CTX_init(&mdctx_);
- if (!EVP_SignInit_ex(&mdctx_, md, nullptr))
+ if (!EVP_DigestInit_ex(&mdctx_, md, nullptr))
return kSignInit;
initialised_ = true;
@@ -4032,7 +4047,7 @@ void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
SignBase::Error Sign::SignUpdate(const char* data, int len) {
if (!initialised_)
return kSignNotInitialised;
- if (!EVP_SignUpdate(&mdctx_, data, len))
+ if (!EVP_DigestUpdate(&mdctx_, data, len))
return kSignUpdate;
return kSignOk;
}
@@ -4062,12 +4077,54 @@ void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
sign->CheckThrow(err);
}
+static int Node_SignFinal(EVP_MD_CTX* mdctx, unsigned char* md,
+ unsigned int* sig_len, EVP_PKEY* pkey, int padding,
+ int pss_salt_len) {
+ unsigned char m[EVP_MAX_MD_SIZE];
+ unsigned int m_len;
+ int rv = 0;
+ EVP_PKEY_CTX* pkctx = nullptr;
+
+ *sig_len = 0;
+ if (!EVP_DigestFinal_ex(mdctx, m, &m_len))
+ return rv;
+
+ if (mdctx->digest->flags & EVP_MD_FLAG_PKEY_METHOD_SIGNATURE) {
+ size_t sltmp = static_cast<size_t>(EVP_PKEY_size(pkey));
+ pkctx = EVP_PKEY_CTX_new(pkey, nullptr);
+ if (pkctx == nullptr)
+ goto err;
+ if (EVP_PKEY_sign_init(pkctx) <= 0)
+ goto err;
+ if (!ApplyRSAOptions(pkey, pkctx, padding, pss_salt_len))
+ goto err;
+ if (EVP_PKEY_CTX_set_signature_md(pkctx, mdctx->digest) <= 0)
+ goto err;
+ if (EVP_PKEY_sign(pkctx, md, &sltmp, m, m_len) <= 0)
+ goto err;
+ *sig_len = sltmp;
+ rv = 1;
+ err:
+ EVP_PKEY_CTX_free(pkctx);
+ return rv;
+ }
+
+ if (mdctx->digest->sign == nullptr) {
+ EVPerr(EVP_F_EVP_SIGNFINAL, EVP_R_NO_SIGN_FUNCTION_CONFIGURED);
+ return 0;
+ }
+
+ return mdctx->digest->sign(mdctx->digest->type, m, m_len, md, sig_len,
+ pkey->pkey.ptr);
+}
SignBase::Error Sign::SignFinal(const char* key_pem,
int key_pem_len,
const char* passphrase,
unsigned char** sig,
- unsigned int *sig_len) {
+ unsigned int* sig_len,
+ int padding,
+ int salt_len) {
if (!initialised_)
return kSignNotInitialised;
@@ -4113,7 +4170,7 @@ SignBase::Error Sign::SignFinal(const char* key_pem,
}
#endif // NODE_FIPS_MODE
- if (EVP_SignFinal(&mdctx_, *sig, sig_len, pkey))
+ if (Node_SignFinal(&mdctx_, *sig, sig_len, pkey, padding, salt_len))
fatal = false;
initialised_ = false;
@@ -4156,6 +4213,16 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
size_t buf_len = Buffer::Length(args[0]);
char* buf = Buffer::Data(args[0]);
+ CHECK(args[3]->IsInt32());
+ Maybe<int32_t> maybe_padding = args[3]->Int32Value(env->context());
+ CHECK(maybe_padding.IsJust());
+ int padding = maybe_padding.ToChecked();
+
+ CHECK(args[4]->IsInt32());
+ Maybe<int32_t> maybe_salt_len = args[4]->Int32Value(env->context());
+ CHECK(maybe_salt_len.IsJust());
+ int salt_len = maybe_salt_len.ToChecked();
+
md_len = 8192; // Maximum key size is 8192 bits
md_value = new unsigned char[md_len];
@@ -4167,7 +4234,9 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
buf_len,
len >= 3 && !args[2]->IsNull() ? *passphrase : nullptr,
&md_value,
- &md_len);
+ &md_len,
+ padding,
+ salt_len);
if (err != kSignOk) {
delete[] md_value;
md_value = nullptr;
@@ -4211,7 +4280,7 @@ SignBase::Error Verify::VerifyInit(const char* verify_type) {
return kSignUnknownDigest;
EVP_MD_CTX_init(&mdctx_);
- if (!EVP_VerifyInit_ex(&mdctx_, md, nullptr))
+ if (!EVP_DigestInit_ex(&mdctx_, md, nullptr))
return kSignInit;
initialised_ = true;
@@ -4239,7 +4308,7 @@ SignBase::Error Verify::VerifyUpdate(const char* data, int len) {
if (!initialised_)
return kSignNotInitialised;
- if (!EVP_VerifyUpdate(&mdctx_, data, len))
+ if (!EVP_DigestUpdate(&mdctx_, data, len))
return kSignUpdate;
return kSignOk;
@@ -4275,6 +4344,8 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem,
int key_pem_len,
const char* sig,
int siglen,
+ int padding,
+ int saltlen,
bool* verify_result) {
if (!initialised_)
return kSignNotInitialised;
@@ -4286,7 +4357,10 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem,
BIO* bp = nullptr;
X509* x509 = nullptr;
bool fatal = true;
+ unsigned char m[EVP_MAX_MD_SIZE];
+ unsigned int m_len;
int r = 0;
+ EVP_PKEY_CTX* pkctx = nullptr;
bp = BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len);
if (bp == nullptr)
@@ -4321,11 +4395,29 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem,
goto exit;
}
+ if (!EVP_DigestFinal_ex(&mdctx_, m, &m_len)) {
+ goto exit;
+ }
+
fatal = false;
- r = EVP_VerifyFinal(&mdctx_,
+
+ pkctx = EVP_PKEY_CTX_new(pkey, nullptr);
+ if (pkctx == nullptr)
+ goto err;
+ if (EVP_PKEY_verify_init(pkctx) <= 0)
+ goto err;
+ if (!ApplyRSAOptions(pkey, pkctx, padding, saltlen))
+ goto err;
+ if (EVP_PKEY_CTX_set_signature_md(pkctx, mdctx_.digest) <= 0)
+ goto err;
+ r = EVP_PKEY_verify(pkctx,
reinterpret_cast<const unsigned char*>(sig),
siglen,
- pkey);
+ m,
+ m_len);
+
+ err:
+ EVP_PKEY_CTX_free(pkctx);
exit:
if (pkey != nullptr)
@@ -4381,8 +4473,19 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
hbuf = Buffer::Data(args[1]);
}
+ CHECK(args[3]->IsInt32());
+ Maybe<int32_t> maybe_padding = args[3]->Int32Value(env->context());
+ CHECK(maybe_padding.IsJust());
+ int padding = maybe_padding.ToChecked();
+
+ CHECK(args[4]->IsInt32());
+ Maybe<int32_t> maybe_salt_len = args[4]->Int32Value(env->context());
+ CHECK(maybe_salt_len.IsJust());
+ int salt_len = maybe_salt_len.ToChecked();
+
bool verify_result;
- Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, &verify_result);
+ Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, padding, salt_len,
+ &verify_result);
if (args[1]->IsString())
delete[] hbuf;
if (err != kSignOk)
diff --git a/src/node_crypto.h b/src/node_crypto.h
index 63e6ab684f..ffb8444ce6 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -592,7 +592,9 @@ class Sign : public SignBase {
int key_pem_len,
const char* passphrase,
unsigned char** sig,
- unsigned int *sig_len);
+ unsigned int *sig_len,
+ int padding,
+ int saltlen);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -615,6 +617,8 @@ class Verify : public SignBase {
int key_pem_len,
const char* sig,
int siglen,
+ int padding,
+ int saltlen,
bool* verify_result);
protected:
diff --git a/test/fixtures/pss-vectors.json b/test/fixtures/pss-vectors.json
new file mode 100644
index 0000000000..b540d13a54
--- /dev/null
+++ b/test/fixtures/pss-vectors.json
@@ -0,0 +1,89 @@
+{
+ "example01": {
+ "publicKey": [
+ "-----BEGIN PUBLIC KEY-----",
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClbkoOcBAXWJpRh9x+qEHRVvLs",
+ "DjatUqRN/rHmH3rZkdjFEFb/7bFitMDyg6EqiKOU3/Umq3KRy7MHzqv84LHf1c2V",
+ "CAltWyuLbfXWce9jd8CSHLI8Jwpw4lmOb/idGfEFrMLT8Ms18pKA4Thrb2TE7yLh",
+ "4fINDOjP+yJJvZohNwIDAQAB",
+ "-----END PUBLIC KEY-----"
+ ],
+ "tests": [
+ {
+ "message": "cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b62371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb769757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb061a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d6193c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0",
+ "salt": "dee959c7e06411361420ff80185ed57f3e6776af",
+ "signature": "9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887e57ce7fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215df6ebb9c2f9579aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65984a601bc69eb28deb27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c"
+ },
+ {
+ "message": "851384cdfe819c22ed6c4ccb30daeb5cf059bc8e1166b7e3530c4c233e2b5f8f71a1cca582d43ecc72b1bca16dfc7013226b9e",
+ "salt": "ef2869fa40c346cb183dab3d7bffc98fd56df42d",
+ "signature": "3ef7f46e831bf92b32274142a585ffcefbdca7b32ae90d10fb0f0c729984f04ef29a9df0780775ce43739b97838390db0a5505e63de927028d9d29b219ca2c4517832558a55d694a6d25b9dab66003c4cccd907802193be5170d26147d37b93590241be51c25055f47ef62752cfbe21418fafe98c22c4d4d47724fdb5669e843"
+ },
+ {
+ "message": "a4b159941761c40c6a82f2b80d1b94f5aa2654fd17e12d588864679b54cd04ef8bd03012be8dc37f4b83af7963faff0dfa225477437c48017ff2be8191cf3955fc07356eab3f322f7f620e21d254e5db4324279fe067e0910e2e81ca2cab31c745e67a54058eb50d993cdb9ed0b4d029c06d21a94ca661c3ce27fae1d6cb20f4564d66ce4767583d0e5f060215b59017be85ea848939127bd8c9c4d47b51056c031cf336f17c9980f3b8f5b9b6878e8b797aa43b882684333e17893fe9caa6aa299f7ed1a18ee2c54864b7b2b99b72618fb02574d139ef50f019c9eef416971338e7d470",
+ "salt": "710b9c4747d800d4de87f12afdce6df18107cc77",
+ "signature": "666026fba71bd3e7cf13157cc2c51a8e4aa684af9778f91849f34335d141c00154c4197621f9624a675b5abc22ee7d5baaffaae1c9baca2cc373b3f33e78e6143c395a91aa7faca664eb733afd14d8827259d99a7550faca501ef2b04e33c23aa51f4b9e8282efdb728cc0ab09405a91607c6369961bc8270d2d4f39fce612b1"
+ },
+ {
+ "message": "bc656747fa9eafb3f0",
+ "salt": "056f00985de14d8ef5cea9e82f8c27bef720335e",
+ "signature": "4609793b23e9d09362dc21bb47da0b4f3a7622649a47d464019b9aeafe53359c178c91cd58ba6bcb78be0346a7bc637f4b873d4bab38ee661f199634c547a1ad8442e03da015b136e543f7ab07c0c13e4225b8de8cce25d4f6eb8400f81f7e1833b7ee6e334d370964ca79fdb872b4d75223b5eeb08101591fb532d155a6de87"
+ },
+ {
+ "message": "b45581547e5427770c768e8b82b75564e0ea4e9c32594d6bff706544de0a8776c7a80b4576550eee1b2acabc7e8b7d3ef7bb5b03e462c11047eadd00629ae575480ac1470fe046f13a2bf5af17921dc4b0aa8b02bee6334911651d7f8525d10f32b51d33be520d3ddf5a709955a3dfe78283b9e0ab54046d150c177f037fdccc5be4ea5f68b5e5a38c9d7edcccc4975f455a6909b4",
+ "salt": "80e70ff86a08de3ec60972b39b4fbfdcea67ae8e",
+ "signature": "1d2aad221ca4d31ddf13509239019398e3d14b32dc34dc5af4aeaea3c095af73479cf0a45e5629635a53a018377615b16cb9b13b3e09d671eb71e387b8545c5960da5a64776e768e82b2c93583bf104c3fdb23512b7b4e89f633dd0063a530db4524b01c3f384c09310e315a79dcd3d684022a7f31c865a664e316978b759fad"
+ },
+ {
+ "message": "10aae9a0ab0b595d0841207b700d48d75faedde3b775cd6b4cc88ae06e4694ec74ba18f8520d4f5ea69cbbe7cc2beba43efdc10215ac4eb32dc302a1f53dc6c4352267e7936cfebf7c8d67035784a3909fa859c7b7b59b8e39c5c2349f1886b705a30267d402f7486ab4f58cad5d69adb17ab8cd0ce1caf5025af4ae24b1fb8794c6070cc09a51e2f9911311e3877d0044c71c57a993395008806b723ac38373d395481818528c1e7053739282053529510e935cd0fa77b8fa53cc2d474bd4fb3cc5c672d6ffdc90a00f9848712c4bcfe46c60573659b11e6457e861f0f604b6138d144f8ce4e2da73",
+ "salt": "a8ab69dd801f0074c2a1fc60649836c616d99681",
+ "signature": "2a34f6125e1f6b0bf971e84fbd41c632be8f2c2ace7de8b6926e31ff93e9af987fbc06e51e9be14f5198f91f3f953bd67da60a9df59764c3dc0fe08e1cbef0b75f868d10ad3fba749fef59fb6dac46a0d6e504369331586f58e4628f39aa278982543bc0eeb537dc61958019b394fb273f215858a0a01ac4d650b955c67f4c58"
+ }
+ ]
+ },
+ "example10": {
+ "publicKey": [
+ "-----BEGIN PUBLIC KEY-----",
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd2GesTLAvkLlFfUjBSn",
+ "cO+ZHFbDnA7GX9Ea+ok3zqV7m+esc7RcABdhW4LWIuMYdTtgJ8D9FXvhL4CQ/uKn",
+ "rc0O73WfiLpJl8ekLVjJqhLLma4AH+UhwTu1QxRFqNWuT15MfpSKwifTYEBx8g5X",
+ "fpBfvrFd+vBtHeWuYlPWOmohILMaXaXavJVQYA4g8n03OeJieSX+o8xQnyHf8E5u",
+ "6kVJxUDWgJ/5MH7t6R//WHM9g4WiN9bTcFoz45GQCZIHDfet8TV89+NwDONmfeg/",
+ "F7jfF3jbOB3OCctK0FilEQAac4GY7ifPVaE7dUU5kGWC7IsXS9WNXR89dnxhNyGu",
+ "BQIDAQAB",
+ "-----END PUBLIC KEY-----"
+ ],
+ "tests": [
+ {
+ "message": "883177e5126b9be2d9a9680327d5370c6f26861f5820c43da67a3ad609",
+ "salt": "04e215ee6ff934b9da70d7730c8734abfcecde89",
+ "signature": "82c2b160093b8aa3c0f7522b19f87354066c77847abf2a9fce542d0e84e920c5afb49ffdfdace16560ee94a1369601148ebad7a0e151cf16331791a5727d05f21e74e7eb811440206935d744765a15e79f015cb66c532c87a6a05961c8bfad741a9a6657022894393e7223739796c02a77455d0f555b0ec01ddf259b6207fd0fd57614cef1a5573baaff4ec00069951659b85f24300a25160ca8522dc6e6727e57d019d7e63629b8fe5e89e25cc15beb3a647577559299280b9b28f79b0409000be25bbd96408ba3b43cc486184dd1c8e62553fa1af4040f60663de7f5e49c04388e257f1ce89c95dab48a315d9b66b1b7628233876ff2385230d070d07e1666"
+ },
+ {
+ "message": "dd670a01465868adc93f26131957a50c52fb777cdbaa30892c9e12361164ec13979d43048118e4445db87bee58dd987b3425d02071d8dbae80708b039dbb64dbd1de5657d9fed0c118a54143742e0ff3c87f74e45857647af3f79eb0a14c9d75ea9a1a04b7cf478a897a708fd988f48e801edb0b7039df8c23bb3c56f4e821ac",
+ "salt": "8b2bdd4b40faf545c778ddf9bc1a49cb57f9b71b",
+ "signature": "14ae35d9dd06ba92f7f3b897978aed7cd4bf5ff0b585a40bd46ce1b42cd2703053bb9044d64e813d8f96db2dd7007d10118f6f8f8496097ad75e1ff692341b2892ad55a633a1c55e7f0a0ad59a0e203a5b8278aec54dd8622e2831d87174f8caff43ee6c46445345d84a59659bfb92ecd4c818668695f34706f66828a89959637f2bf3e3251c24bdba4d4b7649da0022218b119c84e79a6527ec5b8a5f861c159952e23ec05e1e717346faefe8b1686825bd2b262fb2531066c0de09acde2e4231690728b5d85e115a2f6b92b79c25abc9bd9399ff8bcf825a52ea1f56ea76dd26f43baafa18bfa92a504cbd35699e26d1dcc5a2887385f3c63232f06f3244c3"
+ },
+ {
+ "message": "48b2b6a57a63c84cea859d65c668284b08d96bdcaabe252db0e4a96cb1bac6019341db6fbefb8d106b0e90eda6bcc6c6262f37e7ea9c7e5d226bd7df85ec5e71efff2f54c5db577ff729ff91b842491de2741d0c631607df586b905b23b91af13da12304bf83eca8a73e871ff9db",
+ "salt": "4e96fc1b398f92b44671010c0dc3efd6e20c2d73",
+ "signature": "6e3e4d7b6b15d2fb46013b8900aa5bbb3939cf2c095717987042026ee62c74c54cffd5d7d57efbbf950a0f5c574fa09d3fc1c9f513b05b4ff50dd8df7edfa20102854c35e592180119a70ce5b085182aa02d9ea2aa90d1df03f2daae885ba2f5d05afdac97476f06b93b5bc94a1a80aa9116c4d615f333b098892b25fface266f5db5a5a3bcc10a824ed55aad35b727834fb8c07da28fcf416a5d9b2224f1f8b442b36f91e456fdea2d7cfe3367268de0307a4c74e924159ed33393d5e0655531c77327b89821bdedf880161c78cd4196b5419f7acc3f13e5ebf161b6e7c6724716ca33b85c2e25640192ac2859651d50bde7eb976e51cec828b98b6563b86bb"
+ },
+ {
+ "message": "0b8777c7f839baf0a64bbbdbc5ce79755c57a205b845c174e2d2e90546a089c4e6ec8adffa23a7ea97bae6b65d782b82db5d2b5a56d22a29a05e7c4433e2b82a621abba90add05ce393fc48a840542451a",
+ "salt": "c7cd698d84b65128d8835e3a8b1eb0e01cb541ec",
+ "signature": "34047ff96c4dc0dc90b2d4ff59a1a361a4754b255d2ee0af7d8bf87c9bc9e7ddeede33934c63ca1c0e3d262cb145ef932a1f2c0a997aa6a34f8eaee7477d82ccf09095a6b8acad38d4eec9fb7eab7ad02da1d11d8e54c1825e55bf58c2a23234b902be124f9e9038a8f68fa45dab72f66e0945bf1d8bacc9044c6f07098c9fcec58a3aab100c805178155f030a124c450e5acbda47d0e4f10b80a23f803e774d023b0015c20b9f9bbe7c91296338d5ecb471cafb032007b67a60be5f69504a9f01abb3cb467b260e2bce860be8d95bf92c0c8e1496ed1e528593a4abb6df462dde8a0968dffe4683116857a232f5ebf6c85be238745ad0f38f767a5fdbf486fb"
+ },
+ {
+ "message": "f1036e008e71e964dadc9219ed30e17f06b4b68a955c16b312b1eddf028b74976bed6b3f6a63d4e77859243c9cccdc98016523abb02483b35591c33aad81213bb7c7bb1a470aabc10d44256c4d4559d916",
+ "salt": "efa8bff96212b2f4a3f371a10d574152655f5dfb",
+ "signature": "7e0935ea18f4d6c1d17ce82eb2b3836c55b384589ce19dfe743363ac9948d1f346b7bfddfe92efd78adb21faefc89ade42b10f374003fe122e67429a1cb8cbd1f8d9014564c44d120116f4990f1a6e38774c194bd1b8213286b077b0499d2e7b3f434ab12289c556684deed78131934bb3dd6537236f7c6f3dcb09d476be07721e37e1ceed9b2f7b406887bd53157305e1c8b4f84d733bc1e186fe06cc59b6edb8f4bd7ffefdf4f7ba9cfb9d570689b5a1a4109a746a690893db3799255a0cb9215d2d1cd490590e952e8c8786aa0011265252470c041dfbc3eec7c3cbf71c24869d115c0cb4a956f56d530b80ab589acfefc690751ddf36e8d383f83cedd2cc"
+ },
+ {
+ "message": "25f10895a87716c137450bb9519dfaa1f207faa942ea88abf71e9c17980085b555aebab76264ae2a3ab93c2d12981191ddac6fb5949eb36aee3c5da940f00752c916d94608fa7d97ba6a2915b688f20323d4e9d96801d89a72ab5892dc2117c07434fcf972e058cf8c41ca4b4ff554f7d5068ad3155fced0f3125bc04f9193378a8f5c4c3b8cb4dd6d1cc69d30ecca6eaa51e36a05730e9e342e855baf099defb8afd7",
+ "salt": "ad8b1523703646224b660b550885917ca2d1df28",
+ "signature": "6d3b5b87f67ea657af21f75441977d2180f91b2c5f692de82955696a686730d9b9778d970758ccb26071c2209ffbd6125be2e96ea81b67cb9b9308239fda17f7b2b64ecda096b6b935640a5a1cb42a9155b1c9ef7a633a02c59f0d6ee59b852c43b35029e73c940ff0410e8f114eed46bbd0fae165e42be2528a401c3b28fd818ef3232dca9f4d2a0f5166ec59c42396d6c11dbc1215a56fa17169db9575343ef34f9de32a49cdc3174922f229c23e18e45df9353119ec4319cedce7a17c64088c1f6f52be29634100b3919d38f3d1ed94e6891e66a73b8fb849f5874df59459e298c7bbce2eee782a195aa66fe2d0732b25e595f57d3e061b1fc3e4063bf98f"
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js
index 81b2c109b6..241f2b6ee9 100644
--- a/test/parallel/test-crypto-sign-verify.js
+++ b/test/parallel/test-crypto-sign-verify.js
@@ -2,6 +2,8 @@
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
+const path = require('path');
+const exec = require('child_process').exec;
if (!common.hasCrypto) {
common.skip('missing crypto');
@@ -12,6 +14,7 @@ const crypto = require('crypto');
// Test certificates
const certPem = fs.readFileSync(common.fixturesDir + '/test_cert.pem', 'ascii');
const keyPem = fs.readFileSync(common.fixturesDir + '/test_key.pem', 'ascii');
+const modSize = 1024;
// Test signing and verifying
{
@@ -71,9 +74,203 @@ const keyPem = fs.readFileSync(common.fixturesDir + '/test_key.pem', 'ascii');
assert.strictEqual(verified, true, 'sign and verify (stream)');
}
+// Special tests for RSA_PKCS1_PSS_PADDING
+{
+ function testPSS(algo, hLen) {
+ // Maximum permissible salt length
+ const max = modSize / 8 - hLen - 2;
+
+ function getEffectiveSaltLength(saltLength) {
+ switch (saltLength) {
+ case crypto.constants.RSA_PSS_SALTLEN_DIGEST:
+ return hLen;
+ case crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN:
+ return max;
+ default:
+ return saltLength;
+ }
+ }
+
+ const signSaltLengths = [
+ crypto.constants.RSA_PSS_SALTLEN_DIGEST,
+ getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST),
+ crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN,
+ getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN),
+ 0, 16, 32, 64, 128
+ ];
+
+ const verifySaltLengths = [
+ crypto.constants.RSA_PSS_SALTLEN_DIGEST,
+ getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST),
+ getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN),
+ 0, 16, 32, 64, 128
+ ];
+
+ signSaltLengths.forEach((signSaltLength) => {
+ if (signSaltLength > max) {
+ // If the salt length is too big, an Error should be thrown
+ assert.throws(() => {
+ crypto.createSign(algo)
+ .update('Test123')
+ .sign({
+ key: keyPem,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: signSaltLength
+ });
+ }, /^Error:.*data too large for key size$/);
+ } else {
+ // Otherwise, a valid signature should be generated
+ const s4 = crypto.createSign(algo)
+ .update('Test123')
+ .sign({
+ key: keyPem,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: signSaltLength
+ });
+
+ let verified;
+ verifySaltLengths.forEach((verifySaltLength) => {
+ // Verification should succeed if and only if the salt length is
+ // correct
+ verified = crypto.createVerify(algo)
+ .update('Test123')
+ .verify({
+ key: certPem,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: verifySaltLength
+ }, s4);
+ const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) ==
+ getEffectiveSaltLength(verifySaltLength);
+ assert.strictEqual(verified, saltLengthCorrect, 'verify (PSS)');
+ });
+
+ // Verification using RSA_PSS_SALTLEN_AUTO should always work
+ verified = crypto.createVerify(algo)
+ .update('Test123')
+ .verify({
+ key: certPem,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
+ }, s4);
+ assert.strictEqual(verified, true, 'verify (PSS with SALTLEN_AUTO)');
+
+ // Verifying an incorrect message should never work
+ verified = crypto.createVerify(algo)
+ .update('Test1234')
+ .verify({
+ key: certPem,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
+ }, s4);
+ assert.strictEqual(verified, false, 'verify (PSS, incorrect)');
+ }
+ });
+ }
+
+ testPSS('RSA-SHA1', 20);
+ testPSS('RSA-SHA256', 32);
+}
+
+// Test vectors for RSA_PKCS1_PSS_PADDING provided by the RSA Laboratories:
+// https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm
+{
+ // We only test verification as we cannot specify explicit salts when signing
+ function testVerify(cert, vector) {
+ const verified = crypto.createVerify('RSA-SHA1')
+ .update(Buffer.from(vector.message, 'hex'))
+ .verify({
+ key: cert,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: vector.salt.length / 2
+ }, vector.signature, 'hex');
+ assert.strictEqual(verified, true, 'verify (PSS)');
+ }
+
+ const vectorfile = path.join(common.fixturesDir, 'pss-vectors.json');
+ const examples = JSON.parse(fs.readFileSync(vectorfile, {
+ encoding: 'utf8'
+ }));
+
+ for (const key in examples) {
+ const example = examples[key];
+ const publicKey = example.publicKey.join('\n');
+ example.tests.forEach((test) => testVerify(publicKey, test));
+ }
+}
+
+// Test exceptions for invalid `padding` and `saltLength` values
+{
+ [null, undefined, NaN, 'boom', {}, [], true, false]
+ .forEach((invalidValue) => {
+ assert.throws(() => {
+ crypto.createSign('RSA-SHA256')
+ .update('Test123')
+ .sign({
+ key: keyPem,
+ padding: invalidValue
+ });
+ }, /^TypeError: padding must be an integer$/);
+
+ assert.throws(() => {
+ crypto.createSign('RSA-SHA256')
+ .update('Test123')
+ .sign({
+ key: keyPem,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: invalidValue
+ });
+ }, /^TypeError: saltLength must be an integer$/);
+ });
+
+ assert.throws(() => {
+ crypto.createSign('RSA-SHA1')
+ .update('Test123')
+ .sign({
+ key: keyPem,
+ padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
+ });
+ }, /^Error:.*illegal or unsupported padding mode$/);
+}
+
// Test throws exception when key options is null
{
assert.throws(() => {
crypto.createSign('RSA-SHA1').update('Test123').sign(null, 'base64');
}, /^Error: No key provided to sign$/);
}
+
+// RSA-PSS Sign test by verifying with 'openssl dgst -verify'
+{
+ if (!common.opensslCli) {
+ common.skip('node compiled without OpenSSL CLI.');
+ return;
+ }
+
+ const pubfile = path.join(common.fixturesDir, 'keys/rsa_public_2048.pem');
+ const privfile = path.join(common.fixturesDir, 'keys/rsa_private_2048.pem');
+ const privkey = fs.readFileSync(privfile);
+
+ const msg = 'Test123';
+ const s5 = crypto.createSign('RSA-SHA256')
+ .update(msg)
+ .sign({
+ key: privkey,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING
+ });
+
+ common.refreshTmpDir();
+
+ const sigfile = path.join(common.tmpDir, 's5.sig');
+ fs.writeFileSync(sigfile, s5);
+ const msgfile = path.join(common.tmpDir, 's5.msg');
+ fs.writeFileSync(msgfile, msg);
+
+ const cmd = '"' + common.opensslCli + '" dgst -sha256 -verify "' + pubfile +
+ '" -signature "' + sigfile +
+ '" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 "' +
+ msgfile + '"';
+
+ exec(cmd, common.mustCall((err, stdout, stderr) => {
+ assert(stdout.includes('Verified OK'));
+ }));
+}