Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
crypto: add keyObject.asymmetricKeyDetails for asymmetric keys
This API exposes key details. It is conceptually different from the
previously discussed keyObject.fields property since it does not give
access to information that could compromise the security of the key, and
the obtained information cannot be used to uniquely identify a key.

The intended purpose is to determine "security properties" of keys, e.g.
to generate a new key pair with the same parameters, or to decide
whether a key is secure enough.

closes #30045
  • Loading branch information
panva committed Nov 20, 2020
commit 3438cca63a04dddcdf47036b380dc7bee06cf640
21 changes: 21 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,27 @@ passing keys as strings or `Buffer`s due to improved security features.
The receiver obtains a cloned `KeyObject`, and the `KeyObject` does not need to
be listed in the `transferList` argument.

### `keyObject.asymmetricKeyDetails`
<!-- YAML
added: REPLACEME
-->

* {Object}

This property exists only on asymmetric keys. Depending on the type of the key,
this object contains information about the key. None of the information obtained
through this property can be used to uniquely identify a key or to compromise
the security of the key.

For `'rsa'` and `'rsa-pss'` keys, this object has the properties `modulusLength`
and `publicExponent`.

For `'dsa'` keys, this object has the properties `modulusLength` and
`divisorLength`.

For `'ec'` keys with a known curve, this object has the string property
`namedCurve`.

### `keyObject.asymmetricKeyType`
<!-- YAML
added: v11.6.0
Expand Down
23 changes: 23 additions & 0 deletions lib/internal/crypto/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
ObjectDefineProperty,
ObjectSetPrototypeOf,
Symbol,
Uint8Array,
} = primordials;

const {
Expand Down Expand Up @@ -36,6 +37,7 @@ const {
kHandle,
kKeyObject,
getArrayBufferOrView,
bigIntArrayToUnsignedInt,
} = require('internal/crypto/util');

const {
Expand Down Expand Up @@ -128,12 +130,33 @@ const [
}

const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
const kAsymmetricKeyDetails = Symbol('kAsymmetricKeyDetails');

// TODO: should the returne details object be frozen or otherwise protected
// from manipulation?
function normalizeKeyDetails(details = {}) {
if ('publicExponent' in details) {
return {
...details,
publicExponent:
bigIntArrayToUnsignedInt(new Uint8Array(details.publicExponent))
};
}
return details;
}

class AsymmetricKeyObject extends KeyObject {
get asymmetricKeyType() {
return this[kAsymmetricKeyType] ||
(this[kAsymmetricKeyType] = this[kHandle].getAsymmetricKeyType());
}

get asymmetricKeyDetails() {
return this[kAsymmetricKeyDetails] ||
(this[kAsymmetricKeyDetails] = normalizeKeyDetails(
this[kHandle].keyDetail({})
));
}
}

class PublicKeyObject extends AsymmetricKeyObject {
Expand Down
3 changes: 1 addition & 2 deletions src/crypto/crypto_keys.cc
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,7 @@ Maybe<bool> GetAsymmetricKeyDetail(
case EVP_PKEY_EC: return GetEcKeyDetail(env, key, target);
case EVP_PKEY_DH: return GetDhKeyDetail(env, key, target);
}
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<bool>();
return Just(false);
}
} // namespace

Expand Down
1 change: 1 addition & 0 deletions test/parallel/test-crypto-key-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
assert.strictEqual(key.type, 'secret');
assert.strictEqual(key.symmetricKeySize, 32);
assert.strictEqual(key.asymmetricKeyType, undefined);
assert.strictEqual(key.asymmetricKeyDetails, undefined);

const exportedKey = key.export();
assert(keybuf.equals(exportedKey));
Expand Down
56 changes: 52 additions & 4 deletions test/parallel/test-crypto-keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,18 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
assert.strictEqual(typeof publicKey, 'object');
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
modulusLength: 512,
publicExponent: 65537
});

assert.strictEqual(typeof privateKey, 'object');
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
modulusLength: 512,
publicExponent: 65537
});
}

{
Expand Down Expand Up @@ -268,9 +276,17 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
}, common.mustSucceed((publicKey, privateKey) => {
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
modulusLength: 512,
publicExponent: 65537
});

assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
modulusLength: 512,
publicExponent: 65537
});

// Unlike RSA, RSA-PSS does not allow encryption.
assert.throws(() => {
Expand Down Expand Up @@ -342,6 +358,28 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
}));
}

{
// Test async DSA key object generation.
generateKeyPair('dsa', {
modulusLength: 512,
divisorLength: 256
}, common.mustSucceed((publicKey, privateKey) => {
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
modulusLength: 512,
divisorLength: 256
});

assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
modulusLength: 512,
divisorLength: 256
});
}));
}

{
// Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1
// private key.
Expand Down Expand Up @@ -925,16 +963,24 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
// It should recognize both NIST and standard curve names.
generateKeyPair('ec', {
namedCurve: 'P-256',
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
}, common.mustSucceed((publicKey, privateKey) => {
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
namedCurve: 'prime256v1'
});
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
namedCurve: 'prime256v1'
});
}));

generateKeyPair('ec', {
namedCurve: 'secp256k1',
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
}, common.mustSucceed((publicKey, privateKey) => {
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
namedCurve: 'secp256k1'
});
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
namedCurve: 'secp256k1'
});
}));
}

Expand All @@ -945,9 +991,11 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
generateKeyPair(keyType, common.mustSucceed((publicKey, privateKey) => {
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, keyType);
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {});

assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, keyType);
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {});
}));
});
}
Expand Down