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.asymmetricKeyDetails for asymmetric keys #36188

Closed
wants to merge 4 commits into from
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).
panva marked this conversation as resolved.
Show resolved Hide resolved

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.
panva marked this conversation as resolved.
Show resolved Hide resolved

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
});
panva marked this conversation as resolved.
Show resolved Hide resolved

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