node/test/parallel/test-crypto-key-objects.js
James M Snell 761de815c5
test: move crypto related common utilities in common/crypto
Since `common/crypto` already exists, it makes sense to keep
crypto-related utilities there. The only exception being
common.hasCrypto which is needed up front to determine
if tests should be skipped.

Eliminate the redundant check in hasFipsCrypto and just
use crypto.getFips() directly where needed.

PR-URL: https://github.com/nodejs/node/pull/56714
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
2025-01-25 00:58:32 +00:00

891 lines
31 KiB
JavaScript

'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const {
createCipheriv,
createDecipheriv,
createSign,
createVerify,
createSecretKey,
createPublicKey,
createPrivateKey,
KeyObject,
randomBytes,
publicDecrypt,
publicEncrypt,
privateDecrypt,
privateEncrypt,
getCurves,
generateKeySync,
generateKeyPairSync,
} = require('crypto');
const { hasOpenSSL3 } = require('../common/crypto');
const fixtures = require('../common/fixtures');
const publicPem = fixtures.readKey('rsa_public.pem', 'ascii');
const privatePem = fixtures.readKey('rsa_private.pem', 'ascii');
const publicDsa = fixtures.readKey('dsa_public_1025.pem', 'ascii');
const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
'ascii');
{
// Attempting to create a key of a wrong type should throw
const TYPE = 'wrong_type';
assert.throws(() => new KeyObject(TYPE), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_VALUE',
message: `The argument 'type' is invalid. Received '${TYPE}'`
});
}
{
// Attempting to create a key with non-object handle should throw
assert.throws(() => new KeyObject('secret', ''), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message:
'The "handle" argument must be of type object. Received type ' +
"string ('')"
});
}
{
assert.throws(() => KeyObject.from('invalid_key'), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message:
'The "key" argument must be an instance of CryptoKey. Received type ' +
"string ('invalid_key')"
});
}
{
const keybuf = randomBytes(32);
const key = createSecretKey(keybuf);
assert.strictEqual(key.type, 'secret');
assert.strictEqual(key.toString(), '[object KeyObject]');
assert.strictEqual(key.symmetricKeySize, 32);
assert.strictEqual(key.asymmetricKeyType, undefined);
assert.strictEqual(key.asymmetricKeyDetails, undefined);
const exportedKey = key.export();
assert(keybuf.equals(exportedKey));
const plaintext = Buffer.from('Hello world', 'utf8');
const cipher = createCipheriv('aes-256-ecb', key, null);
const ciphertext = Buffer.concat([
cipher.update(plaintext), cipher.final(),
]);
const decipher = createDecipheriv('aes-256-ecb', key, null);
const deciphered = Buffer.concat([
decipher.update(ciphertext), decipher.final(),
]);
assert(plaintext.equals(deciphered));
}
{
// Passing an existing public key object to createPublicKey should throw.
const publicKey = createPublicKey(publicPem);
assert.throws(() => createPublicKey(publicKey), {
name: 'TypeError',
code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
message: 'Invalid key object type public, expected private.'
});
// Constructing a private key from a public key should be impossible, even
// if the public key was derived from a private key.
assert.throws(() => createPrivateKey(createPublicKey(privatePem)), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
});
// Similarly, passing an existing private key object to createPrivateKey
// should throw.
const privateKey = createPrivateKey(privatePem);
assert.throws(() => createPrivateKey(privateKey), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
});
}
{
const jwk = {
e: 'AQAB',
n: 't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +
'1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +
'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +
'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +
'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',
d: 'ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0' +
'Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW' +
'5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2' +
'fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-' +
'Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ',
p: '8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B' +
'kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp' +
'bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8',
q: 'wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X' +
'PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI' +
'jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs',
dp: 'qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr' +
'6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN' +
'Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8',
dq: 'WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_' +
'Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ' +
'fZabRRiI0VQR472300AVEeX4vgbrDBn600',
qi: 'k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl' +
'D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX' +
'ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM',
kty: 'RSA',
};
const publicJwk = { kty: jwk.kty, e: jwk.e, n: jwk.n };
const publicKey = createPublicKey(publicPem);
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.toString(), '[object KeyObject]');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
assert.strictEqual(publicKey.symmetricKeySize, undefined);
const privateKey = createPrivateKey(privatePem);
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.toString(), '[object KeyObject]');
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
assert.strictEqual(privateKey.symmetricKeySize, undefined);
// It should be possible to derive a public key from a private key.
const derivedPublicKey = createPublicKey(privateKey);
assert.strictEqual(derivedPublicKey.type, 'public');
assert.strictEqual(derivedPublicKey.toString(), '[object KeyObject]');
assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa');
assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined);
const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: 'jwk' });
assert.strictEqual(publicKeyFromJwk.type, 'public');
assert.strictEqual(publicKeyFromJwk.toString(), '[object KeyObject]');
assert.strictEqual(publicKeyFromJwk.asymmetricKeyType, 'rsa');
assert.strictEqual(publicKeyFromJwk.symmetricKeySize, undefined);
const privateKeyFromJwk = createPrivateKey({ key: jwk, format: 'jwk' });
assert.strictEqual(privateKeyFromJwk.type, 'private');
assert.strictEqual(privateKeyFromJwk.toString(), '[object KeyObject]');
assert.strictEqual(privateKeyFromJwk.asymmetricKeyType, 'rsa');
assert.strictEqual(privateKeyFromJwk.symmetricKeySize, undefined);
// It should also be possible to import an encrypted private key as a public
// key.
const decryptedKey = createPublicKey({
key: privateKey.export({
type: 'pkcs8',
format: 'pem',
passphrase: '123',
cipher: 'aes-128-cbc'
}),
format: 'pem',
passphrase: '123'
});
assert.strictEqual(decryptedKey.type, 'public');
assert.strictEqual(decryptedKey.asymmetricKeyType, 'rsa');
// Test exporting with an invalid options object, this should throw.
for (const opt of [undefined, null, 'foo', 0, NaN]) {
assert.throws(() => publicKey.export(opt), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: /^The "options" argument must be of type object/
});
}
for (const keyObject of [publicKey, derivedPublicKey, publicKeyFromJwk]) {
assert.deepStrictEqual(
keyObject.export({ format: 'jwk' }),
{ kty: 'RSA', n: jwk.n, e: jwk.e }
);
}
for (const keyObject of [privateKey, privateKeyFromJwk]) {
assert.deepStrictEqual(
keyObject.export({ format: 'jwk' }),
jwk
);
}
// Exporting the key using JWK should not work since this format does not
// support key encryption
assert.throws(() => {
privateKey.export({ format: 'jwk', passphrase: 'secret' });
}, {
message: 'The selected key encoding jwk does not support encryption.',
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
});
const publicDER = publicKey.export({
format: 'der',
type: 'pkcs1'
});
const privateDER = privateKey.export({
format: 'der',
type: 'pkcs1'
});
assert(Buffer.isBuffer(publicDER));
assert(Buffer.isBuffer(privateDER));
const plaintext = Buffer.from('Hello world', 'utf8');
const testDecryption = (fn, ciphertexts, decryptionKeys) => {
for (const ciphertext of ciphertexts) {
for (const key of decryptionKeys) {
const deciphered = fn(key, ciphertext);
assert.deepStrictEqual(deciphered, plaintext);
}
}
};
testDecryption(privateDecrypt, [
// Encrypt using the public key.
publicEncrypt(publicKey, plaintext),
publicEncrypt({ key: publicKey }, plaintext),
publicEncrypt({ key: publicJwk, format: 'jwk' }, plaintext),
// Encrypt using the private key.
publicEncrypt(privateKey, plaintext),
publicEncrypt({ key: privateKey }, plaintext),
publicEncrypt({ key: jwk, format: 'jwk' }, plaintext),
// Encrypt using a public key derived from the private key.
publicEncrypt(derivedPublicKey, plaintext),
publicEncrypt({ key: derivedPublicKey }, plaintext),
// Test distinguishing PKCS#1 public and private keys based on the
// DER-encoded data only.
publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext),
publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext),
], [
privateKey,
{ format: 'pem', key: privatePem },
{ format: 'der', type: 'pkcs1', key: privateDER },
{ key: jwk, format: 'jwk' },
]);
testDecryption(publicDecrypt, [
privateEncrypt(privateKey, plaintext),
], [
// Decrypt using the public key.
publicKey,
{ format: 'pem', key: publicPem },
{ format: 'der', type: 'pkcs1', key: publicDER },
{ key: publicJwk, format: 'jwk' },
// Decrypt using the private key.
privateKey,
{ format: 'pem', key: privatePem },
{ format: 'der', type: 'pkcs1', key: privateDER },
{ key: jwk, format: 'jwk' },
]);
}
{
// This should not cause a crash: https://github.com/nodejs/node/issues/25247
assert.throws(() => {
createPrivateKey({ key: '' });
}, hasOpenSSL3 ? {
message: 'error:1E08010C:DECODER routines::unsupported',
} : {
message: 'error:0909006C:PEM routines:get_name:no start line',
code: 'ERR_OSSL_PEM_NO_START_LINE',
reason: 'no start line',
library: 'PEM routines',
function: 'get_name',
});
// This should not abort either: https://github.com/nodejs/node/issues/29904
assert.throws(() => {
createPrivateKey({ key: Buffer.alloc(0), format: 'der', type: 'spki' });
}, {
code: 'ERR_INVALID_ARG_VALUE',
message: "The property 'options.type' is invalid. Received 'spki'"
});
// Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys),
// so it should be accepted by createPrivateKey, but OpenSSL won't parse it.
assert.throws(() => {
const key = createPublicKey(publicPem).export({
format: 'der',
type: 'pkcs1'
});
createPrivateKey({ key, format: 'der', type: 'pkcs1' });
}, hasOpenSSL3 ? {
message: /error:1E08010C:DECODER routines::unsupported/,
library: 'DECODER routines'
} : {
message: /asn1 encoding/,
library: 'asn1 encoding routines'
});
}
[
{ private: fixtures.readKey('ed25519_private.pem', 'ascii'),
public: fixtures.readKey('ed25519_public.pem', 'ascii'),
keyType: 'ed25519',
jwk: {
crv: 'Ed25519',
x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',
d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',
kty: 'OKP'
} },
{ private: fixtures.readKey('ed448_private.pem', 'ascii'),
public: fixtures.readKey('ed448_public.pem', 'ascii'),
keyType: 'ed448',
jwk: {
crv: 'Ed448',
x: 'oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o' +
'Dgc2V5ZUA',
d: '060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX' +
'jcR9mxppY',
kty: 'OKP'
} },
{ private: fixtures.readKey('x25519_private.pem', 'ascii'),
public: fixtures.readKey('x25519_public.pem', 'ascii'),
keyType: 'x25519',
jwk: {
crv: 'X25519',
x: 'aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig',
d: 'mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc',
kty: 'OKP'
} },
{ private: fixtures.readKey('x448_private.pem', 'ascii'),
public: fixtures.readKey('x448_public.pem', 'ascii'),
keyType: 'x448',
jwk: {
crv: 'X448',
x: 'ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg' +
'vSKsDFPA',
d: 'tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy' +
'S0jlSYJk',
kty: 'OKP'
} },
].forEach((info) => {
const keyType = info.keyType;
{
const key = createPrivateKey(info.private);
assert.strictEqual(key.type, 'private');
assert.strictEqual(key.asymmetricKeyType, keyType);
assert.strictEqual(key.symmetricKeySize, undefined);
assert.strictEqual(
key.export({ type: 'pkcs8', format: 'pem' }), info.private);
assert.deepStrictEqual(
key.export({ format: 'jwk' }), info.jwk);
}
{
const key = createPrivateKey({ key: info.jwk, format: 'jwk' });
assert.strictEqual(key.type, 'private');
assert.strictEqual(key.asymmetricKeyType, keyType);
assert.strictEqual(key.symmetricKeySize, undefined);
assert.strictEqual(
key.export({ type: 'pkcs8', format: 'pem' }), info.private);
assert.deepStrictEqual(
key.export({ format: 'jwk' }), info.jwk);
}
{
for (const input of [
info.private, info.public, { key: info.jwk, format: 'jwk' }]) {
const key = createPublicKey(input);
assert.strictEqual(key.type, 'public');
assert.strictEqual(key.asymmetricKeyType, keyType);
assert.strictEqual(key.symmetricKeySize, undefined);
assert.strictEqual(
key.export({ type: 'spki', format: 'pem' }), info.public);
const jwk = { ...info.jwk };
delete jwk.d;
assert.deepStrictEqual(
key.export({ format: 'jwk' }), jwk);
}
}
});
[
{ private: fixtures.readKey('ec_p256_private.pem', 'ascii'),
public: fixtures.readKey('ec_p256_public.pem', 'ascii'),
keyType: 'ec',
namedCurve: 'prime256v1',
jwk: {
crv: 'P-256',
d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo',
kty: 'EC',
x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',
y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI'
} },
{ private: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'),
public: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'),
keyType: 'ec',
namedCurve: 'secp256k1',
jwk: {
crv: 'secp256k1',
d: 'c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM',
kty: 'EC',
x: 'cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA',
y: '-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo'
} },
{ private: fixtures.readKey('ec_p384_private.pem', 'ascii'),
public: fixtures.readKey('ec_p384_public.pem', 'ascii'),
keyType: 'ec',
namedCurve: 'secp384r1',
jwk: {
crv: 'P-384',
d: 'dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi',
kty: 'EC',
x: 'hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV',
y: 'fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl'
} },
{ private: fixtures.readKey('ec_p521_private.pem', 'ascii'),
public: fixtures.readKey('ec_p521_public.pem', 'ascii'),
keyType: 'ec',
namedCurve: 'secp521r1',
jwk: {
crv: 'P-521',
d: 'ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluPJMaaHcNer' +
'bQH_WdVkLLX86ShlHrRyJ',
kty: 'EC',
x: 'AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL' +
'CbhMeHRavUS6P10rsTtBn',
y: 'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB' +
'cvA2iFJRUyQ3whC00j0Np'
} },
].forEach((info) => {
const { keyType, namedCurve } = info;
{
const key = createPrivateKey(info.private);
assert.strictEqual(key.type, 'private');
assert.strictEqual(key.asymmetricKeyType, keyType);
assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
assert.strictEqual(key.symmetricKeySize, undefined);
assert.strictEqual(
key.export({ type: 'pkcs8', format: 'pem' }), info.private);
assert.deepStrictEqual(
key.export({ format: 'jwk' }), info.jwk);
}
{
const key = createPrivateKey({ key: info.jwk, format: 'jwk' });
assert.strictEqual(key.type, 'private');
assert.strictEqual(key.asymmetricKeyType, keyType);
assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
assert.strictEqual(key.symmetricKeySize, undefined);
assert.strictEqual(
key.export({ type: 'pkcs8', format: 'pem' }), info.private);
assert.deepStrictEqual(
key.export({ format: 'jwk' }), info.jwk);
}
{
for (const input of [
info.private, info.public, { key: info.jwk, format: 'jwk' }]) {
const key = createPublicKey(input);
assert.strictEqual(key.type, 'public');
assert.strictEqual(key.asymmetricKeyType, keyType);
assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
assert.strictEqual(key.symmetricKeySize, undefined);
assert.strictEqual(
key.export({ type: 'spki', format: 'pem' }), info.public);
const jwk = { ...info.jwk };
delete jwk.d;
assert.deepStrictEqual(
key.export({ format: 'jwk' }), jwk);
}
}
});
{
// Reading an encrypted key without a passphrase should fail.
assert.throws(() => createPrivateKey(privateDsa), hasOpenSSL3 ? {
name: 'Error',
message: 'error:07880109:common libcrypto routines::interrupted or ' +
'cancelled',
} : {
name: 'TypeError',
code: 'ERR_MISSING_PASSPHRASE',
message: 'Passphrase required for encrypted key'
});
// Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer
// size limit should fail with an appropriate error code.
assert.throws(() => createPrivateKey({
key: privateDsa,
format: 'pem',
passphrase: Buffer.alloc(1025, 'a')
}), hasOpenSSL3 ? { name: 'Error' } : {
code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ',
name: 'Error'
});
// The buffer has a size of 1024 bytes, so this passphrase should be permitted
// (but will fail decryption).
assert.throws(() => createPrivateKey({
key: privateDsa,
format: 'pem',
passphrase: Buffer.alloc(1024, 'a')
}), {
message: /bad decrypt/
});
const publicKey = createPublicKey(publicDsa);
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
assert.strictEqual(publicKey.symmetricKeySize, undefined);
assert.throws(
() => publicKey.export({ format: 'jwk' }),
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
const privateKey = createPrivateKey({
key: privateDsa,
format: 'pem',
passphrase: 'secret'
});
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
assert.strictEqual(privateKey.symmetricKeySize, undefined);
assert.throws(
() => privateKey.export({ format: 'jwk' }),
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
}
{
// Test RSA-PSS.
{
// This key pair does not restrict the message digest algorithm or salt
// length.
const publicPem = fixtures.readKey('rsa_pss_public_2048.pem');
const privatePem = fixtures.readKey('rsa_pss_private_2048.pem');
const publicKey = createPublicKey(publicPem);
const privateKey = createPrivateKey(privatePem);
// Because no RSASSA-PSS-params appears in the PEM, no defaults should be
// added for the PSS parameters. This is different from an empty
// RSASSA-PSS-params sequence (see test below).
const expectedKeyDetails = {
modulusLength: 2048,
publicExponent: 65537n
};
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
assert.throws(
() => publicKey.export({ format: 'jwk' }),
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
assert.throws(
() => privateKey.export({ format: 'jwk' }),
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
for (const key of [privatePem, privateKey]) {
// Any algorithm should work.
for (const algo of ['sha1', 'sha256']) {
// Any salt length should work.
for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
const signature = createSign(algo)
.update('foo')
.sign({ key, saltLength });
for (const pkey of [key, publicKey, publicPem]) {
const okay = createVerify(algo)
.update('foo')
.verify({ key: pkey, saltLength }, signature);
assert.ok(okay);
}
}
}
}
// Exporting the key using PKCS#1 should not work since this would discard
// any algorithm restrictions.
assert.throws(() => {
publicKey.export({ format: 'pem', type: 'pkcs1' });
}, {
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
});
}
{
// This key pair enforces sha1 as the message digest and the MGF1
// message digest and a salt length of 20 bytes.
const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem');
const privatePem =
fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem');
const publicKey = createPublicKey(publicPem);
const privateKey = createPrivateKey(privatePem);
// Unlike the previous key pair, this key pair contains an RSASSA-PSS-params
// sequence. However, because all values in the RSASSA-PSS-params are set to
// their defaults (see RFC 3447), the ASN.1 structure contains an empty
// sequence. Node.js should add the default values to the key details.
const expectedKeyDetails = {
modulusLength: 2048,
publicExponent: 65537n,
hashAlgorithm: 'sha1',
mgf1HashAlgorithm: 'sha1',
saltLength: 20
};
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
}
{
// This key pair enforces sha256 as the message digest and the MGF1
// message digest and a salt length of at least 16 bytes.
const publicPem =
fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem');
const privatePem =
fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem');
const publicKey = createPublicKey(publicPem);
const privateKey = createPrivateKey(privatePem);
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
for (const key of [privatePem, privateKey]) {
// Signing with anything other than sha256 should fail.
assert.throws(() => {
createSign('sha1').sign(key);
}, /digest not allowed/);
// Signing with salt lengths less than 16 bytes should fail.
for (const saltLength of [8, 10, 12]) {
assert.throws(() => {
createSign('sha1').sign({ key, saltLength });
}, /pss saltlen too small/);
}
// Signing with sha256 and appropriate salt lengths should work.
for (const saltLength of [undefined, 16, 18, 20]) {
const signature = createSign('sha256')
.update('foo')
.sign({ key, saltLength });
for (const pkey of [key, publicKey, publicPem]) {
const okay = createVerify('sha256')
.update('foo')
.verify({ key: pkey, saltLength }, signature);
assert.ok(okay);
}
}
}
}
{
// This key enforces sha512 as the message digest and sha256 as the MGF1
// message digest.
const publicPem =
fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem');
const privatePem =
fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem');
const publicKey = createPublicKey(publicPem);
const privateKey = createPrivateKey(privatePem);
const expectedKeyDetails = {
modulusLength: 2048,
publicExponent: 65537n,
hashAlgorithm: 'sha512',
mgf1HashAlgorithm: 'sha256',
saltLength: 20
};
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
// Node.js usually uses the same hash function for the message and for MGF1.
// However, when a different MGF1 message digest algorithm has been
// specified as part of the key, it should automatically switch to that.
// This behavior is required by sections 3.1 and 3.3 of RFC4055.
for (const key of [privatePem, privateKey]) {
// sha256 matches the MGF1 hash function and should be used internally,
// but it should not be permitted as the main message digest algorithm.
for (const algo of ['sha1', 'sha256']) {
assert.throws(() => {
createSign(algo).sign(key);
}, /digest not allowed/);
}
// sha512 should produce a valid signature.
const signature = createSign('sha512')
.update('foo')
.sign(key);
for (const pkey of [key, publicKey, publicPem]) {
const okay = createVerify('sha512')
.update('foo')
.verify(pkey, signature);
assert.ok(okay);
}
}
}
}
{
// Exporting an encrypted private key requires a cipher
const privateKey = createPrivateKey(privatePem);
assert.throws(() => {
privateKey.export({
format: 'pem', type: 'pkcs8', passphrase: 'super-secret'
});
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_VALUE',
message: "The property 'options.cipher' is invalid. Received undefined"
});
}
{
// SecretKeyObject export buffer format (default)
const buffer = Buffer.from('Hello World');
const keyObject = createSecretKey(buffer);
assert.deepStrictEqual(keyObject.export(), buffer);
assert.deepStrictEqual(keyObject.export({}), buffer);
assert.deepStrictEqual(keyObject.export({ format: 'buffer' }), buffer);
assert.deepStrictEqual(keyObject.export({ format: undefined }), buffer);
}
{
// Exporting an "oct" JWK from a SecretKeyObject
const buffer = Buffer.from('Hello World');
const keyObject = createSecretKey(buffer);
assert.deepStrictEqual(
keyObject.export({ format: 'jwk' }),
{ kty: 'oct', k: 'SGVsbG8gV29ybGQ' }
);
}
{
// Exporting a JWK unsupported curve EC key
const supported = ['prime256v1', 'secp256k1', 'secp384r1', 'secp521r1'];
// Find an unsupported curve regardless of whether a FIPS compliant crypto
// provider is currently in use.
const namedCurve = getCurves().find((curve) => !supported.includes(curve));
assert(namedCurve);
const keyPair = generateKeyPairSync('ec', { namedCurve });
const { publicKey, privateKey } = keyPair;
assert.throws(
() => publicKey.export({ format: 'jwk' }),
{
code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
message: `Unsupported JWK EC curve: ${namedCurve}.`
});
assert.throws(
() => privateKey.export({ format: 'jwk' }),
{
code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
message: `Unsupported JWK EC curve: ${namedCurve}.`
});
}
{
const first = Buffer.from('Hello');
const second = Buffer.from('World');
const keyObject = createSecretKey(first);
assert(createSecretKey(first).equals(createSecretKey(first)));
assert(!createSecretKey(first).equals(createSecretKey(second)));
assert.throws(() => keyObject.equals(0), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "otherKeyObject" argument must be an instance of KeyObject. Received type number (0)'
});
assert(keyObject.equals(keyObject));
assert(!keyObject.equals(createPublicKey(publicPem)));
assert(!keyObject.equals(createPrivateKey(privatePem)));
}
{
const first = generateKeyPairSync('ed25519');
const second = generateKeyPairSync('ed25519');
const secret = generateKeySync('aes', { length: 128 });
assert(first.publicKey.equals(first.publicKey));
assert(first.publicKey.equals(createPublicKey(
first.publicKey.export({ format: 'pem', type: 'spki' }))));
assert(!first.publicKey.equals(second.publicKey));
assert(!first.publicKey.equals(second.privateKey));
assert(!first.publicKey.equals(secret));
assert(first.privateKey.equals(first.privateKey));
assert(first.privateKey.equals(createPrivateKey(
first.privateKey.export({ format: 'pem', type: 'pkcs8' }))));
assert(!first.privateKey.equals(second.privateKey));
assert(!first.privateKey.equals(second.publicKey));
assert(!first.privateKey.equals(secret));
}
{
const first = generateKeyPairSync('ed25519');
const second = generateKeyPairSync('ed448');
assert(!first.publicKey.equals(second.publicKey));
assert(!first.publicKey.equals(second.privateKey));
assert(!first.privateKey.equals(second.privateKey));
assert(!first.privateKey.equals(second.publicKey));
}
{
const first = createSecretKey(Buffer.alloc(0));
const second = createSecretKey(new ArrayBuffer(0));
const third = createSecretKey(Buffer.alloc(1));
assert(first.equals(first));
assert(first.equals(second));
assert(!first.equals(third));
assert(!third.equals(first));
}
{
// This should not cause a crash: https://github.com/nodejs/node/issues/44471
for (const key of ['', 'foo', null, undefined, true, Boolean]) {
assert.throws(() => {
createPublicKey({ key, format: 'jwk' });
}, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ });
assert.throws(() => {
createPrivateKey({ key, format: 'jwk' });
}, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ });
}
}