From 7d0e50dcfef98ca56715adf74678bcaf4aa08796 Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 12 Mar 2019 09:17:10 -0400 Subject: [PATCH] crypto: add crypto.sign() and crypto.verify() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These methods are added primarily to allow signing and verifying using Ed25519 and Ed448 keys, which do not support streaming of input data. However, any key type can be used with these new APIs, to allow better performance when only signing/verifying a single chunk. Fixes: https://github.com/nodejs/node/issues/26320 PR-URL: https://github.com/nodejs/node/pull/26611 Reviewed-By: James M Snell Reviewed-By: Rod Vagg Reviewed-By: Sam Roberts Reviewed-By: Tobias Nießen --- doc/api/crypto.md | 64 ++++++ lib/crypto.js | 6 +- lib/internal/crypto/sig.js | 77 ++++++- src/node_crypto.cc | 190 ++++++++++++++-- test/parallel/test-crypto-sign-verify.js | 270 +++++++++++++++++------ 5 files changed, 518 insertions(+), 89 deletions(-) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 0d046be32b6361..35c15a7748ecc5 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -2659,6 +2659,35 @@ added: v10.0.0 Enables the FIPS compliant crypto provider in a FIPS-enabled Node.js build. Throws an error if FIPS mode is not available. +### crypto.sign(algorithm, data, key) + +* `algorithm` {string | null | undefined} +* `data` {Buffer | TypedArray | DataView} +* `key` {Object | string | Buffer | KeyObject} +* Returns: {Buffer} + +Calculates and returns the signature for `data` using the given private key and +algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is +dependent upon the key type (especially Ed25519 and Ed448). + +If `key` is not a [`KeyObject`][], this function behaves as if `key` had been +passed to [`crypto.createPrivateKey()`][]. If it is an object, the following +additional properties can be passed: + +* `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. + ### crypto.timingSafeEqual(a, b) +* `algorithm` {string | null | undefined} +* `data` {Buffer | TypedArray | DataView} +* `key` {Object | string | Buffer | KeyObject} +* `signature` {Buffer | TypedArray | DataView} +* Returns: {boolean} + +Verifies the given signature for `data` using the given key and algorithm. If +`algorithm` is `null` or `undefined`, then the algorithm is dependent upon the +key type (especially Ed25519 and Ed448). + +If `key` is not a [`KeyObject`][], this function behaves as if `key` had been +passed to [`crypto.createPublicKey()`][]. If it is an object, the following +additional properties can be passed: + +* `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 `signature` argument is the previously calculated signature for the `data`. + +Because public keys can be derived from private keys, a private key or a public +key may be passed for `key`. + ## Notes ### Legacy Streams API (pre Node.js v0.10) diff --git a/lib/crypto.js b/lib/crypto.js index 673a198466ec5c..e80c7a8327df98 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -80,7 +80,9 @@ const { } = require('internal/crypto/cipher'); const { Sign, - Verify + signOneShot, + Verify, + verifyOneShot } = require('internal/crypto/sig'); const { Hash, @@ -174,12 +176,14 @@ module.exports = exports = { randomFillSync, scrypt, scryptSync, + sign: signOneShot, setEngine, timingSafeEqual, getFips: !fipsMode ? getFipsDisabled : fipsForced ? getFipsForced : getFipsCrypto, setFips: !fipsMode ? setFipsDisabled : fipsForced ? setFipsForced : setFipsCrypto, + verify: verifyOneShot, // Classes Certificate, diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js index ed3693d6fe90e8..9eacfec8c0b74a 100644 --- a/lib/internal/crypto/sig.js +++ b/lib/internal/crypto/sig.js @@ -2,10 +2,16 @@ const { ERR_CRYPTO_SIGN_KEY_REQUIRED, + ERR_INVALID_ARG_TYPE, ERR_INVALID_OPT_VALUE } = require('internal/errors').codes; const { validateString } = require('internal/validators'); -const { Sign: _Sign, Verify: _Verify } = internalBinding('crypto'); +const { + Sign: _Sign, + Verify: _Verify, + signOneShot: _signOneShot, + verifyOneShot: _verifyOneShot +} = internalBinding('crypto'); const { RSA_PSS_SALTLEN_AUTO, RSA_PKCS1_PADDING @@ -22,6 +28,7 @@ const { preparePublicOrPrivateKey } = require('internal/crypto/keys'); const { Writable } = require('stream'); +const { isArrayBufferView } = require('internal/util/types'); function Sign(algorithm, options) { if (!(this instanceof Sign)) @@ -91,6 +98,35 @@ Sign.prototype.sign = function sign(options, encoding) { return ret; }; +function signOneShot(algorithm, data, key) { + if (algorithm != null) + validateString(algorithm, 'algorithm'); + + if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', + ['Buffer', 'TypedArray', 'DataView'], + data + ); + } + + if (!key) + throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); + + const { + data: keyData, + format: keyFormat, + type: keyType, + passphrase: keyPassphrase + } = preparePrivateKey(key); + + // Options specific to RSA + const rsaPadding = getPadding(key); + const pssSaltLength = getSaltLength(key); + + return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data, + algorithm, rsaPadding, pssSaltLength); +} function Verify(algorithm, options) { if (!(this instanceof Verify)) @@ -132,7 +168,44 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) { legacyNativeHandle(Verify); +function verifyOneShot(algorithm, data, key, signature) { + if (algorithm != null) + validateString(algorithm, 'algorithm'); + + if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', + ['Buffer', 'TypedArray', 'DataView'], + data + ); + } + + const { + data: keyData, + format: keyFormat, + type: keyType, + passphrase: keyPassphrase + } = preparePublicOrPrivateKey(key); + + // Options specific to RSA + const rsaPadding = getPadding(key); + const pssSaltLength = getSaltLength(key); + + if (!isArrayBufferView(signature)) { + throw new ERR_INVALID_ARG_TYPE( + 'signature', + ['Buffer', 'TypedArray', 'DataView'], + signature + ); + } + + return _verifyOneShot(keyData, keyFormat, keyType, keyPassphrase, signature, + data, algorithm, rsaPadding, pssSaltLength); +} + module.exports = { Sign, - Verify + signOneShot, + Verify, + verifyOneShot }; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 649ef9d5b4061e..9658c1d51a208b 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -4598,43 +4598,47 @@ SignBase::Error SignBase::Update(const char* data, int len) { } -void SignBase::CheckThrow(SignBase::Error error) { - HandleScope scope(env()->isolate()); +void CheckThrow(Environment* env, SignBase::Error error) { + HandleScope scope(env->isolate()); switch (error) { - case kSignUnknownDigest: - return env()->ThrowError("Unknown message digest"); + case SignBase::Error::kSignUnknownDigest: + return env->ThrowError("Unknown message digest"); - case kSignNotInitialised: - return env()->ThrowError("Not initialised"); + case SignBase::Error::kSignNotInitialised: + return env->ThrowError("Not initialised"); - case kSignInit: - case kSignUpdate: - case kSignPrivateKey: - case kSignPublicKey: + case SignBase::Error::kSignInit: + case SignBase::Error::kSignUpdate: + case SignBase::Error::kSignPrivateKey: + case SignBase::Error::kSignPublicKey: { unsigned long err = ERR_get_error(); // NOLINT(runtime/int) if (err) - return ThrowCryptoError(env(), err); + return ThrowCryptoError(env, err); switch (error) { - case kSignInit: - return env()->ThrowError("EVP_SignInit_ex failed"); - case kSignUpdate: - return env()->ThrowError("EVP_SignUpdate failed"); - case kSignPrivateKey: - return env()->ThrowError("PEM_read_bio_PrivateKey failed"); - case kSignPublicKey: - return env()->ThrowError("PEM_read_bio_PUBKEY failed"); + case SignBase::Error::kSignInit: + return env->ThrowError("EVP_SignInit_ex failed"); + case SignBase::Error::kSignUpdate: + return env->ThrowError("EVP_SignUpdate failed"); + case SignBase::Error::kSignPrivateKey: + return env->ThrowError("PEM_read_bio_PrivateKey failed"); + case SignBase::Error::kSignPublicKey: + return env->ThrowError("PEM_read_bio_PUBKEY failed"); default: ABORT(); } } - case kSignOk: + case SignBase::Error::kSignOk: return; } } +void SignBase::CheckThrow(SignBase::Error error) { + node::crypto::CheckThrow(env(), error); +} + static bool ApplyRSAOptions(const ManagedEVPPKey& pkey, EVP_PKEY_CTX* pkctx, int padding, @@ -4800,6 +4804,90 @@ void Sign::SignFinal(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(ret.signature.ToBuffer().ToLocalChecked()); } +void SignOneShot(const FunctionCallbackInfo& args) { + ClearErrorOnReturn clear_error_on_return; + Environment* env = Environment::GetCurrent(args); + + unsigned int offset = 0; + ManagedEVPPKey key = GetPrivateKeyFromJs(args, &offset, true); + if (!key) + return; + +#ifdef NODE_FIPS_MODE + /* Validate DSA2 parameters from FIPS 186-4 */ + if (FIPS_mode() && EVP_PKEY_DSA == EVP_PKEY_base_id(key.get())) { + DSA* dsa = EVP_PKEY_get0_DSA(key.get()); + const BIGNUM* p; + DSA_get0_pqg(dsa, &p, nullptr, nullptr); + size_t L = BN_num_bits(p); + const BIGNUM* q; + DSA_get0_pqg(dsa, nullptr, &q, nullptr); + size_t N = BN_num_bits(q); + bool result = false; + + if (L == 1024 && N == 160) + result = true; + else if (L == 2048 && N == 224) + result = true; + else if (L == 2048 && N == 256) + result = true; + else if (L == 3072 && N == 256) + result = true; + + if (!result) { + return CheckThrow(env, SignBase::Error::kSignPrivateKey); + } + } +#endif // NODE_FIPS_MODE + + ArrayBufferViewContents data(args[offset]); + + const EVP_MD* md; + if (args[offset + 1]->IsNullOrUndefined()) { + md = nullptr; + } else { + const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 1]); + md = EVP_get_digestbyname(*sign_type); + if (md == nullptr) + return CheckThrow(env, SignBase::Error::kSignUnknownDigest); + } + + CHECK(args[offset + 2]->IsInt32()); + int rsa_padding = args[offset + 2].As()->Value(); + + CHECK(args[offset + 3]->IsInt32()); + int rsa_salt_len = args[offset + 3].As()->Value(); + + EVP_PKEY_CTX* pkctx = nullptr; + EVPMDPointer mdctx(EVP_MD_CTX_new()); + if (!mdctx || + !EVP_DigestSignInit(mdctx.get(), &pkctx, md, nullptr, key.get())) { + return CheckThrow(env, SignBase::Error::kSignInit); + } + + if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len)) + return CheckThrow(env, SignBase::Error::kSignPrivateKey); + + const unsigned char* input = + reinterpret_cast(data.data()); + size_t sig_len; + if (!EVP_DigestSign(mdctx.get(), nullptr, &sig_len, input, data.length())) + return CheckThrow(env, SignBase::Error::kSignPrivateKey); + + AllocatedBuffer signature = env->AllocateManaged(sig_len); + if (!EVP_DigestSign(mdctx.get(), + reinterpret_cast(signature.data()), + &sig_len, + input, + data.length())) { + return CheckThrow(env, SignBase::Error::kSignPrivateKey); + } + + signature.Resize(sig_len); + + args.GetReturnValue().Set(signature.ToBuffer().ToLocalChecked()); +} + void Verify::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -4904,6 +4992,66 @@ void Verify::VerifyFinal(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(verify_result); } +void VerifyOneShot(const FunctionCallbackInfo& args) { + ClearErrorOnReturn clear_error_on_return; + Environment* env = Environment::GetCurrent(args); + + unsigned int offset = 0; + ManagedEVPPKey key = GetPublicOrPrivateKeyFromJs(args, &offset); + if (!key) + return; + + ArrayBufferViewContents sig(args[offset]); + + ArrayBufferViewContents data(args[offset + 1]); + + const EVP_MD* md; + if (args[offset + 2]->IsNullOrUndefined()) { + md = nullptr; + } else { + const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 2]); + md = EVP_get_digestbyname(*sign_type); + if (md == nullptr) + return CheckThrow(env, SignBase::Error::kSignUnknownDigest); + } + + CHECK(args[offset + 3]->IsInt32()); + int rsa_padding = args[offset + 3].As()->Value(); + + CHECK(args[offset + 4]->IsInt32()); + int rsa_salt_len = args[offset + 4].As()->Value(); + + EVP_PKEY_CTX* pkctx = nullptr; + EVPMDPointer mdctx(EVP_MD_CTX_new()); + if (!mdctx || + !EVP_DigestVerifyInit(mdctx.get(), &pkctx, md, nullptr, key.get())) { + return CheckThrow(env, SignBase::Error::kSignInit); + } + + if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len)) + return CheckThrow(env, SignBase::Error::kSignPublicKey); + + bool verify_result; + const int r = EVP_DigestVerify( + mdctx.get(), + reinterpret_cast(sig.data()), + sig.length(), + reinterpret_cast(data.data()), + data.length()); + switch (r) { + case 1: + verify_result = true; + break; + case 0: + verify_result = false; + break; + default: + return CheckThrow(env, SignBase::Error::kSignPublicKey); + } + + args.GetReturnValue().Set(verify_result); +} + template @@ -6577,6 +6725,8 @@ void Initialize(Local target, NODE_DEFINE_CONSTANT(target, kKeyTypePublic); NODE_DEFINE_CONSTANT(target, kKeyTypePrivate); env->SetMethod(target, "randomBytes", RandomBytes); + env->SetMethod(target, "signOneShot", SignOneShot); + env->SetMethod(target, "verifyOneShot", VerifyOneShot); env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual); env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers); env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers); diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js index e0b0d3fd7bf656..74de785c6453e0 100644 --- a/test/parallel/test-crypto-sign-verify.js +++ b/test/parallel/test-crypto-sign-verify.js @@ -142,63 +142,95 @@ common.expectsError( ]; const errMessage = /^Error:.*data too large for key size$/; + const data = Buffer.from('Test123'); + 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') + .update(data) .sign({ key: keyPem, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: signSaltLength }); }, errMessage); + assert.throws(() => { + crypto.sign(algo, data, { + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + }); + }, errMessage); } else { // Otherwise, a valid signature should be generated const s4 = crypto.createSign(algo) - .update('Test123') + .update(data) .sign({ key: keyPem, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: signSaltLength }); + const s4_2 = crypto.sign(algo, data, { + 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 + [s4, s4_2].forEach((sig) => { + let verified; + verifySaltLengths.forEach((verifySaltLength) => { + // Verification should succeed if and only if the salt length is + // correct + verified = crypto.createVerify(algo) + .update(data) + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: verifySaltLength + }, sig); + assert.strictEqual(verified, crypto.verify(algo, data, { + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: verifySaltLength + }, sig)); + const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) === + getEffectiveSaltLength(verifySaltLength); + assert.strictEqual(verified, saltLengthCorrect); + }); + + // Verification using RSA_PSS_SALTLEN_AUTO should always work verified = crypto.createVerify(algo) - .update('Test123') + .update(data) .verify({ key: certPem, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, - saltLength: verifySaltLength - }, s4); - const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) === - getEffectiveSaltLength(verifySaltLength); - assert.strictEqual(verified, saltLengthCorrect); - }); + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig); + assert.strictEqual(verified, true); + assert.strictEqual(verified, crypto.verify(algo, data, { + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig)); - // 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); - - // 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); + // Verifying an incorrect message should never work + const wrongData = Buffer.from('Test1234'); + verified = crypto.createVerify(algo) + .update(wrongData) + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig); + assert.strictEqual(verified, false); + assert.strictEqual(verified, crypto.verify(algo, wrongData, { + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig)); + }); } }); } @@ -281,40 +313,6 @@ common.expectsError( }); } -// RSA-PSS Sign test by verifying with 'openssl dgst -verify' -{ - if (!common.opensslCli) - common.skip('node compiled without OpenSSL CLI.'); - - const pubfile = fixtures.path('keys', 'rsa_public_2048.pem'); - const privkey = fixtures.readKey('rsa_private_2048.pem'); - - const msg = 'Test123'; - const s5 = crypto.createSign('SHA256') - .update(msg) - .sign({ - key: privkey, - padding: crypto.constants.RSA_PKCS1_PSS_PADDING - }); - - const tmpdir = require('../common/tmpdir'); - tmpdir.refresh(); - - const sigfile = path.join(tmpdir.path, 's5.sig'); - fs.writeFileSync(sigfile, s5); - const msgfile = path.join(tmpdir.path, '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')); - })); -} - { const sign = crypto.createSign('SHA1'); const verify = crypto.createVerify('SHA1'); @@ -368,4 +366,144 @@ common.expectsError( assert.throws( () => crypto.createSign('sha8'), /Unknown message digest/); + assert.throws( + () => crypto.sign('sha8', Buffer.alloc(1), keyPem), + /Unknown message digest/); +} + +[ + { private: fixtures.readSync('test_ed25519_privkey.pem', 'ascii'), + public: fixtures.readSync('test_ed25519_pubkey.pem', 'ascii'), + algo: null, + sigLen: 64 }, + { private: fixtures.readSync('test_ed448_privkey.pem', 'ascii'), + public: fixtures.readSync('test_ed448_pubkey.pem', 'ascii'), + algo: null, + sigLen: 114 }, + { private: fixtures.readKey('rsa_private_2048.pem', 'ascii'), + public: fixtures.readKey('rsa_public_2048.pem', 'ascii'), + algo: 'sha1', + sigLen: 256 } +].forEach((pair) => { + const algo = pair.algo; + + { + const data = Buffer.from('Hello world'); + const sig = crypto.sign(algo, data, pair.private); + assert.strictEqual(sig.length, pair.sigLen); + + assert.strictEqual(crypto.verify(algo, data, pair.private, sig), + true); + assert.strictEqual(crypto.verify(algo, data, pair.public, sig), + true); + } + + { + const data = Buffer.from('Hello world'); + const privKeyObj = crypto.createPrivateKey(pair.private); + const pubKeyObj = crypto.createPublicKey(pair.public); + + const sig = crypto.sign(algo, data, privKeyObj); + assert.strictEqual(sig.length, pair.sigLen); + + assert.strictEqual(crypto.verify(algo, data, privKeyObj, sig), true); + assert.strictEqual(crypto.verify(algo, data, pubKeyObj, sig), true); + } + + { + const data = Buffer.from('Hello world'); + const otherData = Buffer.from('Goodbye world'); + const otherSig = crypto.sign(algo, otherData, pair.private); + assert.strictEqual(crypto.verify(algo, data, pair.private, otherSig), + false); + } + + [ + Uint8Array, Uint16Array, Uint32Array, Float32Array, Float64Array + ].forEach((clazz) => { + const data = new clazz(); + const sig = crypto.sign(algo, data, pair.private); + assert.strictEqual(crypto.verify(algo, data, pair.private, sig), + true); + }); +}); + +[1, {}, [], true, Infinity].forEach((input) => { + const data = Buffer.alloc(1); + const sig = Buffer.alloc(1); + const type = typeof input; + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "data" argument must be one of type Buffer, ' + + `TypedArray, or DataView. Received type ${type}` + }; + + assert.throws(() => crypto.sign(null, input, 'asdf'), errObj); + assert.throws(() => crypto.verify(null, input, 'asdf', sig), errObj); + + errObj.message = 'The "key" argument must be one of type string, Buffer, ' + + `TypedArray, DataView, or KeyObject. Received type ${type}`; + + assert.throws(() => crypto.sign(null, data, input), errObj); + assert.throws(() => crypto.verify(null, data, input, sig), errObj); + + errObj.message = 'The "signature" argument must be one of type ' + + `Buffer, TypedArray, or DataView. Received type ${type}`; + assert.throws(() => crypto.verify(null, data, 'test', input), errObj); +}); + +{ + const privKey = fixtures.readKey('ec-key.pem'); + const data = Buffer.from('Hello world'); + [ + crypto.createSign('sha1').update(data).sign(privKey), + crypto.sign('sha1', data, privKey) + ].forEach((sig) => { + // Signature length variability due to DER encoding + assert.strictEqual(sig.length >= 68, true); + + assert.strictEqual( + crypto.createVerify('sha1').update(data).verify(privKey, sig), + true + ); + assert.strictEqual(crypto.verify('sha1', data, privKey, sig), true); + }); +} + + +// RSA-PSS Sign test by verifying with 'openssl dgst -verify' +// Note: this particular test *must* be the last in this file as it will exit +// early if no openssl binary is found +{ + if (!common.opensslCli) + common.skip('node compiled without OpenSSL CLI.'); + + const pubfile = fixtures.path('keys', 'rsa_public_2048.pem'); + const privkey = fixtures.readKey('rsa_private_2048.pem'); + + const msg = 'Test123'; + const s5 = crypto.createSign('SHA256') + .update(msg) + .sign({ + key: privkey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING + }); + + const tmpdir = require('../common/tmpdir'); + tmpdir.refresh(); + + const sigfile = path.join(tmpdir.path, 's5.sig'); + fs.writeFileSync(sigfile, s5); + const msgfile = path.join(tmpdir.path, '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')); + })); }