• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Flags: --expose-internals
2'use strict';
3
4const common = require('../common');
5
6if (!common.hasCrypto)
7  common.skip('missing crypto');
8
9const assert = require('assert');
10const { types: { isCryptoKey } } = require('util');
11const {
12  webcrypto: { subtle, CryptoKey },
13  createSecretKey,
14  KeyObject,
15} = require('crypto');
16
17const { bigIntArrayToUnsignedBigInt } = require('internal/crypto/util');
18
19const allUsages = [
20  'encrypt',
21  'decrypt',
22  'sign',
23  'verify',
24  'deriveBits',
25  'deriveKey',
26  'wrapKey',
27  'unwrapKey',
28];
29const vectors = {
30  'AES-CTR': {
31    algorithm: { length: 256 },
32    result: 'CryptoKey',
33    usages: [
34      'encrypt',
35      'decrypt',
36      'wrapKey',
37      'unwrapKey',
38    ],
39  },
40  'AES-CBC': {
41    algorithm: { length: 256 },
42    result: 'CryptoKey',
43    usages: [
44      'encrypt',
45      'decrypt',
46      'wrapKey',
47      'unwrapKey',
48    ],
49  },
50  'AES-GCM': {
51    algorithm: { length: 256 },
52    result: 'CryptoKey',
53    usages: [
54      'encrypt',
55      'decrypt',
56      'wrapKey',
57      'unwrapKey',
58    ],
59  },
60  'AES-KW': {
61    algorithm: { length: 256 },
62    result: 'CryptoKey',
63    usages: [
64      'wrapKey',
65      'unwrapKey',
66    ],
67  },
68  'HMAC': {
69    algorithm: { length: 256, hash: 'SHA-256' },
70    result: 'CryptoKey',
71    usages: [
72      'sign',
73      'verify',
74    ],
75  },
76  'RSASSA-PKCS1-v1_5': {
77    algorithm: {
78      modulusLength: 1024,
79      publicExponent: new Uint8Array([1, 0, 1]),
80      hash: 'SHA-256'
81    },
82    result: 'CryptoKeyPair',
83    usages: [
84      'sign',
85      'verify',
86    ],
87  },
88  'RSA-PSS': {
89    algorithm: {
90      modulusLength: 1024,
91      publicExponent: new Uint8Array([1, 0, 1]),
92      hash: 'SHA-256'
93    },
94    result: 'CryptoKeyPair',
95    usages: [
96      'sign',
97      'verify',
98    ],
99  },
100  'RSA-OAEP': {
101    algorithm: {
102      modulusLength: 1024,
103      publicExponent: new Uint8Array([1, 0, 1]),
104      hash: 'SHA-256'
105    },
106    result: 'CryptoKeyPair',
107    usages: [
108      'encrypt',
109      'decrypt',
110      'wrapKey',
111      'unwrapKey',
112    ],
113  },
114  'ECDSA': {
115    algorithm: { namedCurve: 'P-521' },
116    result: 'CryptoKeyPair',
117    usages: [
118      'sign',
119      'verify',
120    ],
121  },
122  'ECDH': {
123    algorithm: { namedCurve: 'P-521' },
124    result: 'CryptoKeyPair',
125    usages: [
126      'deriveKey',
127      'deriveBits',
128    ],
129  },
130  'Ed25519': {
131    result: 'CryptoKeyPair',
132    usages: [
133      'sign',
134      'verify',
135    ],
136  },
137  'Ed448': {
138    result: 'CryptoKeyPair',
139    usages: [
140      'sign',
141      'verify',
142    ],
143  },
144  'X25519': {
145    result: 'CryptoKeyPair',
146    usages: [
147      'deriveKey',
148      'deriveBits',
149    ],
150  },
151  'X448': {
152    result: 'CryptoKeyPair',
153    usages: [
154      'deriveKey',
155      'deriveBits',
156    ],
157  },
158};
159
160// Test invalid algorithms
161{
162  async function test(algorithm) {
163    return assert.rejects(
164      // The extractable and usages values are invalid here also,
165      // but the unrecognized algorithm name should be caught first.
166      subtle.generateKey(algorithm, 7, []), {
167        message: /Unrecognized algorithm name/,
168        name: 'NotSupportedError',
169      });
170  }
171
172  const tests = [
173    'AES',
174    { name: 'AES' },
175    { name: 'AES-CMAC' },
176    { name: 'AES-CFB' },
177    { name: 'HMAC', hash: 'MD5' },
178    {
179      name: 'RSA',
180      hash: 'SHA-256',
181      modulusLength: 2048,
182      publicExponent: new Uint8Array([1, 0, 1])
183    },
184    {
185      name: 'RSA-PSS',
186      hash: 'SHA',
187      modulusLength: 2048,
188      publicExponent: new Uint8Array([1, 0, 1])
189    },
190    {
191      name: 'EC',
192      namedCurve: 'P521'
193    },
194  ].map(async (algorithm) => test(algorithm));
195
196  Promise.all(tests).then(common.mustCall());
197}
198
199// Test bad usages
200{
201  async function test(name) {
202    await assert.rejects(
203      subtle.generateKey(
204        {
205          name, ...vectors[name].algorithm
206        },
207        true,
208        []),
209      { message: /Usages cannot be empty/ });
210
211    // For CryptoKeyPair results the private key
212    // usages must not be empty.
213    // - ECDH(-like) algorithm key pairs only have private key usages
214    // - Signing algorithm key pairs may pass a non-empty array but
215    //   with only a public key usage
216    if (
217      vectors[name].result === 'CryptoKeyPair' &&
218      vectors[name].usages.includes('verify')
219    ) {
220      await assert.rejects(
221        subtle.generateKey(
222          {
223            name, ...vectors[name].algorithm
224          },
225          true,
226          ['verify']),
227        { message: /Usages cannot be empty/ });
228    }
229
230    const invalidUsages = [];
231    allUsages.forEach((usage) => {
232      if (!vectors[name].usages.includes(usage))
233        invalidUsages.push(usage);
234    });
235    for (const invalidUsage of invalidUsages) {
236      await assert.rejects(
237        subtle.generateKey(
238          {
239            name, ...vectors[name].algorithm
240          },
241          true,
242          [...vectors[name].usages, invalidUsage]),
243        { message: /Unsupported key usage/ });
244    }
245  }
246
247  const tests = Object.keys(vectors).map(test);
248
249  Promise.all(tests).then(common.mustCall());
250}
251
252// Test RSA key generation
253{
254  async function test(
255    name,
256    modulusLength,
257    publicExponent,
258    hash,
259    privateUsages,
260    publicUsages = privateUsages) {
261    let usages = privateUsages;
262    if (publicUsages !== privateUsages)
263      usages = usages.concat(publicUsages);
264    const { publicKey, privateKey } = await subtle.generateKey({
265      name,
266      modulusLength,
267      publicExponent,
268      hash
269    }, true, usages);
270
271    assert(publicKey);
272    assert(privateKey);
273    assert(isCryptoKey(publicKey));
274    assert(isCryptoKey(privateKey));
275
276    assert(publicKey instanceof CryptoKey);
277    assert(privateKey instanceof CryptoKey);
278
279    assert.strictEqual(publicKey.type, 'public');
280    assert.strictEqual(privateKey.type, 'private');
281    assert.strictEqual(publicKey.toString(), '[object CryptoKey]');
282    assert.strictEqual(privateKey.toString(), '[object CryptoKey]');
283    assert.strictEqual(publicKey.extractable, true);
284    assert.strictEqual(privateKey.extractable, true);
285    assert.deepStrictEqual(publicKey.usages, publicUsages);
286    assert.deepStrictEqual(privateKey.usages, privateUsages);
287    assert.strictEqual(publicKey.algorithm.name, name);
288    assert.strictEqual(publicKey.algorithm.modulusLength, modulusLength);
289    assert.deepStrictEqual(publicKey.algorithm.publicExponent, publicExponent);
290    assert.strictEqual(
291      KeyObject.from(publicKey).asymmetricKeyDetails.publicExponent,
292      bigIntArrayToUnsignedBigInt(publicExponent));
293    assert.strictEqual(publicKey.algorithm.hash.name, hash);
294    assert.strictEqual(privateKey.algorithm.name, name);
295    assert.strictEqual(privateKey.algorithm.modulusLength, modulusLength);
296    assert.deepStrictEqual(privateKey.algorithm.publicExponent, publicExponent);
297    assert.strictEqual(
298      KeyObject.from(privateKey).asymmetricKeyDetails.publicExponent,
299      bigIntArrayToUnsignedBigInt(publicExponent));
300    assert.strictEqual(privateKey.algorithm.hash.name, hash);
301
302    // Missing parameters
303    await assert.rejects(
304      subtle.generateKey({ name, publicExponent, hash }, true, usages), {
305        code: 'ERR_MISSING_OPTION'
306      });
307
308    await assert.rejects(
309      subtle.generateKey({ name, modulusLength, hash }, true, usages), {
310        code: 'ERR_MISSING_OPTION'
311      });
312
313    await assert.rejects(
314      subtle.generateKey({ name, modulusLength }, true, usages), {
315        code: 'ERR_MISSING_OPTION'
316      });
317
318    await Promise.all([{}].map((modulusLength) => {
319      return assert.rejects(subtle.generateKey({
320        name,
321        modulusLength,
322        publicExponent,
323        hash
324      }, true, usages), {
325        code: 'ERR_INVALID_ARG_TYPE'
326      });
327    }));
328
329    await Promise.all(
330      [
331        '',
332        true,
333        {},
334        1,
335        [],
336        new Uint32Array(2),
337      ].map((publicExponent) => {
338        return assert.rejects(
339          subtle.generateKey(
340            { name, modulusLength, publicExponent, hash }, true, usages),
341          { code: 'ERR_INVALID_ARG_TYPE' });
342      }));
343
344    await Promise.all([true, 1].map((hash) => {
345      return assert.rejects(subtle.generateKey({
346        name,
347        modulusLength,
348        publicExponent,
349        hash
350      }, true, usages), {
351        message: /Unrecognized algorithm name/,
352        name: 'NotSupportedError',
353      });
354    }));
355
356    await Promise.all(['', {}, 1, false].map((usages) => {
357      return assert.rejects(subtle.generateKey({
358        name,
359        modulusLength,
360        publicExponent,
361        hash
362      }, true, usages), {
363        code: 'ERR_INVALID_ARG_TYPE'
364      });
365    }));
366
367    await Promise.all([[1], [1, 0, 0]].map((publicExponent) => {
368      return assert.rejects(subtle.generateKey({
369        name,
370        modulusLength,
371        publicExponent: new Uint8Array(publicExponent),
372        hash
373      }, true, usages), {
374        name: 'OperationError',
375      });
376    }));
377  }
378
379  const kTests = [
380    [
381      'RSASSA-PKCS1-v1_5',
382      1024,
383      Buffer.from([1, 0, 1]),
384      'SHA-256',
385      ['sign'],
386      ['verify'],
387    ],
388    [
389      'RSA-PSS',
390      2048,
391      Buffer.from([1, 0, 1]),
392      'SHA-512',
393      ['sign'],
394      ['verify'],
395    ],
396    [
397      'RSA-OAEP',
398      1024,
399      Buffer.from([3]),
400      'SHA-384',
401      ['decrypt', 'unwrapKey'],
402      ['encrypt', 'wrapKey'],
403    ],
404  ];
405
406  const tests = kTests.map((args) => test(...args));
407
408  Promise.all(tests).then(common.mustCall());
409}
410
411// Test EC Key Generation
412{
413  async function test(
414    name,
415    namedCurve,
416    privateUsages,
417    publicUsages = privateUsages) {
418
419    let usages = privateUsages;
420    if (publicUsages !== privateUsages)
421      usages = usages.concat(publicUsages);
422
423    const { publicKey, privateKey } = await subtle.generateKey({
424      name,
425      namedCurve
426    }, true, usages);
427
428    assert(publicKey);
429    assert(privateKey);
430    assert(isCryptoKey(publicKey));
431    assert(isCryptoKey(privateKey));
432
433    assert.strictEqual(publicKey.type, 'public');
434    assert.strictEqual(privateKey.type, 'private');
435    assert.strictEqual(publicKey.toString(), '[object CryptoKey]');
436    assert.strictEqual(privateKey.toString(), '[object CryptoKey]');
437    assert.strictEqual(publicKey.extractable, true);
438    assert.strictEqual(privateKey.extractable, true);
439    assert.deepStrictEqual(publicKey.usages, publicUsages);
440    assert.deepStrictEqual(privateKey.usages, privateUsages);
441    assert.strictEqual(publicKey.algorithm.name, name);
442    assert.strictEqual(privateKey.algorithm.name, name);
443    assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve);
444    assert.strictEqual(privateKey.algorithm.namedCurve, namedCurve);
445
446    // Invalid parameters
447    [1, true, {}, [], null].forEach(async (namedCurve) => {
448      await assert.rejects(
449        subtle.generateKey({ name, namedCurve }, true, privateUsages), {
450          name: 'NotSupportedError'
451        });
452    });
453    await assert.rejects(
454      subtle.generateKey({ name, namedCurve: undefined }, true, privateUsages), {
455        name: 'TypeError',
456        code: 'ERR_MISSING_OPTION'
457      });
458  }
459
460  const kTests = [
461    [
462      'ECDSA',
463      'P-384',
464      ['sign'],
465      ['verify'],
466    ],
467    [
468      'ECDSA',
469      'P-521',
470      ['sign'],
471      ['verify'],
472    ],
473    [
474      'ECDH',
475      'P-384',
476      ['deriveKey', 'deriveBits'],
477      [],
478    ],
479    [
480      'ECDH',
481      'P-521',
482      ['deriveKey', 'deriveBits'],
483      [],
484    ],
485  ];
486
487  const tests = kTests.map((args) => test(...args));
488
489  // Test bad parameters
490
491  Promise.all(tests).then(common.mustCall());
492}
493
494// Test AES Key Generation
495{
496  async function test(name, length, usages) {
497    const key = await subtle.generateKey({
498      name,
499      length
500    }, true, usages);
501
502    assert(key);
503    assert(isCryptoKey(key));
504
505    assert.strictEqual(key.type, 'secret');
506    assert.strictEqual(key.toString(), '[object CryptoKey]');
507    assert.strictEqual(key.extractable, true);
508    assert.deepStrictEqual(key.usages, usages);
509    assert.strictEqual(key.algorithm.name, name);
510    assert.strictEqual(key.algorithm.length, length);
511
512    // Invalid parameters
513    [1, 100, 257, '', false, null].forEach(async (length) => {
514      await assert.rejects(
515        subtle.generateKey({ name, length }, true, usages), {
516          name: 'OperationError'
517        });
518    });
519
520    await assert.rejects(
521      subtle.generateKey({ name, length: undefined }, true, usages), {
522        name: 'TypeError',
523        code: 'ERR_MISSING_OPTION'
524      });
525  }
526
527  const kTests = [
528    [ 'AES-CTR', 128, ['encrypt', 'decrypt', 'wrapKey']],
529    [ 'AES-CTR', 256, ['encrypt', 'decrypt', 'unwrapKey']],
530    [ 'AES-CBC', 128, ['encrypt', 'decrypt']],
531    [ 'AES-CBC', 256, ['encrypt', 'decrypt']],
532    [ 'AES-GCM', 128, ['encrypt', 'decrypt']],
533    [ 'AES-GCM', 256, ['encrypt', 'decrypt']],
534    [ 'AES-KW', 128, ['wrapKey', 'unwrapKey']],
535    [ 'AES-KW', 256, ['wrapKey', 'unwrapKey']],
536  ];
537
538  const tests = Promise.all(kTests.map((args) => test(...args)));
539
540  tests.then(common.mustCall());
541}
542
543// Test HMAC Key Generation
544{
545  async function test(length, hash, usages) {
546    const key = await subtle.generateKey({
547      name: 'HMAC',
548      length,
549      hash
550    }, true, usages);
551
552    if (length === undefined) {
553      switch (hash) {
554        case 'SHA-1': length = 512; break;
555        case 'SHA-256': length = 512; break;
556        case 'SHA-384': length = 1024; break;
557        case 'SHA-512': length = 1024; break;
558      }
559    }
560
561    assert(key);
562    assert(isCryptoKey(key));
563
564    assert.strictEqual(key.type, 'secret');
565    assert.strictEqual(key.toString(), '[object CryptoKey]');
566    assert.strictEqual(key.extractable, true);
567    assert.deepStrictEqual(key.usages, usages);
568    assert.strictEqual(key.algorithm.name, 'HMAC');
569    assert.strictEqual(key.algorithm.length, length);
570    assert.strictEqual(key.algorithm.hash.name, hash);
571
572    [1, false, null].forEach(async (hash) => {
573      await assert.rejects(
574        subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), {
575          message: /Unrecognized algorithm name/,
576          name: 'NotSupportedError',
577        });
578    });
579  }
580
581  const kTests = [
582    [ undefined, 'SHA-1', ['sign', 'verify']],
583    [ undefined, 'SHA-256', ['sign', 'verify']],
584    [ undefined, 'SHA-384', ['sign', 'verify']],
585    [ undefined, 'SHA-512', ['sign', 'verify']],
586    [ 128, 'SHA-256', ['sign', 'verify']],
587    [ 1024, 'SHA-512', ['sign', 'verify']],
588  ];
589
590  const tests = Promise.all(kTests.map((args) => test(...args)));
591
592  tests.then(common.mustCall());
593}
594
595// End user code cannot create CryptoKey directly
596assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' });
597
598{
599  const buffer = Buffer.from('Hello World');
600  const keyObject = createSecretKey(buffer);
601  assert(!isCryptoKey(buffer));
602  assert(!isCryptoKey(keyObject));
603}
604
605// Test OKP Key Generation
606{
607  async function test(
608    name,
609    privateUsages,
610    publicUsages = privateUsages) {
611
612    let usages = privateUsages;
613    if (publicUsages !== privateUsages)
614      usages = usages.concat(publicUsages);
615
616    const { publicKey, privateKey } = await subtle.generateKey({
617      name,
618    }, true, usages);
619
620    assert(publicKey);
621    assert(privateKey);
622    assert(isCryptoKey(publicKey));
623    assert(isCryptoKey(privateKey));
624
625    assert.strictEqual(publicKey.type, 'public');
626    assert.strictEqual(privateKey.type, 'private');
627    assert.strictEqual(publicKey.toString(), '[object CryptoKey]');
628    assert.strictEqual(privateKey.toString(), '[object CryptoKey]');
629    assert.strictEqual(publicKey.extractable, true);
630    assert.strictEqual(privateKey.extractable, true);
631    assert.deepStrictEqual(publicKey.usages, publicUsages);
632    assert.deepStrictEqual(privateKey.usages, privateUsages);
633    assert.strictEqual(publicKey.algorithm.name, name);
634    assert.strictEqual(privateKey.algorithm.name, name);
635  }
636
637  const kTests = [
638    [
639      'Ed25519',
640      ['sign'],
641      ['verify'],
642    ],
643    [
644      'Ed448',
645      ['sign'],
646      ['verify'],
647    ],
648    [
649      'X25519',
650      ['deriveKey', 'deriveBits'],
651      [],
652    ],
653    [
654      'X448',
655      ['deriveKey', 'deriveBits'],
656      [],
657    ],
658  ];
659
660  const tests = kTests.map((args) => test(...args));
661
662  // Test bad parameters
663
664  Promise.all(tests).then(common.mustCall());
665}
666