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 9 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
11 changes: 11 additions & 0 deletions doc/api/crypto.md
Expand Up @@ -2082,6 +2082,17 @@ 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} `true` or `false` depending on the whether the
panva marked this conversation as resolved.
Show resolved Hide resolved
keys have exactly the same type, value, and parameters.
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
47 changes: 47 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,51 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(true);
}

void KeyObjectHandle::Equals(const FunctionCallbackInfo<Value>& args) {
std::shared_ptr<KeyObjectData> key =
Unwrap<KeyObjectHandle>(args.Holder())->Data();
std::shared_ptr<KeyObjectData> key2 =
Unwrap<KeyObjectHandle>(args[0].As<Object>())->Data();
panva marked this conversation as resolved.
Show resolved Hide resolved

KeyType keyType = key->GetKeyType();
panva marked this conversation as resolved.
Show resolved Hide resolved
CHECK_EQ(keyType, key2->GetKeyType());

bool ret;
switch (keyType) {
case kKeyTypeSecret: {
size_t size = key->GetSymmetricKeySize();
if (size == key2->GetSymmetricKeySize())
panva marked this conversation as resolved.
Show resolved Hide resolved
ret = CRYPTO_memcmp(
key->GetSymmetricKey(),
key2->GetSymmetricKey(),
size) == 0;
else
ret = false;
break;
}
case kKeyTypePublic:
// Fall through
panva marked this conversation as resolved.
Show resolved Hide resolved
case kKeyTypePrivate: {
EVP_PKEY* pkey = key->GetAsymmetricKey().get();
EVP_PKEY* pkey2 = key2->GetAsymmetricKey().get();
if (EVP_PKEY_id(pkey) == EVP_PKEY_id(pkey2)) {
#if OPENSSL_VERSION_MAJOR >= 3
ret = EVP_PKEY_eq(pkey, pkey2) == 1;
panva marked this conversation as resolved.
Show resolved Hide resolved
#else
ret = EVP_PKEY_cmp(pkey, pkey2) == 1;
panva marked this conversation as resolved.
Show resolved Hide resolved
#endif
} else {
ret = false;
}
break;
}
default:
CHECK(false);
panva marked this conversation as resolved.
Show resolved Hide resolved
}

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));
}