| '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 = common.mustCall((fn, ciphertexts, decryptionKeys) => { |
| for (const ciphertext of ciphertexts) { |
| for (const key of decryptionKeys) { |
| const deciphered = fn(key, ciphertext); |
| assert.deepStrictEqual(deciphered, plaintext); |
| } |
| } |
| }, 2); |
| |
| 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/ }); |
| } |
| } |