Skip to content

Commit 2b35422

Browse files
panvasxa
authored andcommitted
crypto: add KeyObject.prototype.equals method
PR-URL: #42093 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent 384872f commit 2b35422

File tree

5 files changed

+124
-0
lines changed

5 files changed

+124
-0
lines changed

doc/api/crypto.md

+14
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,20 @@ encryption mechanism, PEM-level encryption is not supported when encrypting
20822082
a PKCS#8 key. See [RFC 5208][] for PKCS#8 encryption and [RFC 1421][] for
20832083
PKCS#1 and SEC1 encryption.
20842084

2085+
### `keyObject.equals(otherKeyObject)`
2086+
2087+
<!-- YAML
2088+
added: REPLACEME
2089+
-->
2090+
2091+
* `otherKeyObject`: {KeyObject} A `KeyObject` with which to
2092+
compare `keyObject`.
2093+
* Returns: {boolean}
2094+
2095+
Returns `true` or `false` depending on whether the keys have exactly the same
2096+
type, value, and parameters. This method is not
2097+
[constant time](https://en.wikipedia.org/wiki/Timing_attack).
2098+
20852099
### `keyObject.symmetricKeySize`
20862100

20872101
<!-- YAML

lib/internal/crypto/keys.js

+10
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ const {
124124
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
125125
return key[kKeyObject];
126126
}
127+
128+
equals(otherKeyObject) {
129+
if (!isKeyObject(otherKeyObject)) {
130+
throw new ERR_INVALID_ARG_TYPE(
131+
'otherKeyObject', 'KeyObject', otherKeyObject);
132+
}
133+
134+
return otherKeyObject.type === this.type &&
135+
this[kHandle].equals(otherKeyObject[kHandle]);
136+
}
127137
}
128138

129139
class SecretKeyObject extends KeyObject {

src/crypto/crypto_keys.cc

+50
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,7 @@ v8::Local<v8::Function> KeyObjectHandle::Initialize(Environment* env) {
921921
env->SetProtoMethod(t, "initEDRaw", InitEDRaw);
922922
env->SetProtoMethod(t, "initJwk", InitJWK);
923923
env->SetProtoMethod(t, "keyDetail", GetKeyDetail);
924+
env->SetProtoMethod(t, "equals", Equals);
924925

925926
auto function = t->GetFunction(env->context()).ToLocalChecked();
926927
env->set_crypto_key_object_handle_constructor(function);
@@ -939,6 +940,7 @@ void KeyObjectHandle::RegisterExternalReferences(
939940
registry->Register(InitEDRaw);
940941
registry->Register(InitJWK);
941942
registry->Register(GetKeyDetail);
943+
registry->Register(Equals);
942944
}
943945

944946
MaybeLocal<Object> KeyObjectHandle::Create(
@@ -1134,6 +1136,54 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) {
11341136
args.GetReturnValue().Set(true);
11351137
}
11361138

1139+
void KeyObjectHandle::Equals(const FunctionCallbackInfo<Value>& args) {
1140+
KeyObjectHandle* self_handle;
1141+
KeyObjectHandle* arg_handle;
1142+
ASSIGN_OR_RETURN_UNWRAP(&self_handle, args.Holder());
1143+
ASSIGN_OR_RETURN_UNWRAP(&arg_handle, args[0].As<Object>());
1144+
std::shared_ptr<KeyObjectData> key = self_handle->Data();
1145+
std::shared_ptr<KeyObjectData> key2 = arg_handle->Data();
1146+
1147+
KeyType key_type = key->GetKeyType();
1148+
CHECK_EQ(key_type, key2->GetKeyType());
1149+
1150+
bool ret;
1151+
switch (key_type) {
1152+
case kKeyTypeSecret: {
1153+
size_t size = key->GetSymmetricKeySize();
1154+
if (size == key2->GetSymmetricKeySize()) {
1155+
ret = CRYPTO_memcmp(
1156+
key->GetSymmetricKey(),
1157+
key2->GetSymmetricKey(),
1158+
size) == 0;
1159+
} else {
1160+
ret = false;
1161+
}
1162+
break;
1163+
}
1164+
case kKeyTypePublic:
1165+
case kKeyTypePrivate: {
1166+
EVP_PKEY* pkey = key->GetAsymmetricKey().get();
1167+
EVP_PKEY* pkey2 = key2->GetAsymmetricKey().get();
1168+
#if OPENSSL_VERSION_MAJOR >= 3
1169+
int ok = EVP_PKEY_eq(pkey, pkey2);
1170+
#else
1171+
int ok = EVP_PKEY_cmp(pkey, pkey2);
1172+
#endif
1173+
if (ok == -2) {
1174+
Environment* env = Environment::GetCurrent(args);
1175+
return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
1176+
}
1177+
ret = ok == 1;
1178+
break;
1179+
}
1180+
default:
1181+
UNREACHABLE("unsupported key type");
1182+
}
1183+
1184+
args.GetReturnValue().Set(ret);
1185+
}
1186+
11371187
void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo<Value>& args) {
11381188
Environment* env = Environment::GetCurrent(args);
11391189
KeyObjectHandle* key;

src/crypto/crypto_keys.h

+1
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ class KeyObjectHandle : public BaseObject {
189189
static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
190190
static void InitJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
191191
static void GetKeyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
192+
static void Equals(const v8::FunctionCallbackInfo<v8::Value>& args);
192193

193194
static void ExportJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
194195

test/parallel/test-crypto-key-objects.js

+49
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const {
2121
privateDecrypt,
2222
privateEncrypt,
2323
getCurves,
24+
generateKeySync,
2425
generateKeyPairSync,
2526
webcrypto,
2627
} = require('crypto');
@@ -846,3 +847,51 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
846847
assert(!isKeyObject(cryptoKey));
847848
});
848849
}
850+
851+
{
852+
const first = Buffer.from('Hello');
853+
const second = Buffer.from('World');
854+
const keyObject = createSecretKey(first);
855+
assert(createSecretKey(first).equals(createSecretKey(first)));
856+
assert(!createSecretKey(first).equals(createSecretKey(second)));
857+
858+
assert.throws(() => keyObject.equals(0), {
859+
name: 'TypeError',
860+
code: 'ERR_INVALID_ARG_TYPE',
861+
message: 'The "otherKeyObject" argument must be an instance of KeyObject. Received type number (0)'
862+
});
863+
864+
assert(keyObject.equals(keyObject));
865+
assert(!keyObject.equals(createPublicKey(publicPem)));
866+
assert(!keyObject.equals(createPrivateKey(privatePem)));
867+
}
868+
869+
{
870+
const first = generateKeyPairSync('ed25519');
871+
const second = generateKeyPairSync('ed25519');
872+
const secret = generateKeySync('aes', { length: 128 });
873+
874+
assert(first.publicKey.equals(first.publicKey));
875+
assert(first.publicKey.equals(createPublicKey(
876+
first.publicKey.export({ format: 'pem', type: 'spki' }))));
877+
assert(!first.publicKey.equals(second.publicKey));
878+
assert(!first.publicKey.equals(second.privateKey));
879+
assert(!first.publicKey.equals(secret));
880+
881+
assert(first.privateKey.equals(first.privateKey));
882+
assert(first.privateKey.equals(createPrivateKey(
883+
first.privateKey.export({ format: 'pem', type: 'pkcs8' }))));
884+
assert(!first.privateKey.equals(second.privateKey));
885+
assert(!first.privateKey.equals(second.publicKey));
886+
assert(!first.privateKey.equals(secret));
887+
}
888+
889+
{
890+
const first = generateKeyPairSync('ed25519');
891+
const second = generateKeyPairSync('ed448');
892+
893+
assert(!first.publicKey.equals(second.publicKey));
894+
assert(!first.publicKey.equals(second.privateKey));
895+
assert(!first.privateKey.equals(second.privateKey));
896+
assert(!first.privateKey.equals(second.publicKey));
897+
}

0 commit comments

Comments
 (0)