1'use strict';
2
3const common = require('../common');
4const fixtures = require('../common/fixtures');
5
6if (!common.hasCrypto)
7  common.skip('missing crypto');
8
9const assert = require('assert');
10const crypto = require('crypto');
11const { subtle } = crypto.webcrypto;
12
13const keyData = {
14  'Ed25519': {
15    jwsAlg: 'EdDSA',
16    spki: Buffer.from(
17      '302a300506032b6570032100a054b618c12b26c8d43595a5c38dd2b0140b944a' +
18      '151f75003278c2b6c58ec08f', 'hex'),
19    pkcs8: Buffer.from(
20      '302e020100300506032b657004220420d53150bdcd17b4d4b21ae756d4965639' +
21      'd75b28f56ff9111b1f88326913e445bc', 'hex'),
22    jwk: {
23      kty: 'OKP',
24      crv: 'Ed25519',
25      x: 'oFS2GMErJsjUNZWlw43SsBQLlEoVH3UAMnjCtsWOwI8',
26      d: '1TFQvc0XtNSyGudW1JZWOddbKPVv-REbH4gyaRPkRbw'
27    }
28  },
29  'Ed448': {
30    jwsAlg: 'EdDSA',
31    spki: Buffer.from(
32      '3043300506032b6571033a0008cc38160c85bca5656ac4924af7ea97a9161b20' +
33      '2528273dcb84afd2eeb99ac912a401b34ef15ef4d9486406a6eecc31e5909219' +
34      'bd54866800', 'hex'),
35    pkcs8: Buffer.from(
36      '3047020100300506032b6571043b0439afd05b2fbb153b47c18dfa66baaed0de' +
37      'fb4e88c651487cdee0fafc40fa3d048fe1cd145a44143243c0468166b5bc161a' +
38      '82e3b904f3e2fcaaf9', 'hex'),
39    jwk: {
40      kty: 'OKP',
41      crv: 'Ed448',
42      x: 'CMw4FgyFvKVlasSSSvfql6kWGyAlKCc9y4Sv0u65mskSpAGzTvFe9NlIZAam7' +
43         'swx5ZCSGb1UhmgA',
44      d: 'r9BbL7sVO0fBjfpmuq7Q3vtOiMZRSHze4Pr8QPo9BI_hzRRaRBQyQ8BGgWa1v' +
45         'BYaguO5BPPi_Kr5'
46    }
47  },
48  'X25519': {
49    jwsAlg: 'ECDH-ES',
50    spki: Buffer.from(
51      '302a300506032b656e032100f38d9f4e621a44e0428176a4c8a534b34f07f8db' +
52      '30152f9ca0167aabf598fe65', 'hex'),
53    pkcs8: Buffer.from(
54      '302e020100300506032b656e04220420a8327850317b4b03a5a8b4e923413b1d' +
55      'a4a642e0d6f7a72cf4d16a549e628a5f', 'hex'),
56    jwk: {
57      kty: 'OKP',
58      crv: 'X25519',
59      x: '842fTmIaROBCgXakyKU0s08H-NswFS-coBZ6q_WY_mU',
60      d: 'qDJ4UDF7SwOlqLTpI0E7HaSmQuDW96cs9NFqVJ5iil8'
61    }
62  },
63  'X448': {
64    jwsAlg: 'ECDH-ES',
65    spki: Buffer.from(
66      '3042300506032b656f0339001d451c8c0c369a42eadfc2875cd44953caeb46c4' +
67      '66dc86568280bfdbbb01f4709a1b0b1e0dd66cf7b11c84119ddc98890db72891' +
68      '29e30da4', 'hex'),
69    pkcs8: Buffer.from(
70      '3046020100300506032b656f043a0438fc818f6546a81f963c27765dc1c05bfd' +
71      'b169667e5e0cf45318ed1cb93872217ab0d9004e0c7dd0dcb00192f72039cc1a' +
72      '1dff750ec31c8afb', 'hex'),
73    jwk: {
74      kty: 'OKP',
75      crv: 'X448',
76      x: 'HUUcjAw2mkLq38KHXNRJU8rrRsRm3IZWgoC_27sB9HCaGwseDdZs97EchBGd3' +
77         'JiJDbcokSnjDaQ',
78      d: '_IGPZUaoH5Y8J3ZdwcBb_bFpZn5eDPRTGO0cuThyIXqw2QBODH3Q3LABkvcgO' +
79         'cwaHf91DsMcivs'
80    }
81  }
82};
83
84const testVectors = [
85  {
86    name: 'Ed25519',
87    privateUsages: ['sign'],
88    publicUsages: ['verify']
89  },
90  {
91    name: 'Ed448',
92    privateUsages: ['sign'],
93    publicUsages: ['verify']
94  },
95  {
96    name: 'X25519',
97    privateUsages: ['deriveKey', 'deriveBits'],
98    publicUsages: []
99  },
100  {
101    name: 'X448',
102    privateUsages: ['deriveKey', 'deriveBits'],
103    publicUsages: []
104  },
105];
106
107async function testImportSpki({ name, publicUsages }, extractable) {
108  const key = await subtle.importKey(
109    'spki',
110    keyData[name].spki,
111    { name },
112    extractable,
113    publicUsages);
114  assert.strictEqual(key.type, 'public');
115  assert.strictEqual(key.extractable, extractable);
116  assert.deepStrictEqual(key.usages, publicUsages);
117  assert.deepStrictEqual(key.algorithm.name, name);
118
119  if (extractable) {
120    // Test the roundtrip
121    const spki = await subtle.exportKey('spki', key);
122    assert.strictEqual(
123      Buffer.from(spki).toString('hex'),
124      keyData[name].spki.toString('hex'));
125  } else {
126    await assert.rejects(
127      subtle.exportKey('spki', key), {
128        message: /key is not extractable/
129      });
130  }
131
132  // Bad usage
133  await assert.rejects(
134    subtle.importKey(
135      'spki',
136      keyData[name].spki,
137      { name },
138      extractable,
139      ['wrapKey']),
140    { message: /Unsupported key usage/ });
141}
142
143async function testImportPkcs8({ name, privateUsages }, extractable) {
144  const key = await subtle.importKey(
145    'pkcs8',
146    keyData[name].pkcs8,
147    { name },
148    extractable,
149    privateUsages);
150  assert.strictEqual(key.type, 'private');
151  assert.strictEqual(key.extractable, extractable);
152  assert.deepStrictEqual(key.usages, privateUsages);
153  assert.deepStrictEqual(key.algorithm.name, name);
154
155  if (extractable) {
156    // Test the roundtrip
157    const pkcs8 = await subtle.exportKey('pkcs8', key);
158    assert.strictEqual(
159      Buffer.from(pkcs8).toString('hex'),
160      keyData[name].pkcs8.toString('hex'));
161  } else {
162    await assert.rejects(
163      subtle.exportKey('pkcs8', key), {
164        message: /key is not extractable/
165      });
166  }
167
168  await assert.rejects(
169    subtle.importKey(
170      'pkcs8',
171      keyData[name].pkcs8,
172      { name },
173      extractable,
174      [/* empty usages */]),
175    { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
176}
177
178async function testImportJwk({ name, publicUsages, privateUsages }, extractable) {
179
180  const jwk = keyData[name].jwk;
181
182  const [
183    publicKey,
184    privateKey,
185  ] = await Promise.all([
186    subtle.importKey(
187      'jwk',
188      {
189        kty: jwk.kty,
190        crv: jwk.crv,
191        x: jwk.x,
192      },
193      { name },
194      extractable, publicUsages),
195    subtle.importKey(
196      'jwk',
197      jwk,
198      { name },
199      extractable,
200      privateUsages),
201    subtle.importKey(
202      'jwk',
203      {
204        alg: keyData[name].jwsAlg,
205        kty: jwk.kty,
206        crv: jwk.crv,
207        x: jwk.x,
208      },
209      { name },
210      extractable, publicUsages),
211    subtle.importKey(
212      'jwk',
213      {
214        ...jwk,
215        alg: keyData[name].jwsAlg,
216      },
217      { name },
218      extractable,
219      privateUsages),
220  ]);
221
222  assert.strictEqual(publicKey.type, 'public');
223  assert.strictEqual(privateKey.type, 'private');
224  assert.strictEqual(publicKey.extractable, extractable);
225  assert.strictEqual(privateKey.extractable, extractable);
226  assert.deepStrictEqual(publicKey.usages, publicUsages);
227  assert.deepStrictEqual(privateKey.usages, privateUsages);
228  assert.strictEqual(publicKey.algorithm.name, name);
229  assert.strictEqual(privateKey.algorithm.name, name);
230
231  if (extractable) {
232    // Test the round trip
233    const [
234      pubJwk,
235      pvtJwk,
236    ] = await Promise.all([
237      subtle.exportKey('jwk', publicKey),
238      subtle.exportKey('jwk', privateKey),
239    ]);
240
241    assert.deepStrictEqual(pubJwk.key_ops, publicUsages);
242    assert.strictEqual(pubJwk.ext, true);
243    assert.strictEqual(pubJwk.kty, 'OKP');
244    assert.strictEqual(pubJwk.x, jwk.x);
245    assert.strictEqual(pubJwk.crv, jwk.crv);
246
247    assert.deepStrictEqual(pvtJwk.key_ops, privateUsages);
248    assert.strictEqual(pvtJwk.ext, true);
249    assert.strictEqual(pvtJwk.kty, 'OKP');
250    assert.strictEqual(pvtJwk.x, jwk.x);
251    assert.strictEqual(pvtJwk.crv, jwk.crv);
252    assert.strictEqual(pvtJwk.d, jwk.d);
253
254    if (jwk.crv.startsWith('Ed')) {
255      assert.strictEqual(pubJwk.alg, 'EdDSA');
256      assert.strictEqual(pvtJwk.alg, 'EdDSA');
257    } else {
258      assert.strictEqual(pubJwk.alg, undefined);
259      assert.strictEqual(pvtJwk.alg, undefined);
260    }
261  } else {
262    await assert.rejects(
263      subtle.exportKey('jwk', publicKey), {
264        message: /key is not extractable/
265      });
266    await assert.rejects(
267      subtle.exportKey('jwk', privateKey), {
268        message: /key is not extractable/
269      });
270  }
271
272  {
273    const invalidUse = name.startsWith('X') ? 'sig' : 'enc';
274    await assert.rejects(
275      subtle.importKey(
276        'jwk',
277        { ...jwk, use: invalidUse },
278        { name },
279        extractable,
280        privateUsages),
281      { message: 'Invalid JWK "use" Parameter' });
282  }
283
284  if (name.startsWith('Ed')) {
285    await assert.rejects(
286      subtle.importKey(
287        'jwk',
288        { kty: jwk.kty, x: jwk.x, crv: jwk.crv, alg: 'foo' },
289        { name },
290        extractable,
291        publicUsages),
292      { message: 'JWK "alg" does not match the requested algorithm' });
293
294    await assert.rejects(
295      subtle.importKey(
296        'jwk',
297        { ...jwk, alg: 'foo' },
298        { name },
299        extractable,
300        privateUsages),
301      { message: 'JWK "alg" does not match the requested algorithm' });
302  }
303
304  for (const crv of [undefined, name === 'Ed25519' ? 'Ed448' : 'Ed25519']) {
305    await assert.rejects(
306      subtle.importKey(
307        'jwk',
308        { kty: jwk.kty, x: jwk.x, y: jwk.y, crv },
309        { name },
310        extractable,
311        publicUsages),
312      { message: 'JWK "crv" Parameter and algorithm name mismatch' });
313
314    await assert.rejects(
315      subtle.importKey(
316        'jwk',
317        { ...jwk, crv },
318        { name },
319        extractable,
320        privateUsages),
321      { message: 'JWK "crv" Parameter and algorithm name mismatch' });
322  }
323
324  await assert.rejects(
325    subtle.importKey(
326      'jwk',
327      { ...jwk },
328      { name },
329      extractable,
330      [/* empty usages */]),
331    { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
332}
333
334async function testImportRaw({ name, publicUsages }) {
335  const jwk = keyData[name].jwk;
336
337  const publicKey = await subtle.importKey(
338    'raw',
339    Buffer.from(jwk.x, 'base64url'),
340    { name },
341    true, publicUsages);
342
343  assert.strictEqual(publicKey.type, 'public');
344  assert.deepStrictEqual(publicKey.usages, publicUsages);
345  assert.strictEqual(publicKey.algorithm.name, name);
346}
347
348(async function() {
349  const tests = [];
350  testVectors.forEach((vector) => {
351    [true, false].forEach((extractable) => {
352      tests.push(testImportSpki(vector, extractable));
353      tests.push(testImportPkcs8(vector, extractable));
354      tests.push(testImportJwk(vector, extractable));
355    });
356    tests.push(testImportRaw(vector));
357  });
358
359  await Promise.all(tests);
360})().then(common.mustCall());
361
362{
363  const rsaPublic = crypto.createPublicKey(
364    fixtures.readKey('rsa_public_2048.pem'));
365  const rsaPrivate = crypto.createPrivateKey(
366    fixtures.readKey('rsa_private_2048.pem'));
367
368  for (const [name, publicUsages, privateUsages] of [
369    ['Ed25519', ['verify'], ['sign']],
370    ['X448', [], ['deriveBits']],
371  ]) {
372    assert.rejects(subtle.importKey(
373      'spki',
374      rsaPublic.export({ format: 'der', type: 'spki' }),
375      { name },
376      true, publicUsages), { message: /Invalid key type/ });
377    assert.rejects(subtle.importKey(
378      'pkcs8',
379      rsaPrivate.export({ format: 'der', type: 'pkcs8' }),
380      { name },
381      true, privateUsages), { message: /Invalid key type/ });
382  }
383}
384