Skip to content

Commit

Permalink
crypto: add KeyObject.prototype.equals method
Browse files Browse the repository at this point in the history
PR-URL: nodejs#42093
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: Tobias Nießen <[email protected]>
  • Loading branch information
panva authored and sxa committed Mar 7, 2022
1 parent 384872f commit 2b35422
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 0 deletions.
14 changes: 14 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
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).

### `keyObject.symmetricKeySize`

<!-- YAML
Expand Down
10 changes: 10 additions & 0 deletions lib/internal/crypto/keys.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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));
}

0 comments on commit 2b35422

Please sign in to comment.