• 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} = require('crypto');
23
24const fixtures = require('../common/fixtures');
25
26const publicPem = fixtures.readKey('rsa_public.pem', 'ascii');
27const privatePem = fixtures.readKey('rsa_private.pem', 'ascii');
28
29const publicDsa = fixtures.readKey('dsa_public_1025.pem', 'ascii');
30const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
31                                    'ascii');
32
33{
34  // Attempting to create an empty key should throw.
35  assert.throws(() => {
36    createSecretKey(Buffer.alloc(0));
37  }, {
38    name: 'RangeError',
39    code: 'ERR_OUT_OF_RANGE',
40    message: 'The value of "key.byteLength" is out of range. ' +
41             'It must be > 0. Received 0'
42  });
43}
44
45{
46  // Attempting to create a key of a wrong type should throw
47  const TYPE = 'wrong_type';
48
49  assert.throws(() => new KeyObject(TYPE), {
50    name: 'TypeError',
51    code: 'ERR_INVALID_ARG_VALUE',
52    message: `The argument 'type' is invalid. Received '${TYPE}'`
53  });
54}
55
56{
57  // Attempting to create a key with non-object handle should throw
58  assert.throws(() => new KeyObject('secret', ''), {
59    name: 'TypeError',
60    code: 'ERR_INVALID_ARG_TYPE',
61    message:
62      'The "handle" argument must be of type object. Received type ' +
63      "string ('')"
64  });
65}
66
67{
68  const keybuf = randomBytes(32);
69  const key = createSecretKey(keybuf);
70  assert.strictEqual(key.type, 'secret');
71  assert.strictEqual(key.symmetricKeySize, 32);
72  assert.strictEqual(key.asymmetricKeyType, undefined);
73
74  const exportedKey = key.export();
75  assert(keybuf.equals(exportedKey));
76
77  const plaintext = Buffer.from('Hello world', 'utf8');
78
79  const cipher = createCipheriv('aes-256-ecb', key, null);
80  const ciphertext = Buffer.concat([
81    cipher.update(plaintext), cipher.final(),
82  ]);
83
84  const decipher = createDecipheriv('aes-256-ecb', key, null);
85  const deciphered = Buffer.concat([
86    decipher.update(ciphertext), decipher.final(),
87  ]);
88
89  assert(plaintext.equals(deciphered));
90}
91
92{
93  // Passing an existing public key object to createPublicKey should throw.
94  const publicKey = createPublicKey(publicPem);
95  assert.throws(() => createPublicKey(publicKey), {
96    name: 'TypeError',
97    code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
98    message: 'Invalid key object type public, expected private.'
99  });
100
101  // Constructing a private key from a public key should be impossible, even
102  // if the public key was derived from a private key.
103  assert.throws(() => createPrivateKey(createPublicKey(privatePem)), {
104    name: 'TypeError',
105    code: 'ERR_INVALID_ARG_TYPE',
106    message: 'The "key" argument must be of type string or an instance of ' +
107             'Buffer, TypedArray, or DataView. Received an instance of ' +
108             'PublicKeyObject'
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    message: 'The "key" argument must be of type string or an instance of ' +
118             'Buffer, TypedArray, or DataView. Received an instance of ' +
119             'PrivateKeyObject'
120  });
121}
122
123{
124  const publicKey = createPublicKey(publicPem);
125  assert.strictEqual(publicKey.type, 'public');
126  assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
127  assert.strictEqual(publicKey.symmetricKeySize, undefined);
128
129  const privateKey = createPrivateKey(privatePem);
130  assert.strictEqual(privateKey.type, 'private');
131  assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
132  assert.strictEqual(privateKey.symmetricKeySize, undefined);
133
134  // It should be possible to derive a public key from a private key.
135  const derivedPublicKey = createPublicKey(privateKey);
136  assert.strictEqual(derivedPublicKey.type, 'public');
137  assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa');
138  assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined);
139
140  // Test exporting with an invalid options object, this should throw.
141  for (const opt of [undefined, null, 'foo', 0, NaN]) {
142    assert.throws(() => publicKey.export(opt), {
143      name: 'TypeError',
144      code: 'ERR_INVALID_ARG_TYPE',
145      message: /^The "options" argument must be of type object/
146    });
147  }
148
149  const publicDER = publicKey.export({
150    format: 'der',
151    type: 'pkcs1'
152  });
153
154  const privateDER = privateKey.export({
155    format: 'der',
156    type: 'pkcs1'
157  });
158
159  assert(Buffer.isBuffer(publicDER));
160  assert(Buffer.isBuffer(privateDER));
161
162  const plaintext = Buffer.from('Hello world', 'utf8');
163  const testDecryption = (fn, ciphertexts, decryptionKeys) => {
164    for (const ciphertext of ciphertexts) {
165      for (const key of decryptionKeys) {
166        const deciphered = fn(key, ciphertext);
167        assert.deepStrictEqual(deciphered, plaintext);
168      }
169    }
170  };
171
172  testDecryption(privateDecrypt, [
173    // Encrypt using the public key.
174    publicEncrypt(publicKey, plaintext),
175    publicEncrypt({ key: publicKey }, plaintext),
176
177    // Encrypt using the private key.
178    publicEncrypt(privateKey, plaintext),
179    publicEncrypt({ key: privateKey }, plaintext),
180
181    // Encrypt using a public key derived from the private key.
182    publicEncrypt(derivedPublicKey, plaintext),
183    publicEncrypt({ key: derivedPublicKey }, plaintext),
184
185    // Test distinguishing PKCS#1 public and private keys based on the
186    // DER-encoded data only.
187    publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext),
188    publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext),
189  ], [
190    privateKey,
191    { format: 'pem', key: privatePem },
192    { format: 'der', type: 'pkcs1', key: privateDER },
193  ]);
194
195  testDecryption(publicDecrypt, [
196    privateEncrypt(privateKey, plaintext),
197  ], [
198    // Decrypt using the public key.
199    publicKey,
200    { format: 'pem', key: publicPem },
201    { format: 'der', type: 'pkcs1', key: publicDER },
202
203    // Decrypt using the private key.
204    privateKey,
205    { format: 'pem', key: privatePem },
206    { format: 'der', type: 'pkcs1', key: privateDER },
207  ]);
208}
209
210{
211  // This should not cause a crash: https://github.com/nodejs/node/issues/25247
212  assert.throws(() => {
213    createPrivateKey({ key: '' });
214  }, {
215    message: 'error:2007E073:BIO routines:BIO_new_mem_buf:null parameter',
216    code: 'ERR_OSSL_BIO_NULL_PARAMETER',
217    reason: 'null parameter',
218    library: 'BIO routines',
219    function: 'BIO_new_mem_buf',
220  });
221
222  // This should not abort either: https://github.com/nodejs/node/issues/29904
223  assert.throws(() => {
224    createPrivateKey({ key: Buffer.alloc(0), format: 'der', type: 'spki' });
225  }, {
226    code: 'ERR_INVALID_OPT_VALUE',
227    message: 'The value "spki" is invalid for option "type"'
228  });
229
230  // Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys),
231  // so it should be accepted by createPrivateKey, but OpenSSL won't parse it.
232  assert.throws(() => {
233    const key = createPublicKey(publicPem).export({
234      format: 'der',
235      type: 'pkcs1'
236    });
237    createPrivateKey({ key, format: 'der', type: 'pkcs1' });
238  }, {
239    message: /asn1 encoding/,
240    library: 'asn1 encoding routines'
241  });
242}
243
244[
245  { private: fixtures.readKey('ed25519_private.pem', 'ascii'),
246    public: fixtures.readKey('ed25519_public.pem', 'ascii'),
247    keyType: 'ed25519' },
248  { private: fixtures.readKey('ed448_private.pem', 'ascii'),
249    public: fixtures.readKey('ed448_public.pem', 'ascii'),
250    keyType: 'ed448' },
251  { private: fixtures.readKey('x25519_private.pem', 'ascii'),
252    public: fixtures.readKey('x25519_public.pem', 'ascii'),
253    keyType: 'x25519' },
254  { private: fixtures.readKey('x448_private.pem', 'ascii'),
255    public: fixtures.readKey('x448_public.pem', 'ascii'),
256    keyType: 'x448' },
257].forEach((info) => {
258  const keyType = info.keyType;
259
260  {
261    const exportOptions = { type: 'pkcs8', format: 'pem' };
262    const key = createPrivateKey(info.private);
263    assert.strictEqual(key.type, 'private');
264    assert.strictEqual(key.asymmetricKeyType, keyType);
265    assert.strictEqual(key.symmetricKeySize, undefined);
266    assert.strictEqual(key.export(exportOptions), info.private);
267  }
268
269  {
270    const exportOptions = { type: 'spki', format: 'pem' };
271    [info.private, info.public].forEach((pem) => {
272      const key = createPublicKey(pem);
273      assert.strictEqual(key.type, 'public');
274      assert.strictEqual(key.asymmetricKeyType, keyType);
275      assert.strictEqual(key.symmetricKeySize, undefined);
276      assert.strictEqual(key.export(exportOptions), info.public);
277    });
278  }
279});
280
281{
282  // Reading an encrypted key without a passphrase should fail.
283  assert.throws(() => createPrivateKey(privateDsa), {
284    name: 'TypeError',
285    code: 'ERR_MISSING_PASSPHRASE',
286    message: 'Passphrase required for encrypted key'
287  });
288
289  // Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer
290  // size limit should fail with an appropriate error code.
291  assert.throws(() => createPrivateKey({
292    key: privateDsa,
293    format: 'pem',
294    passphrase: Buffer.alloc(1025, 'a')
295  }), {
296    code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ',
297    name: 'Error'
298  });
299
300  // The buffer has a size of 1024 bytes, so this passphrase should be permitted
301  // (but will fail decryption).
302  assert.throws(() => createPrivateKey({
303    key: privateDsa,
304    format: 'pem',
305    passphrase: Buffer.alloc(1024, 'a')
306  }), {
307    message: /bad decrypt/
308  });
309
310  const publicKey = createPublicKey(publicDsa);
311  assert.strictEqual(publicKey.type, 'public');
312  assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
313  assert.strictEqual(publicKey.symmetricKeySize, undefined);
314
315  const privateKey = createPrivateKey({
316    key: privateDsa,
317    format: 'pem',
318    passphrase: 'secret'
319  });
320  assert.strictEqual(privateKey.type, 'private');
321  assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
322  assert.strictEqual(privateKey.symmetricKeySize, undefined);
323
324}
325
326{
327  // Test RSA-PSS.
328  {
329    // This key pair does not restrict the message digest algorithm or salt
330    // length.
331    const publicPem = fixtures.readKey('rsa_pss_public_2048.pem');
332    const privatePem = fixtures.readKey('rsa_pss_private_2048.pem');
333
334    const publicKey = createPublicKey(publicPem);
335    const privateKey = createPrivateKey(privatePem);
336
337    assert.strictEqual(publicKey.type, 'public');
338    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
339
340    assert.strictEqual(privateKey.type, 'private');
341    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
342
343    for (const key of [privatePem, privateKey]) {
344      // Any algorithm should work.
345      for (const algo of ['sha1', 'sha256']) {
346        // Any salt length should work.
347        for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
348          const signature = createSign(algo)
349                            .update('foo')
350                            .sign({ key, saltLength });
351
352          for (const pkey of [key, publicKey, publicPem]) {
353            const okay = createVerify(algo)
354                         .update('foo')
355                         .verify({ key: pkey, saltLength }, signature);
356
357            assert.ok(okay);
358          }
359        }
360      }
361    }
362
363    // Exporting the key using PKCS#1 should not work since this would discard
364    // any algorithm restrictions.
365    assert.throws(() => {
366      publicKey.export({ format: 'pem', type: 'pkcs1' });
367    }, {
368      code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
369    });
370  }
371
372  {
373    // This key pair enforces sha256 as the message digest and the MGF1
374    // message digest and a salt length of at least 16 bytes.
375    const publicPem =
376      fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem');
377    const privatePem =
378      fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem');
379
380    const publicKey = createPublicKey(publicPem);
381    const privateKey = createPrivateKey(privatePem);
382
383    assert.strictEqual(publicKey.type, 'public');
384    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
385
386    assert.strictEqual(privateKey.type, 'private');
387    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
388
389    for (const key of [privatePem, privateKey]) {
390      // Signing with anything other than sha256 should fail.
391      assert.throws(() => {
392        createSign('sha1').sign(key);
393      }, /digest not allowed/);
394
395      // Signing with salt lengths less than 16 bytes should fail.
396      for (const saltLength of [8, 10, 12]) {
397        assert.throws(() => {
398          createSign('sha1').sign({ key, saltLength });
399        }, /pss saltlen too small/);
400      }
401
402      // Signing with sha256 and appropriate salt lengths should work.
403      for (const saltLength of [undefined, 16, 18, 20]) {
404        const signature = createSign('sha256')
405                          .update('foo')
406                          .sign({ key, saltLength });
407
408        for (const pkey of [key, publicKey, publicPem]) {
409          const okay = createVerify('sha256')
410                       .update('foo')
411                       .verify({ key: pkey, saltLength }, signature);
412
413          assert.ok(okay);
414        }
415      }
416    }
417  }
418
419  {
420    // This key enforces sha512 as the message digest and sha256 as the MGF1
421    // message digest.
422    const publicPem =
423      fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem');
424    const privatePem =
425      fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem');
426
427    const publicKey = createPublicKey(publicPem);
428    const privateKey = createPrivateKey(privatePem);
429
430    assert.strictEqual(publicKey.type, 'public');
431    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
432
433    assert.strictEqual(privateKey.type, 'private');
434    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
435
436    // Node.js usually uses the same hash function for the message and for MGF1.
437    // However, when a different MGF1 message digest algorithm has been
438    // specified as part of the key, it should automatically switch to that.
439    // This behavior is required by sections 3.1 and 3.3 of RFC4055.
440    for (const key of [privatePem, privateKey]) {
441      // sha256 matches the MGF1 hash function and should be used internally,
442      // but it should not be permitted as the main message digest algorithm.
443      for (const algo of ['sha1', 'sha256']) {
444        assert.throws(() => {
445          createSign(algo).sign(key);
446        }, /digest not allowed/);
447      }
448
449      // sha512 should produce a valid signature.
450      const signature = createSign('sha512')
451                        .update('foo')
452                        .sign(key);
453
454      for (const pkey of [key, publicKey, publicPem]) {
455        const okay = createVerify('sha512')
456                     .update('foo')
457                     .verify(pkey, signature);
458
459        assert.ok(okay);
460      }
461    }
462  }
463}
464
465{
466  // Exporting an encrypted private key requires a cipher
467  const privateKey = createPrivateKey(privatePem);
468  assert.throws(() => {
469    privateKey.export({
470      format: 'pem', type: 'pkcs8', passphrase: 'super-secret'
471    });
472  }, {
473    name: 'TypeError',
474    code: 'ERR_INVALID_OPT_VALUE',
475    message: 'The value "undefined" is invalid for option "cipher"'
476  });
477}
478