summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/crypto.md64
-rw-r--r--lib/crypto.js6
-rw-r--r--lib/internal/crypto/sig.js77
-rw-r--r--src/node_crypto.cc190
-rw-r--r--test/parallel/test-crypto-sign-verify.js270
5 files changed, 518 insertions, 89 deletions
diff --git a/doc/api/crypto.md b/doc/api/crypto.md
index 0d046be32b..35c15a7748 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)
+<!-- YAML
+added: REPLACEME
+-->
+* `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)
<!-- YAML
added: v6.6.0
@@ -2680,6 +2709,41 @@ Use of `crypto.timingSafeEqual` does not guarantee that the *surrounding* code
is timing-safe. Care should be taken to ensure that the surrounding code does
not introduce timing vulnerabilities.
+### crypto.verify(algorithm, data, key, signature)
+<!-- YAML
+added: REPLACEME
+-->
+* `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 673a198466..e80c7a8327 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 ed3693d6fe..9eacfec8c0 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 649ef9d5b4..9658c1d51a 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<Value>& args) {
args.GetReturnValue().Set(ret.signature.ToBuffer().ToLocalChecked());
}
+void SignOneShot(const FunctionCallbackInfo<Value>& 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<char> 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<Int32>()->Value();
+
+ CHECK(args[offset + 3]->IsInt32());
+ int rsa_salt_len = args[offset + 3].As<Int32>()->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<const unsigned char*>(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<unsigned char*>(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<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
@@ -4904,6 +4992,66 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(verify_result);
}
+void VerifyOneShot(const FunctionCallbackInfo<Value>& args) {
+ ClearErrorOnReturn clear_error_on_return;
+ Environment* env = Environment::GetCurrent(args);
+
+ unsigned int offset = 0;
+ ManagedEVPPKey key = GetPublicOrPrivateKeyFromJs(args, &offset);
+ if (!key)
+ return;
+
+ ArrayBufferViewContents<char> sig(args[offset]);
+
+ ArrayBufferViewContents<char> 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<Int32>()->Value();
+
+ CHECK(args[offset + 4]->IsInt32());
+ int rsa_salt_len = args[offset + 4].As<Int32>()->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<const unsigned char*>(sig.data()),
+ sig.length(),
+ reinterpret_cast<const unsigned char*>(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 <PublicKeyCipher::Operation operation,
PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
@@ -6577,6 +6725,8 @@ void Initialize(Local<Object> 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 e0b0d3fd7b..74de785c64 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'));
+ }));
}