Skip to content

Commit

Permalink
crypto: add keyObject.asymmetricKeyDetails for asymmetric keys
Browse files Browse the repository at this point in the history
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

PR-URL: #36188
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
panva authored and ruyadorno committed Jan 21, 2021
1 parent 82eccdd commit 1149af6
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 4 deletions.
19 changes: 19 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,25 @@ 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}
* `modulusLength`: {number} Key size in bits (RSA, DSA).
* `publicExponent`: {bigint} Public exponent (RSA).
* `divisorLength`: {number} Size of `q` in bits (DSA).
* `namedCurve`: {string} Name of the curve (EC).

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.

RSA-PSS parameters, DH, or any future key type details might be exposed via this
API using additional attributes.

### `keyObject.asymmetricKeyType`
<!-- YAML
added: v11.6.0
Expand Down
29 changes: 29 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,
bigIntArrayToUnsignedBigInt,
} = require('internal/crypto/util');

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

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

function normalizeKeyDetails(details = {}) {
if (details.publicExponent !== undefined) {
return {
...details,
publicExponent:
bigIntArrayToUnsignedBigInt(new Uint8Array(details.publicExponent))
};
}
return details;
}

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

get asymmetricKeyDetails() {
switch (this.asymmetricKeyType) {
case 'rsa':
case 'rsa-pss':
case 'dsa':
case 'ec':
return this[kAsymmetricKeyDetails] ||
(this[kAsymmetricKeyDetails] = normalizeKeyDetails(
this[kHandle].keyDetail({})
));
default:
return {};
}
}
}

class PublicKeyObject extends AsymmetricKeyObject {
Expand Down
13 changes: 13 additions & 0 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const {
ArrayPrototypeIncludes,
ArrayPrototypePush,
BigInt,
FunctionPrototypeBind,
Number,
Promise,
Expand Down Expand Up @@ -308,6 +309,17 @@ function bigIntArrayToUnsignedInt(input) {
return result;
}

function bigIntArrayToUnsignedBigInt(input) {
let result = 0n;

for (let n = 0; n < input.length; ++n) {
const n_reversed = input.length - n - 1;
result |= BigInt(input[n]) << 8n * BigInt(n_reversed);
}

return result;
}

function getStringOption(options, key) {
let value;
if (options && (value = options[key]) != null)
Expand Down Expand Up @@ -413,6 +425,7 @@ module.exports = {
jobPromise,
lazyRequire,
validateMaxBufferLength,
bigIntArrayToUnsignedBigInt,
bigIntArrayToUnsignedInt,
getStringOption,
getUsagesUnion,
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
81 changes: 77 additions & 4 deletions test/parallel/test-crypto-keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,31 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
testSignVerify(publicKey, privateKey);
}

{
// Test sync key generation with key objects with a non-standard
// publicExpononent
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
publicExponent: 3,
modulusLength: 512
});

assert.strictEqual(typeof publicKey, 'object');
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
modulusLength: 512,
publicExponent: 3n
});

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

{
// Test sync key generation with key objects.
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
Expand All @@ -123,10 +148,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: 65537n
});

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

{
Expand Down Expand Up @@ -268,9 +301,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: 65537n
});

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

// Unlike RSA, RSA-PSS does not allow encryption.
assert.throws(() => {
Expand Down Expand Up @@ -342,6 +383,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 +988,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 +1016,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

0 comments on commit 1149af6

Please sign in to comment.