crypto: optimize and benchmark key preparation

Optimize asymmetric key preparation with benchmark and regression
coverage for the supported input paths.

Assisted-by: GitHub Copilot, Codex
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65892
Backport-PR-URL: https://github.com/nodejs/node/pull/66092
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Filip Skokan authored and Antoine du Hamel committed 2026-09-18 14:49:45 +02:00
1 parent da341e2571
commit bfb544eb00
4 files changed
+449 -93

No files matched your search

+178
View File
@@ -0,0 +1,178 @@
'use strict';
const common = require('../common.js');
const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
const { hasOpenSSL, isBoringSSL } = require('../../test/common/crypto.js');
const inputs = [
'public-keyobject', 'public-keyobject-wrapped', 'public-pem-buffer-wrapped',
'private-keyobject', 'private-keyobject-wrapped',
'private-pem-string', 'private-pem-buffer', 'private-pem-arraybuffer',
'private-pem-string-wrapped', 'private-pem-buffer-wrapped', 'private-pem-arraybuffer-wrapped',
'private-jwk',
'public-der-pkcs1-bytes', 'private-der-pkcs1-bytes', 'private-pem-pkcs8-bytes',
'private-der-pkcs8-bytes', 'private-der-pkcs8-base64', 'private-der-pkcs8-hex',
'public-der-spki-bytes', 'private-der-sec1-bytes',
'raw-public-ec-buffer', 'raw-public-ed25519-buffer',
'raw-private-ec-buffer', 'raw-private-ec-arraybuffer', 'raw-private-ed25519-buffer',
'pem-passphrase-absent', 'pem-passphrase-empty', 'pem-passphrase-string',
'pem-passphrase-base64', 'pem-passphrase-buffer', 'pem-passphrase-arraybuffer',
'file-passphrase-absent', 'file-passphrase-string', 'file-direct',
'provider-passphrase-absent', 'provider-passphrase-absent-properties',
'provider-passphrase-empty', 'provider-passphrase-string', 'provider-passphrase-base64',
'provider-passphrase-buffer', 'provider-passphrase-arraybuffer', 'provider-direct',
];
if (hasOpenSSL(3, 5) || isBoringSSL)
inputs.push('raw-seed-ml-dsa-44-buffer');
const bench = common.createBenchmark(main, {
input: inputs,
context: ['consume-public', 'consume-private', 'create-public'],
n: [1e6],
}, {
flags: ['--expose-internals'],
combinationFilter({ input, context }) {
if (input === 'private-keyobject' || input === 'private-keyobject-wrapped')
return true;
if (input.startsWith('public-') || input.startsWith('raw-public-'))
return context === 'consume-public';
return context === 'consume-private';
},
});
function main({ input, context, n }) {
const {
prepareAsymmetricKey,
kConsumePublic,
kConsumePrivate,
kCreatePublic,
} = require('internal/crypto/keys');
const ctx = {
'consume-public': kConsumePublic,
'consume-private': kConsumePrivate,
'create-public': kCreatePublic,
}[context];
const fixtureDir = path.resolve(__dirname, '../../test/fixtures/keys');
const privateKeyPath = path.join(fixtureDir, 'rsa_private_2048.pem');
const privateKey = crypto.createPrivateKey(fs.readFileSync(privateKeyPath));
const publicKey = crypto.createPublicKey(privateKey);
const pem = privateKey.export({ format: 'pem', type: 'pkcs8' });
const buffer = Buffer.from(pem);
const arraybuffer = Uint8Array.from(buffer).buffer;
const fileURL = pathToFileURL(privateKeyPath);
const providerURL = new URL('pkcs11:object=signing-key;type=private');
const passphrase = Buffer.from('password');
let key;
switch (input) {
case 'public-keyobject': key = publicKey; break;
case 'public-keyobject-wrapped': key = { key: publicKey }; break;
case 'public-pem-buffer-wrapped':
key = { key: Buffer.from(publicKey.export({ format: 'pem', type: 'spki' })) };
break;
case 'private-keyobject': key = privateKey; break;
case 'private-keyobject-wrapped': key = { key: privateKey }; break;
case 'private-pem-string': key = pem; break;
case 'private-pem-buffer': key = buffer; break;
case 'private-pem-arraybuffer': key = arraybuffer; break;
case 'private-pem-string-wrapped': key = { key: pem }; break;
case 'private-pem-buffer-wrapped': key = { key: buffer }; break;
case 'private-pem-arraybuffer-wrapped': key = { key: arraybuffer }; break;
case 'private-jwk': key = { key: privateKey.export({ format: 'jwk' }), format: 'jwk' }; break;
case 'public-der-pkcs1-bytes': key = { key: publicKey, format: 'der', type: 'pkcs1' }; break;
case 'private-der-pkcs1-bytes': key = { key: privateKey, format: 'der', type: 'pkcs1' }; break;
case 'private-pem-pkcs8-bytes': key = { key: privateKey, format: 'pem', type: 'pkcs8' }; break;
case 'private-der-pkcs8-bytes': key = { key: privateKey, format: 'der', type: 'pkcs8' }; break;
case 'private-der-pkcs8-base64':
key = { key: privateKey, format: 'der', type: 'pkcs8', encoding: 'base64' };
break;
case 'private-der-pkcs8-hex':
key = { key: privateKey, format: 'der', type: 'pkcs8', encoding: 'hex' };
break;
case 'public-der-spki-bytes': key = { key: publicKey, format: 'der', type: 'spki' }; break;
case 'private-der-sec1-bytes':
key = {
key: crypto.createPrivateKey(fs.readFileSync(path.join(fixtureDir, 'ec_p256_private.pem'))),
format: 'der', type: 'sec1',
};
break;
case 'raw-public-ec-buffer':
case 'raw-public-ed25519-buffer':
case 'raw-private-ec-buffer':
case 'raw-private-ec-arraybuffer':
case 'raw-private-ed25519-buffer': {
const asymmetricKeyType = input.includes('-ec-') ? 'ec' : 'ed25519';
const fixture = asymmetricKeyType === 'ec' ? 'ec_p256_private.pem' : 'ed25519_private.pem';
let rawKey = crypto.createPrivateKey(fs.readFileSync(path.join(fixtureDir, fixture)));
const format = input.startsWith('raw-public-') ? 'raw-public' : 'raw-private';
if (format === 'raw-public') rawKey = crypto.createPublicKey(rawKey);
const bytes = rawKey.export({ format });
key = {
key: input.endsWith('-arraybuffer') ? Uint8Array.from(bytes).buffer : bytes,
format,
asymmetricKeyType,
namedCurve: asymmetricKeyType === 'ec' ? 'prime256v1' : undefined,
};
break;
}
case 'raw-seed-ml-dsa-44-buffer': {
const seedKey = crypto.createPrivateKey(
fs.readFileSync(path.join(fixtureDir, 'ml_dsa_44_private_seed_only.pem')));
key = { key: seedKey.export({ format: 'raw-seed' }), format: 'raw-seed', asymmetricKeyType: 'ml-dsa-44' };
break;
}
case 'pem-passphrase-absent': key = { key: privateKey }; break;
case 'pem-passphrase-empty': key = { key: privateKey, passphrase: '' }; break;
case 'pem-passphrase-string': key = { key: privateKey, passphrase: 'password' }; break;
case 'pem-passphrase-base64':
key = { key: privateKey, passphrase: passphrase.toString('base64'), encoding: 'base64' };
break;
case 'pem-passphrase-buffer': key = { key: privateKey, passphrase }; break;
case 'pem-passphrase-arraybuffer':
key = { key: privateKey, passphrase: Uint8Array.from(passphrase).buffer };
break;
case 'file-passphrase-absent': key = { key: fileURL }; break;
case 'file-passphrase-string': key = { key: fileURL, passphrase: 'password' }; break;
case 'file-direct': key = fileURL; break;
case 'provider-passphrase-absent': key = { key: providerURL }; break;
case 'provider-passphrase-absent-properties': key = { key: providerURL, properties: 'provider=default' }; break;
case 'provider-passphrase-empty': key = { key: providerURL, passphrase: '' }; break;
case 'provider-passphrase-string': key = { key: providerURL, passphrase: 'password' }; break;
case 'provider-passphrase-base64':
key = { key: providerURL, passphrase: passphrase.toString('base64'), encoding: 'base64' };
break;
case 'provider-passphrase-buffer': key = { key: providerURL, passphrase }; break;
case 'provider-passphrase-arraybuffer':
key = { key: providerURL, passphrase: Uint8Array.from(passphrase).buffer };
break;
case 'provider-direct': key = providerURL; break;
default: throw new Error(`Unsupported input: ${input}`);
}
if (input.startsWith('pem-passphrase-')) {
const bytes = key.passphrase === undefined ? undefined :
typeof key.passphrase === 'string' ? Buffer.from(key.passphrase, key.encoding) :
Buffer.from(key.passphrase);
key = {
key: Buffer.from(privateKey.export({
format: 'pem', type: 'pkcs8',
...(bytes === undefined ? {} : { cipher: 'aes-256-cbc', passphrase: bytes }),
})),
format: 'pem', type: 'pkcs8', passphrase: key.passphrase, encoding: key.encoding,
};
} else if (key.key instanceof crypto.KeyObject && key.format !== undefined) {
const bytes = Buffer.from(key.key.export({ format: key.format, type: key.type }));
key.key = key.encoding === undefined ? bytes : bytes.toString(key.encoding);
}
prepareAsymmetricKey(key, ctx);
bench.start();
for (let index = 0; index < n; index++) {
prepareAsymmetricKey(key, ctx);
}
bench.end(n);
}
+120 -93
View File
@@ -392,7 +392,7 @@ const {
return [KeyObject, SecretKeyObject, PublicKeyObject, PrivateKeyObject];
});
function parseKeyFormat(formatStr, defaultFormat, optionName) {
function parseKeyFormat(formatStr, defaultFormat, objName) {
if (formatStr === undefined && defaultFormat !== undefined)
return defaultFormat;
else if (formatStr === 'pem')
@@ -407,10 +407,10 @@ function parseKeyFormat(formatStr, defaultFormat, optionName) {
return kKeyFormatRawPrivate;
else if (formatStr === 'raw-seed')
return kKeyFormatRawSeed;
throw new ERR_INVALID_ARG_VALUE(optionName, formatStr);
throw new ERR_INVALID_ARG_VALUE(option('format', objName), formatStr);
}
function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
function parseKeyType(typeStr, required, keyType, isPublic, objName) {
if (typeStr === undefined && !required) {
return undefined;
} else if (typeStr === 'pkcs1') {
@@ -431,7 +431,7 @@ function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
return kKeyEncodingSEC1;
}
throw new ERR_INVALID_ARG_VALUE(optionName, typeStr);
throw new ERR_INVALID_ARG_VALUE(option('type', objName), typeStr);
}
function option(name, prefix) {
@@ -445,7 +445,7 @@ function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
const isInput = keyType === undefined;
const format = parseKeyFormat(formatStr,
isInput ? kKeyFormatPEM : undefined,
option('format', objName));
objName);
if (format === kKeyFormatRawPublic) {
if (isPublic === false) {
@@ -481,7 +481,7 @@ function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
isRequired,
keyType,
isPublic,
option('type', objName));
objName);
return { format, type };
}
@@ -491,6 +491,10 @@ function isStringOrBuffer(val) {
isAnyArrayBuffer(val);
}
function encodeKeyString(value, encoding) {
return Buffer.from(value, encoding === 'buffer' ? 'utf8' : encoding);
}
function parseKeyEncoding(enc, keyType, isPublic, objName) {
validateObject(enc, 'options');
@@ -538,8 +542,8 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) {
}
}
if (passphrase !== undefined)
passphrase = getArrayBufferOrView(passphrase, 'key.passphrase', encoding);
if (typeof passphrase === 'string')
passphrase = encodeKeyString(passphrase, encoding);
return { format, type, cipher, passphrase };
}
@@ -567,11 +571,11 @@ function validateAsymmetricKeyType(type, ctx, key) {
);
}
if (type !== 'private') {
if (type !== kKeyTypePrivate) {
if (ctx === kConsumePrivate || ctx === kCreatePublic)
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(type, 'private');
if (type !== 'public') {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(type,
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(keyObjectTypeToString(type), 'private');
if (type !== kKeyTypePublic) {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(keyObjectTypeToString(type),
'private or public');
}
}
@@ -613,10 +617,9 @@ function prepareStorePrivateKey(url, name, passphrase, encoding, properties) {
if (!isStringOrBuffer(passphrase)) {
throw new ERR_INVALID_ARG_VALUE(option('passphrase', name), passphrase);
}
passphrase = getArrayBufferOrView(
passphrase,
option('passphrase', name),
encoding);
if (typeof passphrase === 'string') {
passphrase = encodeKeyString(passphrase, encoding);
}
}
if (properties !== undefined) {
@@ -641,97 +644,35 @@ function prepareStorePrivateKey(url, name, passphrase, encoding, properties) {
}
function prepareAsymmetricKey(key, ctx, name = 'key') {
if (typeof key === 'string') {
return { format: kKeyFormatPEM, data: encodeKeyString(key) };
}
if (isArrayBufferView(key)) {
return { format: kKeyFormatPEM, data: key };
}
const isPrivateKeyInput = ctx === kConsumePrivate || ctx === kCreatePrivate;
if (isKeyObject(key)) {
// Best case: A key object, as simple as that.
const type = getKeyObjectType(key);
validateAsymmetricKeyType(type, ctx, key);
return { data: getKeyObjectHandle(key) };
const slots = getKeyObjectSlots(key);
validateAsymmetricKeyType(slots[kKeyObjectSlotType], ctx, key);
return { data: slots[kKeyObjectSlotHandle] };
}
if (isCryptoKey(key)) {
emitDEP0203();
validateAsymmetricKeyType(getCryptoKeyType(key), ctx, key);
return { data: getCryptoKeyHandle(key) };
const slots = getSlots(key);
validateAsymmetricKeyType(slots[kSlotType], ctx, key);
return { data: slots[kSlotHandle] };
}
if (isPrivateKeyInput && isURLInstance(key)) {
// A private key referenced by a URI for an OpenSSL STORE loader.
return prepareStorePrivateKey(key, name);
}
if (isStringOrBuffer(key)) {
// Expect PEM by default, mostly for backward compatibility.
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, name) };
if (isAnyArrayBuffer(key)) {
return { format: kKeyFormatPEM, data: key };
}
if (typeof key === 'object') {
const { key: data, encoding, format, properties } = key;
// The 'key' property can be a KeyObject as well to allow specifying
// additional options such as padding along with the key.
if (isKeyObject(data)) {
const type = getKeyObjectType(data);
validateAsymmetricKeyType(type, ctx, data);
return { data: getKeyObjectHandle(data) };
}
if (isCryptoKey(data)) {
emitDEP0203();
validateAsymmetricKeyType(getCryptoKeyType(data), ctx, data);
return { data: getCryptoKeyHandle(data) };
}
if (isPrivateKeyInput && isURLInstance(data)) {
// A private key referenced by a URI for an OpenSSL STORE loader, with
// an optional passphrase/PIN.
return prepareStorePrivateKey(
data,
name,
key.passphrase,
encoding,
properties);
}
if (format === 'jwk') {
validateObject(data, `${name}.key`);
return { data, format: kKeyFormatJWK };
} else if (format === 'raw-public' || format === 'raw-private' ||
format === 'raw-seed') {
if ((ctx === kConsumePrivate || ctx === kCreatePrivate) &&
format === 'raw-public') {
throw new ERR_INVALID_ARG_VALUE(`${name}.format`, format);
}
if (!isArrayBufferView(data) && !isAnyArrayBuffer(data)) {
throw new ERR_INVALID_ARG_TYPE(
`${name}.key`,
['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
data);
}
validateString(key.asymmetricKeyType, `${name}.asymmetricKeyType`);
if (key.asymmetricKeyType === 'ec') {
validateString(key.namedCurve, `${name}.namedCurve`);
}
const rawFormat = parseKeyFormat(format, undefined, `${name}.format`);
return {
data: getArrayBufferOrView(data, `${name}.key`),
format: rawFormat,
type: key.asymmetricKeyType,
namedCurve: key.namedCurve ?? null,
};
}
// Either PEM or DER using PKCS#1 or SPKI.
if (!isStringOrBuffer(data)) {
throw new ERR_INVALID_ARG_TYPE(
`${name}.key`,
getKeyTypes({
allowKeyObject: ctx !== kCreatePrivate,
allowURL: isPrivateKeyInput,
}),
data);
}
const isPublic =
(ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined;
return {
data: getArrayBufferOrView(data, `${name}.key`, encoding),
...parseKeyEncoding(key, undefined, isPublic, name),
};
return prepareAsymmetricKeyOptions(key, ctx, name, isPrivateKeyInput);
}
throw new ERR_INVALID_ARG_TYPE(
@@ -743,6 +684,87 @@ function prepareAsymmetricKey(key, ctx, name = 'key') {
key);
}
function prepareAsymmetricKeyOptions(key, ctx, name, isPrivateKeyInput) {
const { key: data, encoding, format, properties } = key;
let isData = typeof data === 'string' || isArrayBufferView(data);
// The 'key' property can be a KeyObject as well to allow specifying
// additional options such as padding along with the key.
if (!isData && isKeyObject(data)) {
const slots = getKeyObjectSlots(data);
validateAsymmetricKeyType(slots[kKeyObjectSlotType], ctx, data);
return { data: slots[kKeyObjectSlotHandle] };
}
if (!isData && isCryptoKey(data)) {
emitDEP0203();
const slots = getSlots(data);
validateAsymmetricKeyType(slots[kSlotType], ctx, data);
return { data: slots[kSlotHandle] };
}
isData ||= isAnyArrayBuffer(data);
if (!isData && isPrivateKeyInput && isURLInstance(data)) {
// A private key referenced by a URI for an OpenSSL STORE loader, with
// an optional passphrase/PIN.
return prepareStorePrivateKey(
data,
name,
key.passphrase,
encoding,
properties);
}
if (format === 'jwk') {
validateObject(data, `${name}.key`);
return { data, format: kKeyFormatJWK };
}
if (format === 'raw-public' || format === 'raw-private' ||
format === 'raw-seed') {
if (isPrivateKeyInput && format === 'raw-public') {
throw new ERR_INVALID_ARG_VALUE(`${name}.format`, format);
}
if (!isData || typeof data === 'string') {
throw new ERR_INVALID_ARG_TYPE(
`${name}.key`,
['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
data);
}
validateString(key.asymmetricKeyType, `${name}.asymmetricKeyType`);
if (key.asymmetricKeyType === 'ec') {
validateString(key.namedCurve, `${name}.namedCurve`);
}
const rawFormat = format === 'raw-public' ? kKeyFormatRawPublic :
format === 'raw-private' ? kKeyFormatRawPrivate : kKeyFormatRawSeed;
return {
data,
format: rawFormat,
type: key.asymmetricKeyType,
namedCurve: key.namedCurve ?? null,
};
}
// Either PEM or DER using PKCS#1 or SPKI.
if (!isData) {
throw new ERR_INVALID_ARG_TYPE(
`${name}.key`,
getKeyTypes({
allowKeyObject: ctx !== kCreatePrivate,
allowURL: isPrivateKeyInput,
}),
data);
}
const isPublic = isPrivateKeyInput ? false : undefined;
const keyData = typeof data === 'string' ?
encodeKeyString(data, encoding) : data;
const parsed = parseKeyEncoding(key, undefined, isPublic, name);
return {
data: keyData,
format: parsed.format,
type: parsed.type,
cipher: parsed.cipher,
passphrase: parsed.passphrase,
};
}
function preparePrivateKey(key, name) {
return prepareAsymmetricKey(key, kConsumePrivate, name);
}
@@ -1512,6 +1534,11 @@ module.exports = {
parsePrivateKeyEncoding,
parseKeyEncoding,
toPublicCryptoKey,
prepareAsymmetricKey,
kConsumePublic,
kConsumePrivate,
kCreatePublic,
kCreatePrivate,
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey,
@@ -0,0 +1,50 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('node:assert');
const crypto = require('node:crypto');
const fixtures = require('../common/fixtures');
const {
prepareAsymmetricKey,
getCryptoKeyHandle,
kConsumePublic,
kConsumePrivate,
kCreatePublic,
kCreatePrivate,
} = require('internal/crypto/keys');
common.expectWarning({
DeprecationWarning: {
DEP0203: 'Passing a CryptoKey to node:crypto functions is deprecated.',
},
});
const privateKeyObject = crypto.createPrivateKey(fixtures.readKey('ec_p256_private.pem'));
const algorithm = { name: 'ECDSA', namedCurve: 'P-256' };
const privateKey = privateKeyObject.toCryptoKey(algorithm, true, ['sign']);
const publicKey = crypto.createPublicKey(privateKeyObject).toCryptoKey(algorithm, true, ['verify']);
const secretKey = crypto.createSecretKey(Buffer.alloc(16)).toCryptoKey('AES-CBC', true, ['encrypt']);
for (const key of [privateKey, publicKey, secretKey]) {
for (const input of [key, { key, format: 'raw-public', asymmetricKeyType: 'invalid' }]) {
for (const context of [kConsumePublic, kConsumePrivate, kCreatePublic]) {
if (key === privateKey || (key === publicKey && context === kConsumePublic)) {
assert.strictEqual(prepareAsymmetricKey(input, context).data, getCryptoKeyHandle(key));
} else {
const expected = context === kConsumePublic ? 'private or public' : 'private';
assert.throws(() => prepareAsymmetricKey(input, context), {
code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
name: 'TypeError',
message: `Invalid key object type ${key.type}, expected ${expected}.`,
});
}
}
assert.throws(() => prepareAsymmetricKey(input, kCreatePrivate), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
}
}
@@ -0,0 +1,101 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('node:assert');
const crypto = require('node:crypto');
const fixtures = require('../common/fixtures');
const {
prepareAsymmetricKey,
getKeyObjectHandle,
kConsumePublic,
kConsumePrivate,
kCreatePublic,
kCreatePrivate,
} = require('internal/crypto/keys');
const pem = fixtures.readKey('ec_p256_private.pem');
const keyObject = crypto.createPrivateKey(pem);
const contexts = [kConsumePublic, kConsumePrivate, kCreatePublic, kCreatePrivate];
for (const input of [
pem,
new Uint8Array(pem),
new DataView(Uint8Array.from(pem).buffer),
Uint8Array.from(pem).buffer,
new SharedArrayBuffer(pem.length),
]) {
Object.defineProperty(input, 'key', { get: common.mustNotCall() });
Object.defineProperty(input, 'format', { get: common.mustNotCall() });
Object.defineProperty(input, Symbol.toStringTag, { value: 'KeyObject' });
for (const context of contexts) {
assert.strictEqual(prepareAsymmetricKey(input, context).data, input);
assert.strictEqual(prepareAsymmetricKey({ key: input }, context).data, input);
}
}
for (const context of [kConsumePublic, kConsumePrivate, kCreatePublic]) {
const options = { key: keyObject, format: 'raw-public', asymmetricKeyType: 'invalid' };
assert.strictEqual(prepareAsymmetricKey(options, context).data, getKeyObjectHandle(keyObject));
}
{
const reads = [];
const values = { key: pem.toString(), encoding: 'utf8', format: 'pem' };
const options = {};
for (const name of ['key', 'encoding', 'format', 'properties', 'type', 'cipher', 'passphrase']) {
Object.defineProperty(options, name, {
get() {
reads.push(name);
return values[name];
},
});
}
assert.deepStrictEqual(prepareAsymmetricKey(options, kConsumePrivate).data, pem);
assert.deepStrictEqual(reads, [
'key', 'encoding', 'format', 'properties', 'format', 'type', 'cipher', 'passphrase', 'encoding',
]);
}
{
const input = {
key: pem.toString(),
encoding: 'invalid',
get format() { return 'pem'; },
get type() { return assert.fail('String conversion must precede encoding option parsing'); },
};
assert.throws(() => prepareAsymmetricKey(input, kConsumePrivate), { code: 'ERR_UNKNOWN_ENCODING' });
}
{
const raw = keyObject.export({ format: 'raw-private' });
const input = { key: raw, format: 'raw-private', asymmetricKeyType: 'ec', namedCurve: 'P-256' };
assert.strictEqual(prepareAsymmetricKey(input, kConsumePrivate).data, raw);
assert.throws(() => prepareAsymmetricKey({ ...input, key: raw.toString('hex') }, kConsumePrivate), {
code: 'ERR_INVALID_ARG_TYPE',
});
}
for (const encoding of [undefined, 'buffer', 'utf8', 'hex', 'base64']) {
const inputEncoding = encoding === 'buffer' ? 'utf8' : encoding;
const passphrase = Buffer.from('password');
const result = prepareAsymmetricKey({
key: pem.toString(inputEncoding),
passphrase: passphrase.toString(inputEncoding),
encoding,
}, kConsumePrivate);
assert.deepStrictEqual(result.data, pem);
assert.deepStrictEqual(result.passphrase, passphrase);
}
for (const [options, optionName] of [
[{ format: 'invalid' }, 'format'],
[{ format: 'der', type: 'invalid' }, 'type'],
]) {
assert.throws(() => prepareAsymmetricKey({ key: pem, ...options }, kConsumePrivate, 'options.key'), {
code: 'ERR_INVALID_ARG_VALUE',
message: new RegExp(`options\\.key\\.${optionName}`),
});
}