crypto: add KeyObject.prototype.equals method

PR-URL: https://github.com/nodejs/node/pull/42093
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
Filip Skokan 2022-02-26 14:51:09 +01:00 committed by GitHub
parent bcc523e389
commit aa97c9d973
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 124 additions and 0 deletions

View File

@ -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 a PKCS#8 key. See [RFC 5208][] for PKCS#8 encryption and [RFC 1421][] for
PKCS#1 and SEC1 encryption. 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` ### `keyObject.symmetricKeySize`
<!-- YAML <!-- YAML

View File

@ -124,6 +124,16 @@ const {
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key); throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
return key[kKeyObject]; 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 { class SecretKeyObject extends KeyObject {

View File

@ -921,6 +921,7 @@ v8::Local<v8::Function> KeyObjectHandle::Initialize(Environment* env) {
env->SetProtoMethod(t, "initEDRaw", InitEDRaw); env->SetProtoMethod(t, "initEDRaw", InitEDRaw);
env->SetProtoMethod(t, "initJwk", InitJWK); env->SetProtoMethod(t, "initJwk", InitJWK);
env->SetProtoMethod(t, "keyDetail", GetKeyDetail); env->SetProtoMethod(t, "keyDetail", GetKeyDetail);
env->SetProtoMethod(t, "equals", Equals);
auto function = t->GetFunction(env->context()).ToLocalChecked(); auto function = t->GetFunction(env->context()).ToLocalChecked();
env->set_crypto_key_object_handle_constructor(function); env->set_crypto_key_object_handle_constructor(function);
@ -939,6 +940,7 @@ void KeyObjectHandle::RegisterExternalReferences(
registry->Register(InitEDRaw); registry->Register(InitEDRaw);
registry->Register(InitJWK); registry->Register(InitJWK);
registry->Register(GetKeyDetail); registry->Register(GetKeyDetail);
registry->Register(Equals);
} }
MaybeLocal<Object> KeyObjectHandle::Create( MaybeLocal<Object> KeyObjectHandle::Create(
@ -1134,6 +1136,54 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(true); 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) { void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key; KeyObjectHandle* key;

View File

@ -189,6 +189,7 @@ class KeyObjectHandle : public BaseObject {
static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args); static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitJWK(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 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); static void ExportJWK(const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -21,6 +21,7 @@ const {
privateDecrypt, privateDecrypt,
privateEncrypt, privateEncrypt,
getCurves, getCurves,
generateKeySync,
generateKeyPairSync, generateKeyPairSync,
webcrypto, webcrypto,
} = require('crypto'); } = require('crypto');
@ -846,3 +847,51 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
assert(!isKeyObject(cryptoKey)); 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));
}