diff options
-rw-r--r-- | doc/api/crypto.md | 14 | ||||
-rw-r--r-- | lib/internal/crypto/keys.js | 10 | ||||
-rw-r--r-- | src/crypto/crypto_keys.cc | 50 | ||||
-rw-r--r-- | src/crypto/crypto_keys.h | 1 | ||||
-rw-r--r-- | test/parallel/test-crypto-key-objects.js | 49 |
5 files changed, 124 insertions, 0 deletions
diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 470c8f66418..8025e0f9182 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -2082,6 +2082,20 @@ encryption mechanism, PEM-level encryption is not supported when encrypting a PKCS#8 key. See [RFC 5208][] for PKCS#8 encryption and [RFC 1421][] for PKCS#1 and SEC1 encryption. +### `keyObject.equals(otherKeyObject)` + +<!-- YAML +added: REPLACEME +--> + +* `otherKeyObject`: {KeyObject} A `KeyObject` with which to + compare `keyObject`. +* Returns: {boolean} + +Returns `true` or `false` depending on whether the keys have exactly the same +type, value, and parameters. This method is not +[constant time](https://en.wikipedia.org/wiki/Timing_attack). + ### `keyObject.symmetricKeySize` <!-- YAML diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index ea2ecc262ed..5c9f1fd0926 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -124,6 +124,16 @@ const { throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key); return key[kKeyObject]; } + + equals(otherKeyObject) { + if (!isKeyObject(otherKeyObject)) { + throw new ERR_INVALID_ARG_TYPE( + 'otherKeyObject', 'KeyObject', otherKeyObject); + } + + return otherKeyObject.type === this.type && + this[kHandle].equals(otherKeyObject[kHandle]); + } } class SecretKeyObject extends KeyObject { diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index c5b3e6b3718..08b95190698 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -921,6 +921,7 @@ v8::Local<v8::Function> KeyObjectHandle::Initialize(Environment* env) { env->SetProtoMethod(t, "initEDRaw", InitEDRaw); env->SetProtoMethod(t, "initJwk", InitJWK); env->SetProtoMethod(t, "keyDetail", GetKeyDetail); + env->SetProtoMethod(t, "equals", Equals); auto function = t->GetFunction(env->context()).ToLocalChecked(); env->set_crypto_key_object_handle_constructor(function); @@ -939,6 +940,7 @@ void KeyObjectHandle::RegisterExternalReferences( registry->Register(InitEDRaw); registry->Register(InitJWK); registry->Register(GetKeyDetail); + registry->Register(Equals); } MaybeLocal<Object> KeyObjectHandle::Create( @@ -1134,6 +1136,54 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(true); } +void KeyObjectHandle::Equals(const FunctionCallbackInfo<Value>& args) { + KeyObjectHandle* self_handle; + KeyObjectHandle* arg_handle; + ASSIGN_OR_RETURN_UNWRAP(&self_handle, args.Holder()); + ASSIGN_OR_RETURN_UNWRAP(&arg_handle, args[0].As<Object>()); + std::shared_ptr<KeyObjectData> key = self_handle->Data(); + std::shared_ptr<KeyObjectData> key2 = arg_handle->Data(); + + KeyType key_type = key->GetKeyType(); + CHECK_EQ(key_type, key2->GetKeyType()); + + bool ret; + switch (key_type) { + case kKeyTypeSecret: { + size_t size = key->GetSymmetricKeySize(); + if (size == key2->GetSymmetricKeySize()) { + ret = CRYPTO_memcmp( + key->GetSymmetricKey(), + key2->GetSymmetricKey(), + size) == 0; + } else { + ret = false; + } + break; + } + case kKeyTypePublic: + case kKeyTypePrivate: { + EVP_PKEY* pkey = key->GetAsymmetricKey().get(); + EVP_PKEY* pkey2 = key2->GetAsymmetricKey().get(); +#if OPENSSL_VERSION_MAJOR >= 3 + int ok = EVP_PKEY_eq(pkey, pkey2); +#else + int ok = EVP_PKEY_cmp(pkey, pkey2); +#endif + if (ok == -2) { + Environment* env = Environment::GetCurrent(args); + return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); + } + ret = ok == 1; + break; + } + default: + UNREACHABLE("unsupported key type"); + } + + args.GetReturnValue().Set(ret); +} + void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 48d38cd20b0..d06565d8ad7 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -189,6 +189,7 @@ class KeyObjectHandle : public BaseObject { static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args); static void InitJWK(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetKeyDetail(const v8::FunctionCallbackInfo<v8::Value>& args); + static void Equals(const v8::FunctionCallbackInfo<v8::Value>& args); static void ExportJWK(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index c564c4dcb43..e543ff730dc 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -21,6 +21,7 @@ const { privateDecrypt, privateEncrypt, getCurves, + generateKeySync, generateKeyPairSync, webcrypto, } = require('crypto'); @@ -846,3 +847,51 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', assert(!isKeyObject(cryptoKey)); }); } + +{ + const first = Buffer.from('Hello'); + const second = Buffer.from('World'); + const keyObject = createSecretKey(first); + assert(createSecretKey(first).equals(createSecretKey(first))); + assert(!createSecretKey(first).equals(createSecretKey(second))); + + assert.throws(() => keyObject.equals(0), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "otherKeyObject" argument must be an instance of KeyObject. Received type number (0)' + }); + + assert(keyObject.equals(keyObject)); + assert(!keyObject.equals(createPublicKey(publicPem))); + assert(!keyObject.equals(createPrivateKey(privatePem))); +} + +{ + const first = generateKeyPairSync('ed25519'); + const second = generateKeyPairSync('ed25519'); + const secret = generateKeySync('aes', { length: 128 }); + + assert(first.publicKey.equals(first.publicKey)); + assert(first.publicKey.equals(createPublicKey( + first.publicKey.export({ format: 'pem', type: 'spki' })))); + assert(!first.publicKey.equals(second.publicKey)); + assert(!first.publicKey.equals(second.privateKey)); + assert(!first.publicKey.equals(secret)); + + assert(first.privateKey.equals(first.privateKey)); + assert(first.privateKey.equals(createPrivateKey( + first.privateKey.export({ format: 'pem', type: 'pkcs8' })))); + assert(!first.privateKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.publicKey)); + assert(!first.privateKey.equals(secret)); +} + +{ + const first = generateKeyPairSync('ed25519'); + const second = generateKeyPairSync('ed448'); + + assert(!first.publicKey.equals(second.publicKey)); + assert(!first.publicKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.publicKey)); +} |