Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
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;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion, feel free to ignore: Out of curiosity, did you try benchmarking this versus

BigInt(`0x${Buffer.from(input).toString('hex')}`)

Or, if performance really is a concern in this code path,

BigInt(`0x${Buffer.from(input.buffer, input.byteOffset, input.byteLength).toString('hex')}`)

(I know that this is essentially the same as the existing function bigIntArrayToUnsignedInt above, I am just curious what the performance impact is.)


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