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')); + })); }