Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: add KeyObject.prototype.equals method #42093

Merged
merged 15 commits into from Feb 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/api/crypto.md
Expand Up @@ -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).
panva marked this conversation as resolved.
Show resolved Hide resolved

### `keyObject.symmetricKeySize`

<!-- YAML
Expand Down
10 changes: 10 additions & 0 deletions lib/internal/crypto/keys.js
Expand Up @@ -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 {
Expand Down
50 changes: 50 additions & 0 deletions src/crypto/crypto_keys.cc
Expand Up @@ -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);
Expand All @@ -939,6 +940,7 @@ void KeyObjectHandle::RegisterExternalReferences(
registry->Register(InitEDRaw);
registry->Register(InitJWK);
registry->Register(GetKeyDetail);
registry->Register(Equals);
}

MaybeLocal<Object> KeyObjectHandle::Create(
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_keys.h
Expand Up @@ -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);

Expand Down
49 changes: 49 additions & 0 deletions test/parallel/test-crypto-key-objects.js
Expand Up @@ -21,6 +21,7 @@ const {
privateDecrypt,
privateEncrypt,
getCurves,
generateKeySync,
generateKeyPairSync,
webcrypto,
} = require('crypto');
Expand Down Expand Up @@ -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));
}