mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
91d30f35c4
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>
889 lines
31 KiB
JavaScript
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/ });
|
|
}
|
|
}
|