• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const common = require('../common');
4if (!common.hasCrypto)
5  common.skip('missing crypto');
6
7const assert = require('assert');
8const {
9  createCipheriv,
10  createDecipheriv,
11  createSign,
12  createVerify,
13  createSecretKey,
14  createPublicKey,
15  createPrivateKey,
16  KeyObject,
17  randomBytes,
18  publicDecrypt,
19  publicEncrypt,
20  privateDecrypt,
21  privateEncrypt,
22  getCurves,
23  generateKeySync,
24  generateKeyPairSync,
25} = require('crypto');
26
27const fixtures = require('../common/fixtures');
28
29const publicPem = fixtures.readKey('rsa_public.pem', 'ascii');
30const privatePem = fixtures.readKey('rsa_private.pem', 'ascii');
31
32const publicDsa = fixtures.readKey('dsa_public_1025.pem', 'ascii');
33const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
34                                    'ascii');
35
36{
37  // Attempting to create a key of a wrong type should throw
38  const TYPE = 'wrong_type';
39
40  assert.throws(() => new KeyObject(TYPE), {
41    name: 'TypeError',
42    code: 'ERR_INVALID_ARG_VALUE',
43    message: `The argument 'type' is invalid. Received '${TYPE}'`
44  });
45}
46
47{
48  // Attempting to create a key with non-object handle should throw
49  assert.throws(() => new KeyObject('secret', ''), {
50    name: 'TypeError',
51    code: 'ERR_INVALID_ARG_TYPE',
52    message:
53      'The "handle" argument must be of type object. Received type ' +
54      "string ('')"
55  });
56}
57
58{
59  assert.throws(() => KeyObject.from('invalid_key'), {
60    name: 'TypeError',
61    code: 'ERR_INVALID_ARG_TYPE',
62    message:
63      'The "key" argument must be an instance of CryptoKey. Received type ' +
64      "string ('invalid_key')"
65  });
66}
67
68{
69  const keybuf = randomBytes(32);
70  const key = createSecretKey(keybuf);
71  assert.strictEqual(key.type, 'secret');
72  assert.strictEqual(key.toString(), '[object KeyObject]');
73  assert.strictEqual(key.symmetricKeySize, 32);
74  assert.strictEqual(key.asymmetricKeyType, undefined);
75  assert.strictEqual(key.asymmetricKeyDetails, undefined);
76
77  const exportedKey = key.export();
78  assert(keybuf.equals(exportedKey));
79
80  const plaintext = Buffer.from('Hello world', 'utf8');
81
82  const cipher = createCipheriv('aes-256-ecb', key, null);
83  const ciphertext = Buffer.concat([
84    cipher.update(plaintext), cipher.final(),
85  ]);
86
87  const decipher = createDecipheriv('aes-256-ecb', key, null);
88  const deciphered = Buffer.concat([
89    decipher.update(ciphertext), decipher.final(),
90  ]);
91
92  assert(plaintext.equals(deciphered));
93}
94
95{
96  // Passing an existing public key object to createPublicKey should throw.
97  const publicKey = createPublicKey(publicPem);
98  assert.throws(() => createPublicKey(publicKey), {
99    name: 'TypeError',
100    code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
101    message: 'Invalid key object type public, expected private.'
102  });
103
104  // Constructing a private key from a public key should be impossible, even
105  // if the public key was derived from a private key.
106  assert.throws(() => createPrivateKey(createPublicKey(privatePem)), {
107    name: 'TypeError',
108    code: 'ERR_INVALID_ARG_TYPE',
109  });
110
111  // Similarly, passing an existing private key object to createPrivateKey
112  // should throw.
113  const privateKey = createPrivateKey(privatePem);
114  assert.throws(() => createPrivateKey(privateKey), {
115    name: 'TypeError',
116    code: 'ERR_INVALID_ARG_TYPE',
117  });
118}
119
120{
121  const jwk = {
122    e: 'AQAB',
123    n: 't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +
124       '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +
125       'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +
126       'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +
127       'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',
128    d: 'ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0' +
129       'Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW' +
130       '5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2' +
131       'fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-' +
132       'Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ',
133    p: '8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B' +
134       'kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp' +
135       'bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8',
136    q: 'wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X' +
137       'PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI' +
138       'jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs',
139    dp: 'qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr' +
140       '6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN' +
141       'Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8',
142    dq: 'WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_' +
143       'Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ' +
144       'fZabRRiI0VQR472300AVEeX4vgbrDBn600',
145    qi: 'k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl' +
146       'D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX' +
147       'ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM',
148    kty: 'RSA',
149  };
150  const publicJwk = { kty: jwk.kty, e: jwk.e, n: jwk.n };
151
152  const publicKey = createPublicKey(publicPem);
153  assert.strictEqual(publicKey.type, 'public');
154  assert.strictEqual(publicKey.toString(), '[object KeyObject]');
155  assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
156  assert.strictEqual(publicKey.symmetricKeySize, undefined);
157
158  const privateKey = createPrivateKey(privatePem);
159  assert.strictEqual(privateKey.type, 'private');
160  assert.strictEqual(privateKey.toString(), '[object KeyObject]');
161  assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
162  assert.strictEqual(privateKey.symmetricKeySize, undefined);
163
164  // It should be possible to derive a public key from a private key.
165  const derivedPublicKey = createPublicKey(privateKey);
166  assert.strictEqual(derivedPublicKey.type, 'public');
167  assert.strictEqual(derivedPublicKey.toString(), '[object KeyObject]');
168  assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa');
169  assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined);
170
171  const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: 'jwk' });
172  assert.strictEqual(publicKey.type, 'public');
173  assert.strictEqual(publicKey.toString(), '[object KeyObject]');
174  assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
175  assert.strictEqual(publicKey.symmetricKeySize, undefined);
176
177  const privateKeyFromJwk = createPrivateKey({ key: jwk, format: 'jwk' });
178  assert.strictEqual(privateKey.type, 'private');
179  assert.strictEqual(privateKey.toString(), '[object KeyObject]');
180  assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
181  assert.strictEqual(privateKey.symmetricKeySize, undefined);
182
183  // It should also be possible to import an encrypted private key as a public
184  // key.
185  const decryptedKey = createPublicKey({
186    key: privateKey.export({
187      type: 'pkcs8',
188      format: 'pem',
189      passphrase: '123',
190      cipher: 'aes-128-cbc'
191    }),
192    format: 'pem',
193    passphrase: '123'
194  });
195  assert.strictEqual(decryptedKey.type, 'public');
196  assert.strictEqual(decryptedKey.asymmetricKeyType, 'rsa');
197
198  // Test exporting with an invalid options object, this should throw.
199  for (const opt of [undefined, null, 'foo', 0, NaN]) {
200    assert.throws(() => publicKey.export(opt), {
201      name: 'TypeError',
202      code: 'ERR_INVALID_ARG_TYPE',
203      message: /^The "options" argument must be of type object/
204    });
205  }
206
207  for (const keyObject of [publicKey, derivedPublicKey, publicKeyFromJwk]) {
208    assert.deepStrictEqual(
209      keyObject.export({ format: 'jwk' }),
210      { kty: 'RSA', n: jwk.n, e: jwk.e }
211    );
212  }
213
214  for (const keyObject of [privateKey, privateKeyFromJwk]) {
215    assert.deepStrictEqual(
216      keyObject.export({ format: 'jwk' }),
217      jwk
218    );
219  }
220
221  // Exporting the key using JWK should not work since this format does not
222  // support key encryption
223  assert.throws(() => {
224    privateKey.export({ format: 'jwk', passphrase: 'secret' });
225  }, {
226    message: 'The selected key encoding jwk does not support encryption.',
227    code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
228  });
229
230  const publicDER = publicKey.export({
231    format: 'der',
232    type: 'pkcs1'
233  });
234
235  const privateDER = privateKey.export({
236    format: 'der',
237    type: 'pkcs1'
238  });
239
240  assert(Buffer.isBuffer(publicDER));
241  assert(Buffer.isBuffer(privateDER));
242
243  const plaintext = Buffer.from('Hello world', 'utf8');
244  const testDecryption = (fn, ciphertexts, decryptionKeys) => {
245    for (const ciphertext of ciphertexts) {
246      for (const key of decryptionKeys) {
247        const deciphered = fn(key, ciphertext);
248        assert.deepStrictEqual(deciphered, plaintext);
249      }
250    }
251  };
252
253  testDecryption(privateDecrypt, [
254    // Encrypt using the public key.
255    publicEncrypt(publicKey, plaintext),
256    publicEncrypt({ key: publicKey }, plaintext),
257    publicEncrypt({ key: publicJwk, format: 'jwk' }, plaintext),
258
259    // Encrypt using the private key.
260    publicEncrypt(privateKey, plaintext),
261    publicEncrypt({ key: privateKey }, plaintext),
262    publicEncrypt({ key: jwk, format: 'jwk' }, plaintext),
263
264    // Encrypt using a public key derived from the private key.
265    publicEncrypt(derivedPublicKey, plaintext),
266    publicEncrypt({ key: derivedPublicKey }, plaintext),
267
268    // Test distinguishing PKCS#1 public and private keys based on the
269    // DER-encoded data only.
270    publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext),
271    publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext),
272  ], [
273    privateKey,
274    { format: 'pem', key: privatePem },
275    { format: 'der', type: 'pkcs1', key: privateDER },
276    { key: jwk, format: 'jwk' },
277  ]);
278
279  testDecryption(publicDecrypt, [
280    privateEncrypt(privateKey, plaintext),
281  ], [
282    // Decrypt using the public key.
283    publicKey,
284    { format: 'pem', key: publicPem },
285    { format: 'der', type: 'pkcs1', key: publicDER },
286    { key: publicJwk, format: 'jwk' },
287
288    // Decrypt using the private key.
289    privateKey,
290    { format: 'pem', key: privatePem },
291    { format: 'der', type: 'pkcs1', key: privateDER },
292    { key: jwk, format: 'jwk' },
293  ]);
294}
295
296{
297  // This should not cause a crash: https://github.com/nodejs/node/issues/25247
298  assert.throws(() => {
299    createPrivateKey({ key: '' });
300  }, common.hasOpenSSL3 ? {
301    message: 'error:1E08010C:DECODER routines::unsupported',
302  } : {
303    message: 'error:0909006C:PEM routines:get_name:no start line',
304    code: 'ERR_OSSL_PEM_NO_START_LINE',
305    reason: 'no start line',
306    library: 'PEM routines',
307    function: 'get_name',
308  });
309
310  // This should not abort either: https://github.com/nodejs/node/issues/29904
311  assert.throws(() => {
312    createPrivateKey({ key: Buffer.alloc(0), format: 'der', type: 'spki' });
313  }, {
314    code: 'ERR_INVALID_ARG_VALUE',
315    message: "The property 'options.type' is invalid. Received 'spki'"
316  });
317
318  // Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys),
319  // so it should be accepted by createPrivateKey, but OpenSSL won't parse it.
320  assert.throws(() => {
321    const key = createPublicKey(publicPem).export({
322      format: 'der',
323      type: 'pkcs1'
324    });
325    createPrivateKey({ key, format: 'der', type: 'pkcs1' });
326  }, common.hasOpenSSL3 ? {
327    message: /error:1E08010C:DECODER routines::unsupported/,
328    library: 'DECODER routines'
329  } : {
330    message: /asn1 encoding/,
331    library: 'asn1 encoding routines'
332  });
333}
334
335[
336  { private: fixtures.readKey('ed25519_private.pem', 'ascii'),
337    public: fixtures.readKey('ed25519_public.pem', 'ascii'),
338    keyType: 'ed25519',
339    jwk: {
340      crv: 'Ed25519',
341      x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',
342      d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',
343      kty: 'OKP'
344    } },
345  { private: fixtures.readKey('ed448_private.pem', 'ascii'),
346    public: fixtures.readKey('ed448_public.pem', 'ascii'),
347    keyType: 'ed448',
348    jwk: {
349      crv: 'Ed448',
350      x: 'oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o' +
351         'Dgc2V5ZUA',
352      d: '060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX' +
353         'jcR9mxppY',
354      kty: 'OKP'
355    } },
356  { private: fixtures.readKey('x25519_private.pem', 'ascii'),
357    public: fixtures.readKey('x25519_public.pem', 'ascii'),
358    keyType: 'x25519',
359    jwk: {
360      crv: 'X25519',
361      x: 'aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig',
362      d: 'mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc',
363      kty: 'OKP'
364    } },
365  { private: fixtures.readKey('x448_private.pem', 'ascii'),
366    public: fixtures.readKey('x448_public.pem', 'ascii'),
367    keyType: 'x448',
368    jwk: {
369      crv: 'X448',
370      x: 'ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg' +
371         'vSKsDFPA',
372      d: 'tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy' +
373         'S0jlSYJk',
374      kty: 'OKP'
375    } },
376].forEach((info) => {
377  const keyType = info.keyType;
378
379  {
380    const key = createPrivateKey(info.private);
381    assert.strictEqual(key.type, 'private');
382    assert.strictEqual(key.asymmetricKeyType, keyType);
383    assert.strictEqual(key.symmetricKeySize, undefined);
384    assert.strictEqual(
385      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
386    assert.deepStrictEqual(
387      key.export({ format: 'jwk' }), info.jwk);
388  }
389
390  {
391    const key = createPrivateKey({ key: info.jwk, format: 'jwk' });
392    assert.strictEqual(key.type, 'private');
393    assert.strictEqual(key.asymmetricKeyType, keyType);
394    assert.strictEqual(key.symmetricKeySize, undefined);
395    assert.strictEqual(
396      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
397    assert.deepStrictEqual(
398      key.export({ format: 'jwk' }), info.jwk);
399  }
400
401  {
402    for (const input of [
403      info.private, info.public, { key: info.jwk, format: 'jwk' }]) {
404      const key = createPublicKey(input);
405      assert.strictEqual(key.type, 'public');
406      assert.strictEqual(key.asymmetricKeyType, keyType);
407      assert.strictEqual(key.symmetricKeySize, undefined);
408      assert.strictEqual(
409        key.export({ type: 'spki', format: 'pem' }), info.public);
410      const jwk = { ...info.jwk };
411      delete jwk.d;
412      assert.deepStrictEqual(
413        key.export({ format: 'jwk' }), jwk);
414    }
415  }
416});
417
418[
419  { private: fixtures.readKey('ec_p256_private.pem', 'ascii'),
420    public: fixtures.readKey('ec_p256_public.pem', 'ascii'),
421    keyType: 'ec',
422    namedCurve: 'prime256v1',
423    jwk: {
424      crv: 'P-256',
425      d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo',
426      kty: 'EC',
427      x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',
428      y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI'
429    } },
430  { private: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'),
431    public: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'),
432    keyType: 'ec',
433    namedCurve: 'secp256k1',
434    jwk: {
435      crv: 'secp256k1',
436      d: 'c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM',
437      kty: 'EC',
438      x: 'cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA',
439      y: '-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo'
440    } },
441  { private: fixtures.readKey('ec_p384_private.pem', 'ascii'),
442    public: fixtures.readKey('ec_p384_public.pem', 'ascii'),
443    keyType: 'ec',
444    namedCurve: 'secp384r1',
445    jwk: {
446      crv: 'P-384',
447      d: 'dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi',
448      kty: 'EC',
449      x: 'hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV',
450      y: 'fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl'
451    } },
452  { private: fixtures.readKey('ec_p521_private.pem', 'ascii'),
453    public: fixtures.readKey('ec_p521_public.pem', 'ascii'),
454    keyType: 'ec',
455    namedCurve: 'secp521r1',
456    jwk: {
457      crv: 'P-521',
458      d: 'ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluPJMaaHcNer' +
459         'bQH_WdVkLLX86ShlHrRyJ',
460      kty: 'EC',
461      x: 'AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL' +
462         'CbhMeHRavUS6P10rsTtBn',
463      y: 'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB' +
464         'cvA2iFJRUyQ3whC00j0Np'
465    } },
466].forEach((info) => {
467  const { keyType, namedCurve } = info;
468
469  {
470    const key = createPrivateKey(info.private);
471    assert.strictEqual(key.type, 'private');
472    assert.strictEqual(key.asymmetricKeyType, keyType);
473    assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
474    assert.strictEqual(key.symmetricKeySize, undefined);
475    assert.strictEqual(
476      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
477    assert.deepStrictEqual(
478      key.export({ format: 'jwk' }), info.jwk);
479  }
480
481  {
482    const key = createPrivateKey({ key: info.jwk, format: 'jwk' });
483    assert.strictEqual(key.type, 'private');
484    assert.strictEqual(key.asymmetricKeyType, keyType);
485    assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
486    assert.strictEqual(key.symmetricKeySize, undefined);
487    assert.strictEqual(
488      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
489    assert.deepStrictEqual(
490      key.export({ format: 'jwk' }), info.jwk);
491  }
492
493  {
494    for (const input of [
495      info.private, info.public, { key: info.jwk, format: 'jwk' }]) {
496      const key = createPublicKey(input);
497      assert.strictEqual(key.type, 'public');
498      assert.strictEqual(key.asymmetricKeyType, keyType);
499      assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
500      assert.strictEqual(key.symmetricKeySize, undefined);
501      assert.strictEqual(
502        key.export({ type: 'spki', format: 'pem' }), info.public);
503      const jwk = { ...info.jwk };
504      delete jwk.d;
505      assert.deepStrictEqual(
506        key.export({ format: 'jwk' }), jwk);
507    }
508  }
509});
510
511{
512  // Reading an encrypted key without a passphrase should fail.
513  assert.throws(() => createPrivateKey(privateDsa), common.hasOpenSSL3 ? {
514    name: 'Error',
515    message: 'error:07880109:common libcrypto routines::interrupted or ' +
516             'cancelled',
517  } : {
518    name: 'TypeError',
519    code: 'ERR_MISSING_PASSPHRASE',
520    message: 'Passphrase required for encrypted key'
521  });
522
523  // Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer
524  // size limit should fail with an appropriate error code.
525  assert.throws(() => createPrivateKey({
526    key: privateDsa,
527    format: 'pem',
528    passphrase: Buffer.alloc(1025, 'a')
529  }), common.hasOpenSSL3 ? { name: 'Error' } : {
530    code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ',
531    name: 'Error'
532  });
533
534  // The buffer has a size of 1024 bytes, so this passphrase should be permitted
535  // (but will fail decryption).
536  assert.throws(() => createPrivateKey({
537    key: privateDsa,
538    format: 'pem',
539    passphrase: Buffer.alloc(1024, 'a')
540  }), {
541    message: /bad decrypt/
542  });
543
544  const publicKey = createPublicKey(publicDsa);
545  assert.strictEqual(publicKey.type, 'public');
546  assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
547  assert.strictEqual(publicKey.symmetricKeySize, undefined);
548  assert.throws(
549    () => publicKey.export({ format: 'jwk' }),
550    { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
551
552  const privateKey = createPrivateKey({
553    key: privateDsa,
554    format: 'pem',
555    passphrase: 'secret'
556  });
557  assert.strictEqual(privateKey.type, 'private');
558  assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
559  assert.strictEqual(privateKey.symmetricKeySize, undefined);
560  assert.throws(
561    () => privateKey.export({ format: 'jwk' }),
562    { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
563}
564
565{
566  // Test RSA-PSS.
567  {
568    // This key pair does not restrict the message digest algorithm or salt
569    // length.
570    const publicPem = fixtures.readKey('rsa_pss_public_2048.pem');
571    const privatePem = fixtures.readKey('rsa_pss_private_2048.pem');
572
573    const publicKey = createPublicKey(publicPem);
574    const privateKey = createPrivateKey(privatePem);
575
576    // Because no RSASSA-PSS-params appears in the PEM, no defaults should be
577    // added for the PSS parameters. This is different from an empty
578    // RSASSA-PSS-params sequence (see test below).
579    const expectedKeyDetails = {
580      modulusLength: 2048,
581      publicExponent: 65537n
582    };
583
584    assert.strictEqual(publicKey.type, 'public');
585    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
586    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
587
588    assert.strictEqual(privateKey.type, 'private');
589    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
590    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
591
592    assert.throws(
593      () => publicKey.export({ format: 'jwk' }),
594      { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
595    assert.throws(
596      () => privateKey.export({ format: 'jwk' }),
597      { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
598
599    for (const key of [privatePem, privateKey]) {
600      // Any algorithm should work.
601      for (const algo of ['sha1', 'sha256']) {
602        // Any salt length should work.
603        for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
604          const signature = createSign(algo)
605                            .update('foo')
606                            .sign({ key, saltLength });
607
608          for (const pkey of [key, publicKey, publicPem]) {
609            const okay = createVerify(algo)
610                         .update('foo')
611                         .verify({ key: pkey, saltLength }, signature);
612
613            assert.ok(okay);
614          }
615        }
616      }
617    }
618
619    // Exporting the key using PKCS#1 should not work since this would discard
620    // any algorithm restrictions.
621    assert.throws(() => {
622      publicKey.export({ format: 'pem', type: 'pkcs1' });
623    }, {
624      code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
625    });
626  }
627
628  {
629    // This key pair enforces sha1 as the message digest and the MGF1
630    // message digest and a salt length of 20 bytes.
631
632    const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem');
633    const privatePem =
634        fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem');
635
636    const publicKey = createPublicKey(publicPem);
637    const privateKey = createPrivateKey(privatePem);
638
639    // Unlike the previous key pair, this key pair contains an RSASSA-PSS-params
640    // sequence. However, because all values in the RSASSA-PSS-params are set to
641    // their defaults (see RFC 3447), the ASN.1 structure contains an empty
642    // sequence. Node.js should add the default values to the key details.
643    const expectedKeyDetails = {
644      modulusLength: 2048,
645      publicExponent: 65537n,
646      hashAlgorithm: 'sha1',
647      mgf1HashAlgorithm: 'sha1',
648      saltLength: 20
649    };
650
651    assert.strictEqual(publicKey.type, 'public');
652    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
653    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
654
655    assert.strictEqual(privateKey.type, 'private');
656    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
657    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
658  }
659
660  {
661    // This key pair enforces sha256 as the message digest and the MGF1
662    // message digest and a salt length of at least 16 bytes.
663    const publicPem =
664      fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem');
665    const privatePem =
666      fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem');
667
668    const publicKey = createPublicKey(publicPem);
669    const privateKey = createPrivateKey(privatePem);
670
671    assert.strictEqual(publicKey.type, 'public');
672    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
673
674    assert.strictEqual(privateKey.type, 'private');
675    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
676
677    for (const key of [privatePem, privateKey]) {
678      // Signing with anything other than sha256 should fail.
679      assert.throws(() => {
680        createSign('sha1').sign(key);
681      }, /digest not allowed/);
682
683      // Signing with salt lengths less than 16 bytes should fail.
684      for (const saltLength of [8, 10, 12]) {
685        assert.throws(() => {
686          createSign('sha1').sign({ key, saltLength });
687        }, /pss saltlen too small/);
688      }
689
690      // Signing with sha256 and appropriate salt lengths should work.
691      for (const saltLength of [undefined, 16, 18, 20]) {
692        const signature = createSign('sha256')
693                          .update('foo')
694                          .sign({ key, saltLength });
695
696        for (const pkey of [key, publicKey, publicPem]) {
697          const okay = createVerify('sha256')
698                       .update('foo')
699                       .verify({ key: pkey, saltLength }, signature);
700
701          assert.ok(okay);
702        }
703      }
704    }
705  }
706
707  {
708    // This key enforces sha512 as the message digest and sha256 as the MGF1
709    // message digest.
710    const publicPem =
711      fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem');
712    const privatePem =
713      fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem');
714
715    const publicKey = createPublicKey(publicPem);
716    const privateKey = createPrivateKey(privatePem);
717
718    const expectedKeyDetails = {
719      modulusLength: 2048,
720      publicExponent: 65537n,
721      hashAlgorithm: 'sha512',
722      mgf1HashAlgorithm: 'sha256',
723      saltLength: 20
724    };
725
726    assert.strictEqual(publicKey.type, 'public');
727    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
728    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
729
730    assert.strictEqual(privateKey.type, 'private');
731    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
732    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
733
734    // Node.js usually uses the same hash function for the message and for MGF1.
735    // However, when a different MGF1 message digest algorithm has been
736    // specified as part of the key, it should automatically switch to that.
737    // This behavior is required by sections 3.1 and 3.3 of RFC4055.
738    for (const key of [privatePem, privateKey]) {
739      // sha256 matches the MGF1 hash function and should be used internally,
740      // but it should not be permitted as the main message digest algorithm.
741      for (const algo of ['sha1', 'sha256']) {
742        assert.throws(() => {
743          createSign(algo).sign(key);
744        }, /digest not allowed/);
745      }
746
747      // sha512 should produce a valid signature.
748      const signature = createSign('sha512')
749                        .update('foo')
750                        .sign(key);
751
752      for (const pkey of [key, publicKey, publicPem]) {
753        const okay = createVerify('sha512')
754                     .update('foo')
755                     .verify(pkey, signature);
756
757        assert.ok(okay);
758      }
759    }
760  }
761}
762
763{
764  // Exporting an encrypted private key requires a cipher
765  const privateKey = createPrivateKey(privatePem);
766  assert.throws(() => {
767    privateKey.export({
768      format: 'pem', type: 'pkcs8', passphrase: 'super-secret'
769    });
770  }, {
771    name: 'TypeError',
772    code: 'ERR_INVALID_ARG_VALUE',
773    message: "The property 'options.cipher' is invalid. Received undefined"
774  });
775}
776
777{
778  // SecretKeyObject export buffer format (default)
779  const buffer = Buffer.from('Hello World');
780  const keyObject = createSecretKey(buffer);
781  assert.deepStrictEqual(keyObject.export(), buffer);
782  assert.deepStrictEqual(keyObject.export({}), buffer);
783  assert.deepStrictEqual(keyObject.export({ format: 'buffer' }), buffer);
784  assert.deepStrictEqual(keyObject.export({ format: undefined }), buffer);
785}
786
787{
788  // Exporting an "oct" JWK from a SecretKeyObject
789  const buffer = Buffer.from('Hello World');
790  const keyObject = createSecretKey(buffer);
791  assert.deepStrictEqual(
792    keyObject.export({ format: 'jwk' }),
793    { kty: 'oct', k: 'SGVsbG8gV29ybGQ' }
794  );
795}
796
797{
798  // Exporting a JWK unsupported curve EC key
799  const supported = ['prime256v1', 'secp256k1', 'secp384r1', 'secp521r1'];
800  // Find an unsupported curve regardless of whether a FIPS compliant crypto
801  // provider is currently in use.
802  const namedCurve = getCurves().find((curve) => !supported.includes(curve));
803  assert(namedCurve);
804  const keyPair = generateKeyPairSync('ec', { namedCurve });
805  const { publicKey, privateKey } = keyPair;
806  assert.throws(
807    () => publicKey.export({ format: 'jwk' }),
808    {
809      code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
810      message: `Unsupported JWK EC curve: ${namedCurve}.`
811    });
812  assert.throws(
813    () => privateKey.export({ format: 'jwk' }),
814    {
815      code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
816      message: `Unsupported JWK EC curve: ${namedCurve}.`
817    });
818}
819
820{
821  const first = Buffer.from('Hello');
822  const second = Buffer.from('World');
823  const keyObject = createSecretKey(first);
824  assert(createSecretKey(first).equals(createSecretKey(first)));
825  assert(!createSecretKey(first).equals(createSecretKey(second)));
826
827  assert.throws(() => keyObject.equals(0), {
828    name: 'TypeError',
829    code: 'ERR_INVALID_ARG_TYPE',
830    message: 'The "otherKeyObject" argument must be an instance of KeyObject. Received type number (0)'
831  });
832
833  assert(keyObject.equals(keyObject));
834  assert(!keyObject.equals(createPublicKey(publicPem)));
835  assert(!keyObject.equals(createPrivateKey(privatePem)));
836}
837
838{
839  const first = generateKeyPairSync('ed25519');
840  const second = generateKeyPairSync('ed25519');
841  const secret = generateKeySync('aes', { length: 128 });
842
843  assert(first.publicKey.equals(first.publicKey));
844  assert(first.publicKey.equals(createPublicKey(
845    first.publicKey.export({ format: 'pem', type: 'spki' }))));
846  assert(!first.publicKey.equals(second.publicKey));
847  assert(!first.publicKey.equals(second.privateKey));
848  assert(!first.publicKey.equals(secret));
849
850  assert(first.privateKey.equals(first.privateKey));
851  assert(first.privateKey.equals(createPrivateKey(
852    first.privateKey.export({ format: 'pem', type: 'pkcs8' }))));
853  assert(!first.privateKey.equals(second.privateKey));
854  assert(!first.privateKey.equals(second.publicKey));
855  assert(!first.privateKey.equals(secret));
856}
857
858{
859  const first = generateKeyPairSync('ed25519');
860  const second = generateKeyPairSync('ed448');
861
862  assert(!first.publicKey.equals(second.publicKey));
863  assert(!first.publicKey.equals(second.privateKey));
864  assert(!first.privateKey.equals(second.privateKey));
865  assert(!first.privateKey.equals(second.publicKey));
866}
867
868{
869  const first = createSecretKey(Buffer.alloc(0));
870  const second = createSecretKey(new ArrayBuffer(0));
871  const third = createSecretKey(Buffer.alloc(1));
872  assert(first.equals(first));
873  assert(first.equals(second));
874  assert(!first.equals(third));
875  assert(!third.equals(first));
876}
877
878{
879  // This should not cause a crash: https://github.com/nodejs/node/issues/44471
880  for (const key of ['', 'foo', null, undefined, true, Boolean]) {
881    assert.throws(() => {
882      createPublicKey({ key, format: 'jwk' });
883    }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ });
884    assert.throws(() => {
885      createPrivateKey({ key, format: 'jwk' });
886    }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ });
887  }
888}
889