• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
11const kWrappingData = {
12  'RSA-OAEP': {
13    generate: {
14      modulusLength: 4096,
15      publicExponent: new Uint8Array([1, 0, 1]),
16      hash: 'SHA-256',
17    },
18    wrap: { label: new Uint8Array(8) },
19    pair: true
20  },
21  'AES-CTR': {
22    generate: { length: 128 },
23    wrap: { counter: new Uint8Array(16), length: 64 },
24    pair: false
25  },
26  'AES-CBC': {
27    generate: { length: 128 },
28    wrap: { iv: new Uint8Array(16) },
29    pair: false
30  },
31  'AES-GCM': {
32    generate: { length: 128 },
33    wrap: {
34      iv: new Uint8Array(16),
35      additionalData: new Uint8Array(16),
36      tagLength: 64
37    },
38    pair: false
39  },
40  'AES-KW': {
41    generate: { length: 128 },
42    wrap: { },
43    pair: false
44  }
45};
46
47function generateWrappingKeys() {
48  return Promise.all(Object.keys(kWrappingData).map(async (name) => {
49    const keys = await subtle.generateKey(
50      { name, ...kWrappingData[name].generate },
51      true,
52      ['wrapKey', 'unwrapKey']);
53    if (kWrappingData[name].pair) {
54      kWrappingData[name].wrappingKey = keys.publicKey;
55      kWrappingData[name].unwrappingKey = keys.privateKey;
56    } else {
57      kWrappingData[name].wrappingKey = keys;
58      kWrappingData[name].unwrappingKey = keys;
59    }
60  }));
61}
62
63async function generateKeysToWrap() {
64  const parameters = [
65    {
66      algorithm: {
67        name: 'RSASSA-PKCS1-v1_5',
68        modulusLength: 1024,
69        publicExponent: new Uint8Array([1, 0, 1]),
70        hash: 'SHA-256'
71      },
72      privateUsages: ['sign'],
73      publicUsages: ['verify'],
74      pair: true,
75    },
76    {
77      algorithm: {
78        name: 'RSA-PSS',
79        modulusLength: 1024,
80        publicExponent: new Uint8Array([1, 0, 1]),
81        hash: 'SHA-256'
82      },
83      privateUsages: ['sign'],
84      publicUsages: ['verify'],
85      pair: true,
86    },
87    {
88      algorithm: {
89        name: 'RSA-OAEP',
90        modulusLength: 1024,
91        publicExponent: new Uint8Array([1, 0, 1]),
92        hash: 'SHA-256'
93      },
94      privateUsages: ['decrypt'],
95      publicUsages: ['encrypt'],
96      pair: true,
97    },
98    {
99      algorithm: {
100        name: 'ECDSA',
101        namedCurve: 'P-384'
102      },
103      privateUsages: ['sign'],
104      publicUsages: ['verify'],
105      pair: true,
106    },
107    {
108      algorithm: {
109        name: 'ECDH',
110        namedCurve: 'P-384'
111      },
112      privateUsages: ['deriveBits'],
113      publicUsages: [],
114      pair: true,
115    },
116    {
117      algorithm: {
118        name: 'Ed25519',
119      },
120      privateUsages: ['sign'],
121      publicUsages: ['verify'],
122      pair: true,
123    },
124    {
125      algorithm: {
126        name: 'Ed448',
127      },
128      privateUsages: ['sign'],
129      publicUsages: ['verify'],
130      pair: true,
131    },
132    {
133      algorithm: {
134        name: 'X25519',
135      },
136      privateUsages: ['deriveBits'],
137      publicUsages: [],
138      pair: true,
139    },
140    {
141      algorithm: {
142        name: 'X448',
143      },
144      privateUsages: ['deriveBits'],
145      publicUsages: [],
146      pair: true,
147    },
148    {
149      algorithm: {
150        name: 'AES-CTR',
151        length: 128
152      },
153      usages: ['encrypt', 'decrypt'],
154      pair: false,
155    },
156    {
157      algorithm: {
158        name: 'AES-CBC',
159        length: 128
160      },
161      usages: ['encrypt', 'decrypt'],
162      pair: false,
163    },
164    {
165      algorithm: {
166        name: 'AES-GCM', length: 128
167      },
168      usages: ['encrypt', 'decrypt'],
169      pair: false,
170    },
171    {
172      algorithm: {
173        name: 'AES-KW',
174        length: 128
175      },
176      usages: ['wrapKey', 'unwrapKey'],
177      pair: false,
178    },
179    {
180      algorithm: {
181        name: 'HMAC',
182        length: 128,
183        hash: 'SHA-256'
184      },
185      usages: ['sign', 'verify'],
186      pair: false,
187    },
188  ];
189
190  const allkeys = await Promise.all(parameters.map(async (params) => {
191    const usages = 'usages' in params ?
192      params.usages :
193      params.publicUsages.concat(params.privateUsages);
194
195    const keys = await subtle.generateKey(params.algorithm, true, usages);
196
197    if (params.pair) {
198      return [
199        {
200          algorithm: params.algorithm,
201          usages: params.publicUsages,
202          key: keys.publicKey,
203        },
204        {
205          algorithm: params.algorithm,
206          usages: params.privateUsages,
207          key: keys.privateKey,
208        },
209      ];
210    }
211
212    return [{
213      algorithm: params.algorithm,
214      usages: params.usages,
215      key: keys,
216    }];
217  }));
218
219  return allkeys.flat();
220}
221
222function getFormats(key) {
223  switch (key.key.type) {
224    case 'secret': return ['raw', 'jwk'];
225    case 'public': return ['spki', 'jwk'];
226    case 'private': return ['pkcs8', 'jwk'];
227  }
228}
229
230// If the wrapping algorithm is AES-KW, the exported key
231// material length must be a multiple of 8.
232// If the wrapping algorithm is RSA-OAEP, the exported key
233// material maximum length is a factor of the modulusLength
234//
235// As per the NOTE in step 13 https://w3c.github.io/webcrypto/#SubtleCrypto-method-wrapKey
236// we're padding AES-KW wrapped JWK to make sure it is always a multiple of 8 bytes
237// in length
238async function wrappingIsPossible(name, exported) {
239  if ('byteLength' in exported) {
240    switch (name) {
241      case 'AES-KW':
242        return exported.byteLength % 8 === 0;
243      case 'RSA-OAEP':
244        return exported.byteLength <= 446;
245    }
246  } else if ('kty' in exported && name === 'RSA-OAEP') {
247    return JSON.stringify(exported).length <= 478;
248  }
249  return true;
250}
251
252async function testWrap(wrappingKey, unwrappingKey, key, wrap, format) {
253  const exported = await subtle.exportKey(format, key.key);
254  if (!(await wrappingIsPossible(wrappingKey.algorithm.name, exported)))
255    return;
256
257  const wrapped =
258    await subtle.wrapKey(
259      format,
260      key.key,
261      wrappingKey,
262      { name: wrappingKey.algorithm.name, ...wrap });
263  const unwrapped =
264    await subtle.unwrapKey(
265      format,
266      wrapped,
267      unwrappingKey,
268      { name: wrappingKey.algorithm.name, ...wrap },
269      key.algorithm,
270      true,
271      key.usages);
272  assert(unwrapped.extractable);
273
274  const exportedAgain = await subtle.exportKey(format, unwrapped);
275  assert.deepStrictEqual(exported, exportedAgain);
276}
277
278function testWrapping(name, keys) {
279  const variations = [];
280
281  const {
282    wrappingKey,
283    unwrappingKey,
284    wrap
285  } = kWrappingData[name];
286
287  keys.forEach((key) => {
288    getFormats(key).forEach((format) => {
289      variations.push(testWrap(wrappingKey, unwrappingKey, key, wrap, format));
290    });
291  });
292
293  return variations;
294}
295
296(async function() {
297  await generateWrappingKeys();
298  const keys = await generateKeysToWrap();
299  const variations = [];
300  Object.keys(kWrappingData).forEach((name) => {
301    variations.push(...testWrapping(name, keys));
302  });
303  await Promise.all(variations);
304})().then(common.mustCall());
305