1'use strict';
2
3const common = require('../common');
4
5if (!common.hasCrypto)
6  common.skip('missing crypto');
7
8const assert = require('assert');
9const { webcrypto } = require('crypto');
10const { subtle } = webcrypto;
11
12const kTests = [
13  {
14    namedCurve: 'P-521',
15    size: 66,
16    pkcs8: '3081ee020100301006072a8648ce3d020106052b810400230481d63081d302010' +
17           '1044201a67ed321915a64aa359b7d648ddc2618fa8e8d1867e8f71830b10d25ed' +
18           '2891faf12f3c7e75421a2ea264f9a915320d274fe1470742b984e96b98912081f' +
19           'acd478da18189038186000400209d483f28666881c6641f3a126f400f51e46511' +
20           '70fe678c75e85712e2868adc850824997bebf0bc82b43028a6d2ec1777ca45279' +
21           'f7206a3ea8b5cd2073f493e45000cb54c3a5acaa268c56710428878d98b8afbf6' +
22           '8a612153632846d807e92672698f1b9c611de7d38e34cd6c73889092c56e52d68' +
23           '0f1dfd092b87ac8ef9ff3c8fb48',
24    spki: '30819b301006072a8648ce3d020106052b81040023038186000400ee69f94715d7' +
25          '01e9e2011333d4f4f96cba7d91f88b112baf75cf09cc1f8aca97618da9389822d2' +
26          '9b6fe9996a61203ef752b771e8958fc4677bb3778565ab60d6ed00deab6761895b' +
27          '935e3ad325fb8549e56f13786aa73f88a2ecfe40933473d8aef240c4dfd7d506f2' +
28          '2cdd0e55558f3fbf05ebf7efef7a72d78f46469b8448f26e2712',
29    result: '009c2bce57be80adab3b07385b8e5990eb7d6fdebdb01bf35371a4f6075e9d28' +
30            '8ac12a6dfe03aa5743bc81709d49a822940219b64b768acd520fa1368ea0af8d' +
31            '475d',
32  },
33  {
34    namedCurve: 'P-384',
35    size: 48,
36    pkcs8: '3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010' +
37           '10430f871a5666589c14a5747263ef85b319cc023db6e35676c3d781eef8b055f' +
38           'cfbe86fa0d06d056b5195fb1323af8de25b3a16403620004f11965df7dd4594d0' +
39           '419c5086482a3b826b9797f9be0bd0d109c9e1e9989c1b9a92b8f269f98e17ad1' +
40           '84ba73c1f79762af45af8141602642da271a6bb0ffeb0cb4478fcf707e661aa6d' +
41           '6cdf51549c88c3f130be9e8201f6f6a09f4185aaf95c4',
42    spki: '3076301006072a8648ce3d020106052b810400220362000491822dc2af59c18f5b' +
43          '67f80df61a2603c2a8f0b3c0af822d63c279701a824560404401dde9a56ee52757' +
44          'ea8bc748d4c82b5337b48d7b65583a3d572438880036bac6730f42ca5278966bd5' +
45          'f21e86e21d30c5a6d0463ec513dd509ffcdcaf1ff5',
46    result: 'e0bd6bce0aef8ca48838a6e2fcc57e67b9c5e8860c5f0be9dabec53e454e18a0' +
47            'a174c48888a26488115b2dc9f1dfa52d',
48  },
49];
50
51async function prepareKeys() {
52  const keys = {};
53  await Promise.all(
54    kTests.map(async ({ namedCurve, size, pkcs8, spki, result }) => {
55      const [
56        privateKey,
57        publicKey,
58      ] = await Promise.all([
59        subtle.importKey(
60          'pkcs8',
61          Buffer.from(pkcs8, 'hex'),
62          {
63            name: 'ECDH',
64            namedCurve
65          },
66          true,
67          ['deriveKey', 'deriveBits']),
68        subtle.importKey(
69          'spki',
70          Buffer.from(spki, 'hex'),
71          {
72            name: 'ECDH',
73            namedCurve
74          },
75          true,
76          []),
77      ]);
78      keys[namedCurve] = {
79        privateKey,
80        publicKey,
81        size,
82        result,
83      };
84    }));
85  return keys;
86}
87
88(async function() {
89  const keys = await prepareKeys();
90
91  await Promise.all(
92    Object.keys(keys).map(async (namedCurve) => {
93      const { size, result, privateKey, publicKey } = keys[namedCurve];
94
95      {
96        // Good parameters
97        const bits = await subtle.deriveBits({
98          name: 'ECDH',
99          public: publicKey
100        }, privateKey, 8 * size);
101
102        assert(bits instanceof ArrayBuffer);
103        assert.strictEqual(Buffer.from(bits).toString('hex'), result);
104      }
105
106      {
107        // Case insensitivity
108        const bits = await subtle.deriveBits({
109          name: 'eCdH',
110          public: publicKey
111        }, privateKey, 8 * size);
112
113        assert.strictEqual(Buffer.from(bits).toString('hex'), result);
114      }
115
116      {
117        // Null length
118        const bits = await subtle.deriveBits({
119          name: 'ECDH',
120          public: publicKey
121        }, privateKey, null);
122
123        assert.strictEqual(Buffer.from(bits).toString('hex'), result);
124      }
125
126      {
127        // Short Result
128        const bits = await subtle.deriveBits({
129          name: 'ECDH',
130          public: publicKey
131        }, privateKey, 8 * size - 32);
132
133        assert.strictEqual(
134          Buffer.from(bits).toString('hex'),
135          result.slice(0, -8));
136      }
137
138      {
139        // Too long result
140        await assert.rejects(subtle.deriveBits({
141          name: 'ECDH',
142          public: publicKey
143        }, privateKey, 8 * size + 8), {
144          message: /derived bit length is too small/
145        });
146      }
147
148      {
149        // Non-multiple of 8
150        const bits = await subtle.deriveBits({
151          name: 'ECDH',
152          public: publicKey
153        }, privateKey, 8 * size - 11);
154
155        assert.strictEqual(
156          Buffer.from(bits).toString('hex'),
157          result.slice(0, -2));
158      }
159    }));
160
161  // Error tests
162  {
163    // Missing public property
164    await assert.rejects(
165      subtle.deriveBits(
166        { name: 'ECDH' },
167        keys['P-384'].privateKey,
168        8 * keys['P-384'].size),
169      { code: 'ERR_MISSING_OPTION' });
170  }
171
172  {
173    // The public property is not a CryptoKey
174    await assert.rejects(
175      subtle.deriveBits(
176        {
177          name: 'ECDH',
178          public: { message: 'Not a CryptoKey' }
179        },
180        keys['P-384'].privateKey,
181        8 * keys['P-384'].size),
182      { code: 'ERR_INVALID_ARG_TYPE' });
183  }
184
185  {
186    // Mismatched named curves
187    await assert.rejects(
188      subtle.deriveBits(
189        {
190          name: 'ECDH',
191          public: keys['P-384'].publicKey
192        },
193        keys['P-521'].privateKey,
194        8 * keys['P-521'].size),
195      { message: /Named curve mismatch/ });
196  }
197
198  {
199    // Incorrect public key algorithm
200    const { publicKey } = await subtle.generateKey(
201      {
202        name: 'ECDSA',
203        namedCurve: 'P-521'
204      }, false, ['sign', 'verify']);
205
206    await assert.rejects(subtle.deriveBits({
207      name: 'ECDH',
208      public: publicKey
209    }, keys['P-521'].privateKey, null), {
210      message: /Keys must be ECDH, X25519, or X448 keys/
211    });
212  }
213
214  {
215    // Private key does not have correct usages
216    const privateKey = await subtle.importKey(
217      'pkcs8',
218      Buffer.from(kTests[0].pkcs8, 'hex'),
219      {
220        name: 'ECDH',
221        namedCurve: 'P-521'
222      }, false, ['deriveKey']);
223
224    await assert.rejects(subtle.deriveBits({
225      name: 'ECDH',
226      public: keys['P-521'].publicKey,
227    }, privateKey, null), {
228      message: /baseKey does not have deriveBits usage/
229    });
230  }
231
232  {
233    // Base key is not a private key
234    await assert.rejects(subtle.deriveBits({
235      name: 'ECDH',
236      public: keys['P-521'].publicKey
237    }, keys['P-521'].publicKey, null), {
238      name: 'InvalidAccessError'
239    });
240  }
241
242  {
243    // Public is not a public key
244    await assert.rejects(subtle.deriveBits({
245      name: 'ECDH',
246      public: keys['P-521'].privateKey
247    }, keys['P-521'].privateKey, null), {
248      name: 'InvalidAccessError'
249    });
250  }
251
252  {
253    // Public is a secret key
254    const keyData = webcrypto.getRandomValues(new Uint8Array(32));
255    const key = await subtle.importKey(
256      'raw',
257      keyData,
258      { name: 'AES-CBC', length: 256 },
259      false, ['encrypt']);
260
261    await assert.rejects(subtle.deriveBits({
262      name: 'ECDH',
263      public: key
264    }, keys['P-521'].publicKey, null), {
265      name: 'InvalidAccessError'
266    });
267  }
268})().then(common.mustCall());
269