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 curves = ['P-256', 'P-384', 'P-521'];
14
15const keyData = {
16  'P-521': {
17    jwsAlg: 'ES512',
18    spki: Buffer.from(
19      '30819b301006072a8648ce3d020106052b8104002303818600040156f479f8df' +
20      '1e20a7ffc04ce420c3e154ae251996bee42f034b84d41b743f34e45f311b813a' +
21      '9cdec8cda59bbbbd31d460b3292521e7c1b722e5667c03db2fae753f01501736' +
22      'cfe247394320d8e4afc2fd39b5a9331061b81e2241282b9e17891822b5b79e05' +
23      '2f4597b59643fd39379c51bd5125c4f48bc3f025ce3cd36953286ccb38fb',
24      'hex'),
25    pkcs8: Buffer.from(
26      '3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020' +
27      '101044200f408758368ba930f30f76ae054fe5cd2ce7fda2c9f76a6d436cf75' +
28      'd66c440bfe6331c7c172a12478193c8251487bc91263fa50217f85ff636f59c' +
29      'd546e3ab483b4a1818903818600040156f479f8df1e20a7ffc04ce420c3e154' +
30      'ae251996bee42f034b84d41b743f34e45f311b813a9cdec8cda59bbbbd31d46' +
31      '0b3292521e7c1b722e5667c03db2fae753f01501736cfe247394320d8e4afc2' +
32      'fd39b5a9331061b81e2241282b9e17891822b5b79e052f4597b59643fd39379' +
33      'c51bd5125c4f48bc3f025ce3cd36953286ccb38fb', 'hex'),
34    jwk: {
35      kty: 'EC',
36      crv: 'P-521',
37      x: 'AVb0efjfHiCn_8BM5CDD4VSuJRmWvuQvA0uE1Bt0PzTkXzEbgTqc3sjN' +
38          'pZu7vTHUYLMpJSHnwbci5WZ8A9svrnU_',
39      y: 'AVAXNs_iRzlDINjkr8L9ObWpMxBhuB4iQSgrnheJGCK1t54FL0W' +
40          'XtZZD_Tk3nFG9USXE9IvD8CXOPNNpUyhsyzj7',
41      d: 'APQIdYNoupMPMPdq4FT-XNLOf9osn3am1DbPddZsRAv-YzHHw' +
42          'XKhJHgZPIJRSHvJEmP6UCF_hf9jb1nNVG46tIO0'
43    }
44  },
45  'P-384': {
46    jwsAlg: 'ES384',
47    spki: Buffer.from(
48      '3076301006072a8648ce3d020106052b8104002203620004219c14d66617b36e' +
49      'c6d8856b385b73a74d344fd8ae75ef046435dda54e3b44bd5fbdebd1d08dd69e' +
50      '2d7dc1dc218cb435bd28138cc778337a842f6bd61b240e74249f24667c2a5810' +
51      'a76bfc28e0335f88a6501dec01976da85afb00869cb6ace8', 'hex'),
52    pkcs8: Buffer.from(
53      '3081b6020100301006072a8648ce3d020106052b8104002204819e30819b0201' +
54      '0104304537b5990784d3c2d22e96a8f92fa1aa492ee873e576a41582e144183c' +
55      '9888d10e6b9eb4ced4b2cc4012e4ac5ea84073a16403620004219c14d66617b3' +
56      '6ec6d8856b385b73a74d344fd8ae75ef046435dda54e3b44bd5fbdebd1d08dd6' +
57      '9e2d7dc1dc218cb435bd28138cc778337a842f6bd61b240e74249f24667c2a58' +
58      '10a76bfc28e0335f88a6501dec01976da85afb00869cb6ace8', 'hex'),
59    jwk: {
60      kty: 'EC',
61      crv: 'P-384',
62      x: 'IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1',
63      y: 'vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo',
64      d: 'RTe1mQeE08LSLpao-S-hqkku6HPldqQVguFEGDyYiNEOa560ztSyzEAS5KxeqEBz'
65    }
66  },
67  'P-256': {
68    jwsAlg: 'ES256',
69    spki: Buffer.from(
70      '3059301306072a8648ce3d020106082a8648ce3d03010703420004d6e8328a95' +
71      'fe29afcdc30977b9251efbb219022807f6b14bb34695b6b4bdb93ee6684548a4' +
72      'ad13c49d00433c45315e8274f3540f58f5d79ef7a1b184f4c21d17', 'hex'),
73    pkcs8: Buffer.from(
74      '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02' +
75      '010104202bc2eda265e46866efa8f8f99da993175b6c85c246e15dceaed7e307' +
76      '0f13fbf8a14403420004d6e8328a95fe29afcdc30977b9251efbb219022807f6' +
77      'b14bb34695b6b4bdb93ee6684548a4ad13c49d00433c45315e8274f3540f58f5' +
78      'd79ef7a1b184f4c21d17', 'hex'),
79    jwk: {
80      kty: 'EC',
81      crv: 'P-256',
82      x: '1ugyipX-Ka_Nwwl3uSUe-7IZAigH9rFLs0aVtrS9uT4',
83      y: '5mhFSKStE8SdAEM8RTFegnTzVA9Y9dee96GxhPTCHRc',
84      d: 'K8LtomXkaGbvqPj5namTF1tshcJG4V3OrtfjBw8T-_g'
85    }
86  },
87};
88
89const testVectors = [
90  {
91    name: 'ECDSA',
92    privateUsages: ['sign'],
93    publicUsages: ['verify']
94  },
95  {
96    name: 'ECDH',
97    privateUsages: ['deriveKey', 'deriveBits'],
98    publicUsages: []
99  },
100];
101
102async function testImportSpki({ name, publicUsages }, namedCurve, extractable) {
103  const key = await subtle.importKey(
104    'spki',
105    keyData[namedCurve].spki,
106    { name, namedCurve },
107    extractable,
108    publicUsages);
109  assert.strictEqual(key.type, 'public');
110  assert.strictEqual(key.extractable, extractable);
111  assert.deepStrictEqual(key.usages, publicUsages);
112  assert.deepStrictEqual(key.algorithm.name, name);
113  assert.deepStrictEqual(key.algorithm.namedCurve, namedCurve);
114
115  if (extractable) {
116    // Test the roundtrip
117    const spki = await subtle.exportKey('spki', key);
118    assert.strictEqual(
119      Buffer.from(spki).toString('hex'),
120      keyData[namedCurve].spki.toString('hex'));
121  } else {
122    await assert.rejects(
123      subtle.exportKey('spki', key), {
124        message: /key is not extractable/
125      });
126  }
127
128  // Bad usage
129  await assert.rejects(
130    subtle.importKey(
131      'spki',
132      keyData[namedCurve].spki,
133      { name, namedCurve },
134      extractable,
135      ['wrapKey']),
136    { message: /Unsupported key usage/ });
137}
138
139async function testImportPkcs8(
140  { name, privateUsages },
141  namedCurve,
142  extractable) {
143  const key = await subtle.importKey(
144    'pkcs8',
145    keyData[namedCurve].pkcs8,
146    { name, namedCurve },
147    extractable,
148    privateUsages);
149  assert.strictEqual(key.type, 'private');
150  assert.strictEqual(key.extractable, extractable);
151  assert.deepStrictEqual(key.usages, privateUsages);
152  assert.deepStrictEqual(key.algorithm.name, name);
153  assert.deepStrictEqual(key.algorithm.namedCurve, namedCurve);
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[namedCurve].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[namedCurve].pkcs8,
172      { name, namedCurve },
173      extractable,
174      [/* empty usages */]),
175    { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
176}
177
178async function testImportJwk(
179  { name, publicUsages, privateUsages },
180  namedCurve,
181  extractable) {
182
183  const jwk = keyData[namedCurve].jwk;
184
185  const [
186    publicKey,
187    privateKey,
188  ] = await Promise.all([
189    subtle.importKey(
190      'jwk',
191      {
192        kty: jwk.kty,
193        crv: jwk.crv,
194        x: jwk.x,
195        y: jwk.y,
196      },
197      { name, namedCurve },
198      extractable, publicUsages),
199    subtle.importKey(
200      'jwk',
201      jwk,
202      { name, namedCurve },
203      extractable,
204      privateUsages),
205    subtle.importKey(
206      'jwk',
207      {
208        alg: name === 'ECDSA' ? keyData[namedCurve].jwsAlg : 'ECDH-ES',
209        kty: jwk.kty,
210        crv: jwk.crv,
211        x: jwk.x,
212        y: jwk.y,
213      },
214      { name, namedCurve },
215      extractable, publicUsages),
216    subtle.importKey(
217      'jwk',
218      {
219        ...jwk,
220        alg: name === 'ECDSA' ? keyData[namedCurve].jwsAlg : 'ECDH-ES',
221      },
222      { name, namedCurve },
223      extractable,
224      privateUsages),
225  ]);
226
227  assert.strictEqual(publicKey.type, 'public');
228  assert.strictEqual(privateKey.type, 'private');
229  assert.strictEqual(publicKey.extractable, extractable);
230  assert.strictEqual(privateKey.extractable, extractable);
231  assert.deepStrictEqual(publicKey.usages, publicUsages);
232  assert.deepStrictEqual(privateKey.usages, privateUsages);
233  assert.strictEqual(publicKey.algorithm.name, name);
234  assert.strictEqual(privateKey.algorithm.name, name);
235  assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve);
236  assert.strictEqual(privateKey.algorithm.namedCurve, namedCurve);
237
238  if (extractable) {
239    // Test the round trip
240    const [
241      pubJwk,
242      pvtJwk,
243    ] = await Promise.all([
244      subtle.exportKey('jwk', publicKey),
245      subtle.exportKey('jwk', privateKey),
246    ]);
247
248    assert.deepStrictEqual(pubJwk.key_ops, publicUsages);
249    assert.strictEqual(pubJwk.ext, true);
250    assert.strictEqual(pubJwk.kty, 'EC');
251    assert.strictEqual(pubJwk.x, jwk.x);
252    assert.strictEqual(pubJwk.y, jwk.y);
253    assert.strictEqual(pubJwk.crv, jwk.crv);
254
255    assert.deepStrictEqual(pvtJwk.key_ops, privateUsages);
256    assert.strictEqual(pvtJwk.ext, true);
257    assert.strictEqual(pvtJwk.kty, 'EC');
258    assert.strictEqual(pvtJwk.x, jwk.x);
259    assert.strictEqual(pvtJwk.y, jwk.y);
260    assert.strictEqual(pvtJwk.crv, jwk.crv);
261    assert.strictEqual(pvtJwk.d, jwk.d);
262  } else {
263    await assert.rejects(
264      subtle.exportKey('jwk', publicKey), {
265        message: /key is not extractable/
266      });
267    await assert.rejects(
268      subtle.exportKey('jwk', privateKey), {
269        message: /key is not extractable/
270      });
271  }
272
273  {
274    const invalidUse = name === 'ECDH' ? 'sig' : 'enc';
275    await assert.rejects(
276      subtle.importKey(
277        'jwk',
278        { ...jwk, use: invalidUse },
279        { name, namedCurve },
280        extractable,
281        privateUsages),
282      { message: 'Invalid JWK "use" Parameter' });
283  }
284
285  if (name === 'ECDSA') {
286    await assert.rejects(
287      subtle.importKey(
288        'jwk',
289        { kty: jwk.kty, x: jwk.x, y: jwk.y, crv: jwk.crv, alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256' },
290        { name, namedCurve },
291        extractable,
292        publicUsages),
293      { message: 'JWK "alg" does not match the requested algorithm' });
294
295    await assert.rejects(
296      subtle.importKey(
297        'jwk',
298        { ...jwk, alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256' },
299        { name, namedCurve },
300        extractable,
301        privateUsages),
302      { message: 'JWK "alg" does not match the requested algorithm' });
303  }
304
305  for (const crv of [undefined, namedCurve === 'P-256' ? 'P-384' : 'P-256']) {
306    await assert.rejects(
307      subtle.importKey(
308        'jwk',
309        { kty: jwk.kty, x: jwk.x, y: jwk.y, crv },
310        { name, namedCurve },
311        extractable,
312        publicUsages),
313      { message: 'JWK "crv" does not match the requested algorithm' });
314
315    await assert.rejects(
316      subtle.importKey(
317        'jwk',
318        { ...jwk, crv },
319        { name, namedCurve },
320        extractable,
321        privateUsages),
322      { message: 'JWK "crv" does not match the requested algorithm' });
323  }
324
325  await assert.rejects(
326    subtle.importKey(
327      'jwk',
328      { ...jwk },
329      { name, namedCurve },
330      extractable,
331      [/* empty usages */]),
332    { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
333}
334
335async function testImportRaw({ name, publicUsages }, namedCurve) {
336  const jwk = keyData[namedCurve].jwk;
337
338  const [publicKey] = await Promise.all([
339    subtle.importKey(
340      'raw',
341      Buffer.concat([
342        Buffer.alloc(1, 0x04),
343        Buffer.from(jwk.x, 'base64url'),
344        Buffer.from(jwk.y, 'base64url'),
345      ]),
346      { name, namedCurve },
347      true, publicUsages),
348    subtle.importKey(
349      'raw',
350      Buffer.concat([
351        Buffer.alloc(1, 0x03),
352        Buffer.from(jwk.x, 'base64url'),
353      ]),
354      { name, namedCurve },
355      true, publicUsages),
356  ]);
357
358  assert.strictEqual(publicKey.type, 'public');
359  assert.deepStrictEqual(publicKey.usages, publicUsages);
360  assert.strictEqual(publicKey.algorithm.name, name);
361  assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve);
362}
363
364(async function() {
365  const tests = [];
366  testVectors.forEach((vector) => {
367    curves.forEach((namedCurve) => {
368      [true, false].forEach((extractable) => {
369        tests.push(testImportSpki(vector, namedCurve, extractable));
370        tests.push(testImportPkcs8(vector, namedCurve, extractable));
371        tests.push(testImportJwk(vector, namedCurve, extractable));
372      });
373      tests.push(testImportRaw(vector, namedCurve));
374    });
375  });
376
377  await Promise.all(tests);
378})().then(common.mustCall());
379
380
381// https://github.com/nodejs/node/issues/45859
382(async function() {
383  const compressed = Buffer.from([48, 57, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 34, 0, 2, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209]);  // eslint-disable-line max-len
384  const uncompressed = Buffer.from([48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149, 112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53, 131, 97, 232]);  // eslint-disable-line max-len
385  for (const name of ['ECDH', 'ECDSA']) {
386    const options = { name, namedCurve: 'P-256' };
387    const key = await subtle.importKey('spki', compressed, options, true, []);
388    const spki = await subtle.exportKey('spki', key);
389    assert.deepStrictEqual(uncompressed, Buffer.from(spki));
390  }
391})().then(common.mustCall());
392
393{
394  const rsaPublic = crypto.createPublicKey(
395    fixtures.readKey('rsa_public_2048.pem'));
396  const rsaPrivate = crypto.createPrivateKey(
397    fixtures.readKey('rsa_private_2048.pem'));
398
399  for (const [name, publicUsages, privateUsages] of [
400    ['ECDSA', ['verify'], ['sign']],
401    ['ECDH', [], ['deriveBits', 'deriveBits']],
402  ]) {
403    assert.rejects(
404      subtle.importKey(
405        'spki',
406        rsaPublic.export({ format: 'der', type: 'spki' }),
407        { name, hash: 'SHA-256', namedCurve: 'P-256' },
408        true, publicUsages), { message: /Invalid key type/ },
409    ).then(common.mustCall());
410    assert.rejects(
411      subtle.importKey(
412        'pkcs8',
413        rsaPrivate.export({ format: 'der', type: 'pkcs8' }),
414        { name, hash: 'SHA-256', namedCurve: 'P-256' },
415        true, privateUsages), { message: /Invalid key type/ },
416    ).then(common.mustCall());
417  }
418}
419
420// Bad private keys
421{
422  for (const { namedCurve, key: pkcs8 } of [
423    // The private key is exactly equal to the order, and the public key is
424    // private key * order.
425    {
426      namedCurve: 'P-256',
427      key: Buffer.from(
428        '3066020100301306072a8648ce3d020106082a8648ce3d030107044c304a0201' +
429        '010420ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc' +
430        '632551a12303210000ffffff00000000ffffffffffffffffbce6faada7179e84' +
431        'f3b9cac2fc632551', 'hex'),
432    },
433    // The private key is exactly equal to the order, and the public key is
434    // omitted.
435    {
436      namedCurve: 'P-256',
437      key: Buffer.from(
438        '3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201' +
439        '010420ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc' +
440        '632551', 'hex'),
441    },
442    // The private key is exactly equal to the order + 11, and the public key is
443    // private key * order.
444    {
445      namedCurve: 'P-521',
446      key: Buffer.from(
447        '3081ee020100301006072a8648ce3d020106052b810400230481d63081d30201' +
448        '01044201ffffffffffffffffffffffffffffffffffffffffffffffffffffffff' +
449        'fffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb7' +
450        '1e91386414a181890381860004008a75841259fdedff546f1a39573b4315cfed' +
451        '5dc7ed7c17849543ef2c54f2991652f3dbc5332663da1bd19b1aebe319108501' +
452        '5c024fa4c9a902ecc0e02dda0cdb9a0096fb303fcbba2129849d0ca877054fb2' +
453        '293add566210bd0493ed2e95d4e0b9b82b1bc8a90e8b42a4ab3892331914a953' +
454        '36dcac80e3f4819b5d58874f92ce48c808', 'hex'),
455    },
456    // The private key is exactly equal to the order + 11, and the public key is
457    // omitted.
458    {
459      namedCurve: 'P-521',
460      key: Buffer.from(
461        '3060020100301006072a8648ce3d020106052b81040023044930470201010442' +
462        '01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' +
463        'fffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e9138' +
464        '6414', 'hex'),
465    },
466  ]) {
467    for (const [name, privateUsages] of [
468      ['ECDSA', ['sign']],
469      ['ECDH', ['deriveBits', 'deriveBits']],
470    ]) {
471      assert.rejects(
472        subtle.importKey(
473          'pkcs8',
474          pkcs8,
475          { name, hash: 'SHA-256', namedCurve },
476          true, privateUsages), { name: 'DataError', message: /Invalid keyData/ },
477      ).then(common.mustCall());
478    }
479  }
480}
481