• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  SafeSet,
5} = primordials;
6
7const { Buffer } = require('buffer');
8
9const {
10  ECKeyExportJob,
11  KeyObjectHandle,
12  SignJob,
13  kCryptoJobAsync,
14  kKeyTypePrivate,
15  kKeyTypePublic,
16  kSignJobModeSign,
17  kSignJobModeVerify,
18} = internalBinding('crypto');
19
20const {
21  getUsagesUnion,
22  hasAnyNotIn,
23  jobPromise,
24  validateKeyOps,
25  kHandle,
26  kKeyObject,
27} = require('internal/crypto/util');
28
29const {
30  emitExperimentalWarning,
31  lazyDOMException,
32  promisify,
33} = require('internal/util');
34
35const {
36  generateKeyPair: _generateKeyPair,
37} = require('internal/crypto/keygen');
38
39const {
40  InternalCryptoKey,
41  PrivateKeyObject,
42  PublicKeyObject,
43  createPrivateKey,
44  createPublicKey,
45} = require('internal/crypto/keys');
46
47const generateKeyPair = promisify(_generateKeyPair);
48
49function verifyAcceptableCfrgKeyUse(name, isPublic, usages) {
50  let checkSet;
51  switch (name) {
52    case 'X25519':
53      // Fall through
54    case 'X448':
55      checkSet = isPublic ? [] : ['deriveKey', 'deriveBits'];
56      break;
57    case 'Ed25519':
58      // Fall through
59    case 'Ed448':
60      checkSet = isPublic ? ['verify'] : ['sign'];
61      break;
62    default:
63      throw lazyDOMException(
64        'The algorithm is not supported', 'NotSupportedError');
65  }
66  if (hasAnyNotIn(usages, checkSet)) {
67    throw lazyDOMException(
68      `Unsupported key usage for a ${name} key`,
69      'SyntaxError');
70  }
71}
72
73function createCFRGRawKey(name, keyData, isPublic) {
74  const handle = new KeyObjectHandle();
75
76  switch (name) {
77    case 'Ed25519':
78    case 'X25519':
79      if (keyData.byteLength !== 32) {
80        throw lazyDOMException(
81          `${name} raw keys must be exactly 32-bytes`, 'DataError');
82      }
83      break;
84    case 'Ed448':
85      if (keyData.byteLength !== 57) {
86        throw lazyDOMException(
87          `${name} raw keys must be exactly 57-bytes`, 'DataError');
88      }
89      break;
90    case 'X448':
91      if (keyData.byteLength !== 56) {
92        throw lazyDOMException(
93          `${name} raw keys must be exactly 56-bytes`, 'DataError');
94      }
95      break;
96  }
97
98  const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
99  if (!handle.initEDRaw(name, keyData, keyType)) {
100    throw lazyDOMException('Invalid keyData', 'DataError');
101  }
102
103  return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
104}
105
106async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
107  const { name } = algorithm;
108  emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
109
110  const usageSet = new SafeSet(keyUsages);
111  switch (name) {
112    case 'Ed25519':
113      // Fall through
114    case 'Ed448':
115      if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
116        throw lazyDOMException(
117          `Unsupported key usage for an ${name} key`,
118          'SyntaxError');
119      }
120      break;
121    case 'X25519':
122      // Fall through
123    case 'X448':
124      if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
125        throw lazyDOMException(
126          `Unsupported key usage for an ${name} key`,
127          'SyntaxError');
128      }
129      break;
130  }
131  let genKeyType;
132  switch (name) {
133    case 'Ed25519':
134      genKeyType = 'ed25519';
135      break;
136    case 'Ed448':
137      genKeyType = 'ed448';
138      break;
139    case 'X25519':
140      genKeyType = 'x25519';
141      break;
142    case 'X448':
143      genKeyType = 'x448';
144      break;
145  }
146
147  const keyPair = await generateKeyPair(genKeyType).catch((err) => {
148    throw lazyDOMException(
149      'The operation failed for an operation-specific reason',
150      { name: 'OperationError', cause: err });
151  });
152
153  let publicUsages;
154  let privateUsages;
155  switch (name) {
156    case 'Ed25519':
157      // Fall through
158    case 'Ed448':
159      publicUsages = getUsagesUnion(usageSet, 'verify');
160      privateUsages = getUsagesUnion(usageSet, 'sign');
161      break;
162    case 'X25519':
163      // Fall through
164    case 'X448':
165      publicUsages = [];
166      privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
167      break;
168  }
169
170  const keyAlgorithm = { name };
171
172  const publicKey =
173    new InternalCryptoKey(
174      keyPair.publicKey,
175      keyAlgorithm,
176      publicUsages,
177      true);
178
179  const privateKey =
180    new InternalCryptoKey(
181      keyPair.privateKey,
182      keyAlgorithm,
183      privateUsages,
184      extractable);
185
186  return { privateKey, publicKey };
187}
188
189function cfrgExportKey(key, format) {
190  emitExperimentalWarning(`The ${key.algorithm.name} Web Crypto API algorithm`);
191  return jobPromise(() => new ECKeyExportJob(
192    kCryptoJobAsync,
193    format,
194    key[kKeyObject][kHandle]));
195}
196
197async function cfrgImportKey(
198  format,
199  keyData,
200  algorithm,
201  extractable,
202  keyUsages) {
203
204  const { name } = algorithm;
205  emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
206  let keyObject;
207  const usagesSet = new SafeSet(keyUsages);
208  switch (format) {
209    case 'spki': {
210      verifyAcceptableCfrgKeyUse(name, true, usagesSet);
211      try {
212        keyObject = createPublicKey({
213          key: keyData,
214          format: 'der',
215          type: 'spki',
216        });
217      } catch (err) {
218        throw lazyDOMException(
219          'Invalid keyData', { name: 'DataError', cause: err });
220      }
221      break;
222    }
223    case 'pkcs8': {
224      verifyAcceptableCfrgKeyUse(name, false, usagesSet);
225      try {
226        keyObject = createPrivateKey({
227          key: keyData,
228          format: 'der',
229          type: 'pkcs8',
230        });
231      } catch (err) {
232        throw lazyDOMException(
233          'Invalid keyData', { name: 'DataError', cause: err });
234      }
235      break;
236    }
237    case 'jwk': {
238      if (!keyData.kty)
239        throw lazyDOMException('Invalid keyData', 'DataError');
240      if (keyData.kty !== 'OKP')
241        throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
242      if (keyData.crv !== name)
243        throw lazyDOMException(
244          'JWK "crv" Parameter and algorithm name mismatch', 'DataError');
245      const isPublic = keyData.d === undefined;
246
247      if (usagesSet.size > 0 && keyData.use !== undefined) {
248        let checkUse;
249        switch (name) {
250          case 'Ed25519':
251            // Fall through
252          case 'Ed448':
253            checkUse = 'sig';
254            break;
255          case 'X25519':
256            // Fall through
257          case 'X448':
258            checkUse = 'enc';
259            break;
260        }
261        if (keyData.use !== checkUse)
262          throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
263      }
264
265      validateKeyOps(keyData.key_ops, usagesSet);
266
267      if (keyData.ext !== undefined &&
268          keyData.ext === false &&
269          extractable === true) {
270        throw lazyDOMException(
271          'JWK "ext" Parameter and extractable mismatch',
272          'DataError');
273      }
274
275      if (keyData.alg !== undefined) {
276        if (
277          (name === 'Ed25519' || name === 'Ed448') &&
278          keyData.alg !== 'EdDSA'
279        ) {
280          throw lazyDOMException(
281            'JWK "alg" does not match the requested algorithm',
282            'DataError');
283        }
284      }
285
286      if (!isPublic && typeof keyData.x !== 'string') {
287        throw lazyDOMException('Invalid JWK', 'DataError');
288      }
289
290      verifyAcceptableCfrgKeyUse(
291        name,
292        isPublic,
293        usagesSet);
294
295      const publicKeyObject = createCFRGRawKey(
296        name,
297        Buffer.from(keyData.x, 'base64'),
298        true);
299
300      if (isPublic) {
301        keyObject = publicKeyObject;
302      } else {
303        keyObject = createCFRGRawKey(
304          name,
305          Buffer.from(keyData.d, 'base64'),
306          false);
307
308        if (!createPublicKey(keyObject).equals(publicKeyObject)) {
309          throw lazyDOMException('Invalid JWK', 'DataError');
310        }
311      }
312      break;
313    }
314    case 'raw': {
315      verifyAcceptableCfrgKeyUse(name, true, usagesSet);
316      keyObject = createCFRGRawKey(name, keyData, true);
317      break;
318    }
319  }
320
321  if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
322    throw lazyDOMException('Invalid key type', 'DataError');
323  }
324
325  return new InternalCryptoKey(
326    keyObject,
327    { name },
328    keyUsages,
329    extractable);
330}
331
332function eddsaSignVerify(key, data, { name, context }, signature) {
333  emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
334  const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
335  const type = mode === kSignJobModeSign ? 'private' : 'public';
336
337  if (key.type !== type)
338    throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
339
340  if (name === 'Ed448' && context?.byteLength) {
341    throw lazyDOMException(
342      'Non zero-length context is not yet supported.', 'NotSupportedError');
343  }
344
345  return jobPromise(() => new SignJob(
346    kCryptoJobAsync,
347    mode,
348    key[kKeyObject][kHandle],
349    undefined,
350    undefined,
351    undefined,
352    data,
353    undefined,
354    undefined,
355    undefined,
356    undefined,
357    signature));
358}
359
360module.exports = {
361  cfrgExportKey,
362  cfrgImportKey,
363  cfrgGenerateKey,
364  eddsaSignVerify,
365};
366