From 6ebdb69472beaabe4d3aac7f66e1f83b196278af Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 9 Sep 2017 18:41:56 -0400 Subject: [PATCH] crypto: fix Node_SignFinal PR #11705 switched Node away from using using OpenSSL's legacy EVP_Sign* and EVP_Verify* APIs. Instead, it computes a hash normally via EVP_Digest* and then uses EVP_PKEY_sign and EVP_PKEY_verify to verify the hash directly. This change corrects two problems: 1. The documentation still recommends the signature algorithm EVP_MD names of OpenSSL's legacy APIs. OpenSSL has since moved away from thosee, which is why ECDSA was strangely inconsistent. (This is why "ecdsa-with-SHA256" was missing.) 2. Node_SignFinal copied some code from EVP_SignFinal's internals. This is problematic for OpenSSL 1.1.0 and is missing a critical check that prevents pkey->pkey.ptr from being cast to the wrong type. To resolve this, remove the non-EVP_PKEY_sign codepath. This codepath is no longer necessary. PR #11705's verify half was already assuming all EVP_PKEYs supported EVP_PKEY_sign and EVP_PKEY_verify. Also, in the documentation, point users towards using hash function names which are more consisent. This avoids an ECDSA special-case and some strangeness around RSA-PSS ("RSA-SHA256" is the OpenSSL name of the sha256WithRSAEncryption OID which is not used for RSA-PSS). PR-URL: https://github.com/nodejs/node/pull/15024 Reviewed-By: Shigeki Ohtsu Reviewed-By: Ruben Bridgewater --- .../crypto/rsa-sign-verify-throughput.js | 2 +- doc/api/crypto.md | 56 +++++++++---------- src/node_crypto.cc | 47 +++++++--------- test/fixtures/0-dns/create-cert.js | 4 +- test/parallel/test-crypto-binary-default.js | 24 ++++---- test/parallel/test-crypto-rsa-dsa.js | 46 +++++++++++---- test/parallel/test-crypto-sign-verify.js | 36 ++++++------ test/parallel/test-crypto-verify-failure.js | 2 +- test/parallel/test-crypto.js | 6 +- test/parallel/test-dsa-fips-invalid-key.js | 2 +- 10 files changed, 118 insertions(+), 107 deletions(-) diff --git a/benchmark/crypto/rsa-sign-verify-throughput.js b/benchmark/crypto/rsa-sign-verify-throughput.js index f13dc2585a7..f912bf41338 100644 --- a/benchmark/crypto/rsa-sign-verify-throughput.js +++ b/benchmark/crypto/rsa-sign-verify-throughput.js @@ -18,7 +18,7 @@ keylen_list.forEach(function(key) { var bench = common.createBenchmark(main, { writes: [500], - algo: ['RSA-SHA1', 'RSA-SHA224', 'RSA-SHA256', 'RSA-SHA384', 'RSA-SHA512'], + algo: ['SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512'], keylen: keylen_list, len: [1024, 102400, 2 * 102400, 3 * 102400, 1024 * 1024] }); diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 08d14e96975..995009b2902 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -918,25 +918,46 @@ of two ways: - Using the [`sign.update()`][] and [`sign.sign()`][] methods to produce the signature. -The [`crypto.createSign()`][] method is used to create `Sign` instances. `Sign` -objects are not to be created directly using the `new` keyword. +The [`crypto.createSign()`][] method is used to create `Sign` instances. The +argument is the string name of the hash function to use. `Sign` objects are not +to be created directly using the `new` keyword. Example: Using `Sign` objects as streams: ```js const crypto = require('crypto'); -const sign = crypto.createSign('RSA-SHA256'); +const sign = crypto.createSign('SHA256'); sign.write('some data to sign'); sign.end(); const privateKey = getPrivateKeySomehow(); console.log(sign.sign(privateKey, 'hex')); -// Prints: the calculated signature +// Prints: the calculated signature using the specified private key and +// SHA-256. For RSA keys, the algorithm is RSASSA-PKCS1-v1_5 (see padding +// parameter below for RSASSA-PSS). For EC keys, the algorithm is ECDSA. ``` Example: Using the [`sign.update()`][] and [`sign.sign()`][] methods: +```js +const crypto = require('crypto'); +const sign = crypto.createSign('SHA256'); + +sign.update('some data to sign'); + +const privateKey = getPrivateKeySomehow(); +console.log(sign.sign(privateKey, 'hex')); +// Prints: the calculated signature +``` + +In some cases, a `Sign` instance can also be created by passing in a signature +algorithm name, such as 'RSA-SHA256'. This will use the corresponding digest +algorithm. This does not work for all signature algorithms, such as +'ecdsa-with-SHA256'. Use digest names instead. + +Example: signing using legacy signature algorithm name + ```js const crypto = require('crypto'); const sign = crypto.createSign('RSA-SHA256'); @@ -948,29 +969,6 @@ console.log(sign.sign(privateKey, 'hex')); // Prints: the calculated signature ``` -A `Sign` instance can also be created by just passing in the digest -algorithm name, in which case OpenSSL will infer the full signature algorithm -from the type of the PEM-formatted private key, including algorithms that -do not have directly exposed name constants, e.g. 'ecdsa-with-SHA256'. - -Example: signing using ECDSA with SHA256 - -```js -const crypto = require('crypto'); -const sign = crypto.createSign('sha256'); - -sign.update('some data to sign'); - -const privateKey = -`-----BEGIN EC PRIVATE KEY----- -MHcCAQEEIF+jnWY1D5kbVYDNvxxo/Y+ku2uJPDwS0r/VuPZQrjjVoAoGCCqGSM49 -AwEHoUQDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNhB8i3mXyIMq704m2m52FdfKZ2 -pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng== ------END EC PRIVATE KEY-----`; - -console.log(sign.sign(privateKey).toString('hex')); -``` - ### sign.sign(privateKey[, outputFormat])