summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian White <mscdex@mscdex.net>2019-03-12 09:17:10 -0400
committerBrian White <mscdex@mscdex.net>2019-03-28 21:57:53 -0400
commit7d0e50dcfef98ca56715adf74678bcaf4aa08796 (patch)
tree2458b8db07d8968ea1e35384f8160664ccaac36c
parent36e5fd2915e1ff9a8f0944b1a7783760fab38654 (diff)
downloadandroid-node-v8-7d0e50dcfef98ca56715adf74678bcaf4aa08796.tar.gz
android-node-v8-7d0e50dcfef98ca56715adf74678bcaf4aa08796.tar.bz2
android-node-v8-7d0e50dcfef98ca56715adf74678bcaf4aa08796.zip
crypto: add crypto.sign() and crypto.verify()
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 <jasnell@gmail.com> Reviewed-By: Rod Vagg <rod@vagg.org> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
-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'));
+ }));
}