node/test/parallel/test-crypto-key-objects.js
响马 91d30f35c4
test, crypto: use correct object on assert
The test case for KeyObject does not really test the creation of
asymmetric cryptographic keys using jwk because of a misspelling of
the variable.

PR-URL: https://github.com/nodejs/node/pull/51820
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
2024-05-12 19:46:19 +00:00

889 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 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: '' });
}, common.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' });
}, common.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), common.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')
}), common.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/ });
}
}