1'use strict';
2
3const common = require('../common');
4
5if (!common.hasCrypto)
6  common.skip('missing crypto');
7
8const assert = require('assert');
9const { subtle } = require('crypto').webcrypto;
10
11function getDeriveKeyInfo(name, length, hash, ...usages) {
12  return [{ name, length, hash }, usages];
13}
14
15const kDerivedKeyTypes = [
16  ['AES-CBC', 128, undefined, 'encrypt', 'decrypt'],
17  ['AES-CBC', 256, undefined, 'encrypt', 'decrypt'],
18  ['AES-CTR', 128, undefined, 'encrypt', 'decrypt'],
19  ['AES-CTR', 256, undefined, 'encrypt', 'decrypt'],
20  ['AES-GCM', 128, undefined, 'encrypt', 'decrypt'],
21  ['AES-GCM', 256, undefined, 'encrypt', 'decrypt'],
22  ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'],
23  ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'],
24  ['HMAC', 256, 'SHA-1', 'sign', 'verify'],
25  ['HMAC', 256, 'SHA-256', 'sign', 'verify'],
26  ['HMAC', 256, 'SHA-384', 'sign', 'verify'],
27  ['HMAC', 256, 'SHA-512', 'sign', 'verify'],
28];
29
30const kDerivedKeys = {
31  short: '5040737377307264',
32  long: '55736572732073686f756c64207069636b206c6f6e6720706173737068726' +
33          '173657320286e6f74207573652073686f72742070617373776f7264732921',
34  empty: ''
35};
36
37const kSalts = {
38  normal: '536f6469756d2043686c6f7269646520636f6d706f756e64',
39  empty: ''
40};
41
42const kInfos = {
43  normal: '484b444620657874726120696e666f',
44  empty: ''
45};
46
47const kDerivations = {
48  short: {
49    normal: {
50      'SHA-384': {
51        normal: '19ba74368e6b993390f27fe9a7d02bc3' +
52                '38173f72be71a19fc744fcdb3fd4b84b',
53        empty: '97601f4e0c53a5d3f3a2810099bc6820' +
54               'ec50083434769b59fc24a417a9543734'
55      },
56      'SHA-512': {
57        normal: '4bbd6db2435fb696157f6089c977c3c7' +
58                '3f3eac5ef3dd6baae604cb53bfbb153e',
59        empty: '2f3157e7fe0c10b01298c8f0886a90ed' +
60               'cf80abdef5dbc1df2b1482532b52b934'
61      },
62      'SHA-1': {
63        normal: '05ad22ed2138c9600e4d9e2725ded301' +
64                'f5d287fbfb5702f999bc6536d3edef98',
65        empty: 'd51b6fb7e599ca30c5ee264593e4b85f' +
66               '2220c7c3ab003157bff8cb4f369c7560'
67      },
68      'SHA-256': {
69        normal: '2af5901e28849c28443857386aa1ac3b' +
70                'b127e92631c1c051482d6690941772b4',
71        empty: '9e4b719033742101e90f1ad61e2ff3b4' +
72               '256863667296d74389f1f02af2c4e6a6'
73      }
74    },
75    empty: {
76      'SHA-384': {
77        normal: 'fb482ff22c4f8d466c4dfe6e29f2cc2e' +
78                'cdabf5884328fbf08a738fd945f166cb',
79        empty: '1e023c17b340533ceaef39230cb8b3bb' +
80               'dbf663a13d6075d0dd326c049478fba5'
81      },
82      'SHA-512': {
83        normal: 'f17b5bdcd8d7d3d4601036a19436317d' +
84                '1644f9a4e0956efc0e372b83acdacfdb',
85        empty: 'c7b474942f31f83faf5d14731802b1bd' +
86               '49478549cb3a8f3dbfedc4d3209cf5b6'
87      },
88      'SHA-1': {
89        normal: 'c126f1e6f25a9de42cf7d427059a52ed' +
90                '9601f29a5815cbfbc64bc7f668c6a341',
91        empty: '3215c3f08de70549b051b7033745a818' +
92               '4f8cbaa6b1735330d2bcb6b16f4642ef'
93      },
94      'SHA-256': {
95        normal: '733c8b6bcfac875c7f08982a6e3ffb56' +
96                '0acea6f165476eb83460b9353ed41dfe',
97        empty: 'c8e12774135305c9147f2cc4766e5ead' +
98               '25d8f457b9a1953d52677361ced558fb'
99      }
100    }
101  },
102  long: {
103    normal: {
104      'SHA-384': {
105        normal: 'f91571b521f7eef13e573aa46378659e' +
106                'f3b7f36ffdd1bb055db2cd77d260c467',
107        empty: '68af1c2cf6b9370d2054344798bdbb18' +
108               '47ccf407b7652b793dd136d4640e0348'
109      },
110      'SHA-512': {
111        normal: '710aae2fdf889e45fe0fb995b2c26b33' +
112                'eb988650ec0faef167028a7a6ccb3638',
113        empty: 'e5de568081c71e562750829871c34275' +
114               '8104765ed6f306f0613c9d4bb336f2aa'
115      },
116      'SHA-1': {
117        normal: '7f957edcbce3cb0b70566e1eb60efd1e' +
118                '405a13304c661d3663778109bf06899c',
119        empty: '3062f3cf1a730b9cef51f02c1dfac85e' +
120               'd91e4b0065eb50ca9fd8b0107e728733'
121      },
122      'SHA-256': {
123        normal: '31b7d68530a863e717c081ca6917b686' +
124                '50b3dd9a29f30606e2cad199bec14d13',
125        empty: 'e579d1f9e7f08e6f990ffcfcce1ed201' +
126               'c5e37e62cdf606f0ba4aca80427fbc44'
127      }
128    },
129    empty: {
130      'SHA-384': {
131        normal: '619eb6f9287395bbd5ed6a67c968465a' +
132                'd82b6c559f3c38b604bbb08f58320b03',
133        empty: 'ff447b423d83fe76836c32337228b56b' +
134               '5bd9bf68d58e7dca4b7cca842a45e11a'
135      },
136      'SHA-512': {
137        normal: '133e8a7f7ff433690cc88432c2a338c2' +
138                '77e5c13756ff878f46753fe6a564e3e5',
139        empty: 'de54f7eec80c9cc66d349fc987f80d46' +
140               '1db2ef4ff4e18505d28bd80cb42c7d76'
141      },
142      'SHA-1': {
143        normal: 'adb93cdbce79b7d51159b6c0131a2b62' +
144                'f23828d26acd685e34c06535e6f77496',
145        empty: '47710d2a7507e05a1ddcc87a7c2f9061' +
146               '77a266efb9e622510cccb3713cd08d58'
147      },
148      'SHA-256': {
149        normal: 'a401d7c9158a29e5c7193ab9730f0748' +
150                '851cc5baadb42cad024b6290fe213436',
151        empty: 'b4f7e7557674d501cbfbc0148ad800c0' +
152               '750189fe295a2aca5e1bf4122c85edf9'
153      }
154    }
155  },
156};
157
158async function setupBaseKeys() {
159  const promises = [];
160
161  const baseKeys = {};
162  const noBits = {};
163  const noKey = {};
164  let wrongKey = null;
165
166  Object.keys(kDerivedKeys).forEach((size) => {
167    const keyData = Buffer.from(kDerivedKeys[size], 'hex');
168    promises.push(
169      subtle.importKey(
170        'raw',
171        keyData,
172        { name: 'HKDF' },
173        false,
174        ['deriveKey', 'deriveBits'])
175        .then(
176          (baseKey) => baseKeys[size] = baseKey,
177          (err) => assert.ifError(err)));
178
179    promises.push(
180      subtle.importKey(
181        'raw',
182        keyData,
183        { name: 'HKDF' },
184        false, ['deriveBits'])
185        .then(
186          (baseKey) => noKey[size] = baseKey,
187          (err) => assert.ifError(err)));
188
189    promises.push(
190      subtle.importKey(
191        'raw',
192        keyData,
193        { name: 'HKDF' },
194        false, ['deriveKey'])
195        .then(
196          (baseKey) => noBits[size] = baseKey,
197          (err) => assert.ifError(err)));
198  });
199
200  promises.push(
201    subtle.generateKey(
202      {
203        name: 'ECDH',
204        namedCurve: 'P-521'
205      },
206      false,
207      ['deriveKey', 'deriveBits'])
208      .then(
209        (baseKey) => wrongKey = baseKey.privateKey,
210        (err) => assert.ifError(err)));
211
212  await Promise.all(promises);
213
214  return {
215    baseKeys,
216    noBits,
217    noKey,
218    wrongKey
219  };
220}
221
222async function testDeriveBits(
223  baseKeys,
224  size,
225  saltSize,
226  hash,
227  infoSize) {
228  const algorithm = {
229    name: 'HKDF',
230    salt: Buffer.from(kSalts[saltSize], 'hex'),
231    info: Buffer.from(kInfos[infoSize], 'hex'),
232    hash
233  };
234
235  const bits = await subtle.deriveBits(
236    algorithm,
237    baseKeys[size],
238    256);
239
240  assert(bits instanceof ArrayBuffer);
241  assert.strictEqual(
242    Buffer.from(bits).toString('hex'),
243    kDerivations[size][saltSize][hash][infoSize]);
244}
245
246async function testDeriveBitsBadLengths(
247  baseKeys,
248  size,
249  saltSize,
250  hash,
251  infoSize) {
252  const algorithm = {
253    name: 'HKDF',
254    salt: Buffer.from(kSalts[saltSize], 'hex'),
255    info: Buffer.from(kInfos[infoSize], 'hex'),
256    hash
257  };
258
259  return Promise.all([
260    assert.rejects(
261      subtle.deriveBits(algorithm, baseKeys[size], undefined), {
262        name: 'OperationError',
263      }),
264    assert.rejects(
265      subtle.deriveBits(algorithm, baseKeys[size], 0), {
266        message: /length cannot be zero/,
267        name: 'OperationError',
268      }),
269    assert.rejects(
270      subtle.deriveBits(algorithm, baseKeys[size], null), {
271        message: 'length cannot be null',
272        name: 'OperationError',
273      }),
274    assert.rejects(
275      subtle.deriveBits(algorithm, baseKeys[size], 15), {
276        message: /length must be a multiple of 8/,
277        name: 'OperationError',
278      }),
279  ]);
280}
281
282async function testDeriveBitsBadHash(
283  baseKeys,
284  size,
285  saltSize,
286  hash,
287  infoSize) {
288  const salt = Buffer.from(kSalts[saltSize], 'hex');
289  const info = Buffer.from(kInfos[infoSize], 'hex');
290  const algorithm = { name: 'HKDF', salt, info };
291
292  return Promise.all([
293    assert.rejects(
294      subtle.deriveBits(
295        {
296          ...algorithm,
297          hash: hash.substring(0, 3) + hash.substring(4)
298        }, baseKeys[size], 256), {
299        message: /Unrecognized algorithm name/,
300        name: 'NotSupportedError',
301      }),
302    assert.rejects(
303      subtle.deriveBits(
304        {
305          ...algorithm,
306          hash: 'PBKDF2'
307        },
308        baseKeys[size], 256), {
309        message: /Unrecognized algorithm name/,
310        name: 'NotSupportedError',
311      }),
312  ]);
313}
314
315async function testDeriveBitsBadUsage(
316  noBits,
317  size,
318  saltSize,
319  hash,
320  infoSize) {
321  const algorithm = {
322    name: 'HKDF',
323    salt: Buffer.from(kSalts[saltSize], 'hex'),
324    info: Buffer.from(kInfos[infoSize], 'hex'),
325    hash
326  };
327
328  return assert.rejects(
329    subtle.deriveBits(algorithm, noBits[size], 256), {
330      message: /baseKey does not have deriveBits usage/
331    });
332}
333
334async function testDeriveBitsMissingSalt(
335  baseKeys,
336  size,
337  saltSize,
338  hash,
339  infoSize) {
340  const algorithm = {
341    name: 'HKDF',
342    info: Buffer.from(kInfos[infoSize], 'hex'),
343    hash
344  };
345
346  return assert.rejects(
347    subtle.deriveBits(algorithm, baseKeys[size], 0), {
348      code: 'ERR_MISSING_OPTION'
349    });
350}
351
352async function testDeriveBitsMissingInfo(
353  baseKeys,
354  size,
355  saltSize,
356  hash,
357  infoSize) {
358  const algorithm = {
359    name: 'HKDF',
360    salt: Buffer.from(kSalts[infoSize], 'hex'),
361    hash
362  };
363
364  return assert.rejects(
365    subtle.deriveBits(algorithm, baseKeys[size], 0), {
366      code: 'ERR_MISSING_OPTION'
367    });
368}
369
370async function testBitsWrongKeyType(
371  wrongKey,
372  saltSize,
373  hash,
374  infoSize) {
375  const algorithm = {
376    name: 'HKDF',
377    salt: Buffer.from(kSalts[saltSize], 'hex'),
378    info: Buffer.from(kInfos[infoSize], 'hex'),
379    hash
380  };
381
382  return assert.rejects(
383    subtle.deriveBits(algorithm, wrongKey, 256), {
384      message: /Key algorithm mismatch/
385    });
386}
387
388async function testDeriveKey(
389  baseKeys,
390  size,
391  saltSize,
392  hash,
393  infoSize,
394  keyType,
395  usages) {
396  const algorithm = {
397    name: 'HKDF',
398    salt: Buffer.from(kSalts[saltSize], 'hex'),
399    info: Buffer.from(kInfos[infoSize], 'hex'),
400    hash
401  };
402
403  const key = await subtle.deriveKey(
404    algorithm,
405    baseKeys[size],
406    keyType,
407    true,
408    usages);
409
410  const bits = await subtle.exportKey('raw', key);
411
412  assert.strictEqual(
413    Buffer.from(bits).toString('hex'),
414    kDerivations[size][saltSize][hash][infoSize].slice(0, keyType.length / 4));
415}
416
417async function testDeriveKeyBadHash(
418  baseKeys,
419  size,
420  saltSize,
421  hash,
422  infoSize,
423  keyType,
424  usages) {
425  const salt = Buffer.from(kSalts[saltSize], 'hex');
426  const info = Buffer.from(kInfos[infoSize], 'hex');
427  const algorithm = { name: 'HKDF', salt, info };
428
429  return Promise.all([
430    assert.rejects(
431      subtle.deriveKey(
432        {
433          ...algorithm,
434          hash: hash.substring(0, 3) + hash.substring(4)
435        },
436        baseKeys[size],
437        keyType,
438        true,
439        usages),
440      {
441        message: /Unrecognized algorithm name/,
442        name: 'NotSupportedError',
443      }),
444    assert.rejects(
445      subtle.deriveKey(
446        {
447          ...algorithm,
448          hash: 'PBKDF2'
449        },
450        baseKeys[size],
451        keyType,
452        true,
453        usages),
454      {
455        message: /Unrecognized algorithm name/,
456        name: 'NotSupportedError',
457      }),
458  ]);
459}
460
461async function testDeriveKeyBadUsage(
462  noKey,
463  size,
464  saltSize,
465  hash,
466  infoSize,
467  keyType,
468  usages) {
469  const algorithm = {
470    name: 'HKDF',
471    salt: Buffer.from(kSalts[saltSize], 'hex'),
472    info: Buffer.from(kInfos[infoSize], 'hex'),
473    hash
474  };
475
476  return assert.rejects(
477    subtle.deriveKey(algorithm, noKey[size], keyType, true, usages), {
478      message: /baseKey does not have deriveKey usage/
479    });
480}
481
482async function testWrongKeyType(
483  wrongKey,
484  saltSize,
485  hash,
486  infoSize,
487  keyType,
488  usages
489) {
490  const algorithm = {
491    name: 'HKDF',
492    salt: Buffer.from(kSalts[saltSize], 'hex'),
493    info: Buffer.from(kInfos[infoSize], 'hex'),
494    hash
495  };
496
497  return assert.rejects(
498    subtle.deriveKey(algorithm, wrongKey, keyType, true, usages), {
499      message: /Key algorithm mismatch/
500    });
501}
502
503(async function() {
504  const {
505    baseKeys,
506    noBits,
507    noKey,
508    wrongKey,
509  } = await setupBaseKeys();
510
511  const variations = [];
512
513  Object.keys(kDerivations).forEach((size) => {
514    Object.keys(kDerivations[size]).forEach((saltSize) => {
515      Object.keys(kDerivations[size][saltSize]).forEach((hash) => {
516        Object.keys(kDerivations[size][saltSize][hash]).forEach(
517          async (infoSize) => {
518            const args = [baseKeys, size, saltSize, hash, infoSize];
519            variations.push(testDeriveBits(...args));
520            variations.push(testDeriveBitsBadLengths(...args));
521            variations.push(testDeriveBitsMissingSalt(...args));
522            variations.push(testDeriveBitsMissingInfo(...args));
523            variations.push(testDeriveBitsBadHash(...args));
524            variations.push(
525              testDeriveBitsBadUsage(
526                noBits,
527                size,
528                saltSize,
529                hash,
530                infoSize));
531            variations.push(
532              testBitsWrongKeyType(
533                wrongKey,
534                saltSize,
535                hash,
536                infoSize));
537
538            kDerivedKeyTypes.forEach((keyType) => {
539              const keyArgs = getDeriveKeyInfo(...keyType);
540              variations.push(testDeriveKey(...args, ...keyArgs));
541              variations.push(testDeriveKeyBadHash(...args, ...keyArgs));
542              variations.push(testDeriveKeyBadUsage(
543                noKey, size, saltSize, hash, infoSize, ...keyArgs));
544              variations.push(
545                testWrongKeyType(
546                  wrongKey,
547                  saltSize,
548                  hash,
549                  infoSize,
550                  ...keyArgs));
551            });
552          });
553      });
554    });
555  });
556
557  await Promise.all(variations);
558
559})().then(common.mustCall());
560