• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypeIncludes,
5  ObjectKeys,
6  SafeSet,
7} = primordials;
8
9const {
10  ECKeyExportJob,
11  KeyObjectHandle,
12  SignJob,
13  kCryptoJobAsync,
14  kKeyTypePrivate,
15  kSignJobModeSign,
16  kSignJobModeVerify,
17  kSigEncP1363,
18} = internalBinding('crypto');
19
20const {
21  getUsagesUnion,
22  hasAnyNotIn,
23  jobPromise,
24  normalizeHashName,
25  validateKeyOps,
26  kHandle,
27  kKeyObject,
28  kNamedCurveAliases,
29} = require('internal/crypto/util');
30
31const {
32  lazyDOMException,
33  promisify,
34} = require('internal/util');
35
36const {
37  generateKeyPair: _generateKeyPair,
38} = require('internal/crypto/keygen');
39
40const {
41  InternalCryptoKey,
42  PrivateKeyObject,
43  PublicKeyObject,
44  createPrivateKey,
45  createPublicKey,
46} = require('internal/crypto/keys');
47
48const generateKeyPair = promisify(_generateKeyPair);
49
50function verifyAcceptableEcKeyUse(name, isPublic, usages) {
51  let checkSet;
52  switch (name) {
53    case 'ECDH':
54      checkSet = isPublic ? [] : ['deriveKey', 'deriveBits'];
55      break;
56    case 'ECDSA':
57      checkSet = isPublic ? ['verify'] : ['sign'];
58      break;
59    default:
60      throw lazyDOMException(
61        'The algorithm is not supported', 'NotSupportedError');
62  }
63  if (hasAnyNotIn(usages, checkSet)) {
64    throw lazyDOMException(
65      `Unsupported key usage for a ${name} key`,
66      'SyntaxError');
67  }
68}
69
70function createECPublicKeyRaw(namedCurve, keyData) {
71  const handle = new KeyObjectHandle();
72
73  if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) {
74    throw lazyDOMException('Invalid keyData', 'DataError');
75  }
76
77  return new PublicKeyObject(handle);
78}
79
80async function ecGenerateKey(algorithm, extractable, keyUsages) {
81  const { name, namedCurve } = algorithm;
82
83  if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) {
84    throw lazyDOMException(
85      'Unrecognized namedCurve',
86      'NotSupportedError');
87  }
88
89  const usageSet = new SafeSet(keyUsages);
90  switch (name) {
91    case 'ECDSA':
92      if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
93        throw lazyDOMException(
94          'Unsupported key usage for an ECDSA key',
95          'SyntaxError');
96      }
97      break;
98    case 'ECDH':
99      if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
100        throw lazyDOMException(
101          'Unsupported key usage for an ECDH key',
102          'SyntaxError');
103      }
104      // Fall through
105  }
106
107  const keypair = await generateKeyPair('ec', { namedCurve }).catch((err) => {
108    throw lazyDOMException(
109      'The operation failed for an operation-specific reason',
110      { name: 'OperationError', cause: err });
111  });
112
113  let publicUsages;
114  let privateUsages;
115  switch (name) {
116    case 'ECDSA':
117      publicUsages = getUsagesUnion(usageSet, 'verify');
118      privateUsages = getUsagesUnion(usageSet, 'sign');
119      break;
120    case 'ECDH':
121      publicUsages = [];
122      privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
123      break;
124  }
125
126  const keyAlgorithm = { name, namedCurve };
127
128  const publicKey =
129    new InternalCryptoKey(
130      keypair.publicKey,
131      keyAlgorithm,
132      publicUsages,
133      true);
134
135  const privateKey =
136    new InternalCryptoKey(
137      keypair.privateKey,
138      keyAlgorithm,
139      privateUsages,
140      extractable);
141
142  return { publicKey, privateKey };
143}
144
145function ecExportKey(key, format) {
146  return jobPromise(() => new ECKeyExportJob(
147    kCryptoJobAsync,
148    format,
149    key[kKeyObject][kHandle]));
150}
151
152async function ecImportKey(
153  format,
154  keyData,
155  algorithm,
156  extractable,
157  keyUsages) {
158
159  const { name, namedCurve } = algorithm;
160
161  if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) {
162    throw lazyDOMException(
163      'Unrecognized namedCurve',
164      'NotSupportedError');
165  }
166
167  let keyObject;
168  const usagesSet = new SafeSet(keyUsages);
169  switch (format) {
170    case 'spki': {
171      verifyAcceptableEcKeyUse(name, true, usagesSet);
172      try {
173        keyObject = createPublicKey({
174          key: keyData,
175          format: 'der',
176          type: 'spki',
177        });
178      } catch (err) {
179        throw lazyDOMException(
180          'Invalid keyData', { name: 'DataError', cause: err });
181      }
182      break;
183    }
184    case 'pkcs8': {
185      verifyAcceptableEcKeyUse(name, false, usagesSet);
186      try {
187        keyObject = createPrivateKey({
188          key: keyData,
189          format: 'der',
190          type: 'pkcs8',
191        });
192      } catch (err) {
193        throw lazyDOMException(
194          'Invalid keyData', { name: 'DataError', cause: err });
195      }
196      break;
197    }
198    case 'jwk': {
199      if (!keyData.kty)
200        throw lazyDOMException('Invalid keyData', 'DataError');
201      if (keyData.kty !== 'EC')
202        throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
203      if (keyData.crv !== namedCurve)
204        throw lazyDOMException(
205          'JWK "crv" does not match the requested algorithm',
206          'DataError');
207
208      verifyAcceptableEcKeyUse(
209        name,
210        keyData.d === undefined,
211        usagesSet);
212
213      if (usagesSet.size > 0 && keyData.use !== undefined) {
214        const checkUse = name === 'ECDH' ? 'enc' : 'sig';
215        if (keyData.use !== checkUse)
216          throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
217      }
218
219      validateKeyOps(keyData.key_ops, usagesSet);
220
221      if (keyData.ext !== undefined &&
222          keyData.ext === false &&
223          extractable === true) {
224        throw lazyDOMException(
225          'JWK "ext" Parameter and extractable mismatch',
226          'DataError');
227      }
228
229      if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
230        let algNamedCurve;
231        switch (keyData.alg) {
232          case 'ES256': algNamedCurve = 'P-256'; break;
233          case 'ES384': algNamedCurve = 'P-384'; break;
234          case 'ES512': algNamedCurve = 'P-521'; break;
235        }
236        if (algNamedCurve !== namedCurve)
237          throw lazyDOMException(
238            'JWK "alg" does not match the requested algorithm',
239            'DataError');
240      }
241
242      const handle = new KeyObjectHandle();
243      const type = handle.initJwk(keyData, namedCurve);
244      if (type === undefined)
245        throw lazyDOMException('Invalid JWK', 'DataError');
246      keyObject = type === kKeyTypePrivate ?
247        new PrivateKeyObject(handle) :
248        new PublicKeyObject(handle);
249      break;
250    }
251    case 'raw': {
252      verifyAcceptableEcKeyUse(name, true, usagesSet);
253      keyObject = createECPublicKeyRaw(namedCurve, keyData);
254      break;
255    }
256  }
257
258  switch (algorithm.name) {
259    case 'ECDSA':
260      // Fall through
261    case 'ECDH':
262      if (keyObject.asymmetricKeyType !== 'ec')
263        throw lazyDOMException('Invalid key type', 'DataError');
264      break;
265  }
266
267  if (!keyObject[kHandle].checkEcKeyData()) {
268    throw lazyDOMException('Invalid keyData', 'DataError');
269  }
270
271  const {
272    namedCurve: checkNamedCurve,
273  } = keyObject[kHandle].keyDetail({});
274  if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
275    throw lazyDOMException('Named curve mismatch', 'DataError');
276
277  return new InternalCryptoKey(
278    keyObject,
279    { name, namedCurve },
280    keyUsages,
281    extractable);
282}
283
284function ecdsaSignVerify(key, data, { name, hash }, signature) {
285  const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
286  const type = mode === kSignJobModeSign ? 'private' : 'public';
287
288  if (key.type !== type)
289    throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
290
291  const hashname = normalizeHashName(hash.name);
292
293  return jobPromise(() => new SignJob(
294    kCryptoJobAsync,
295    mode,
296    key[kKeyObject][kHandle],
297    undefined,
298    undefined,
299    undefined,
300    data,
301    hashname,
302    undefined,  // Salt length, not used with ECDSA
303    undefined,  // PSS Padding, not used with ECDSA
304    kSigEncP1363,
305    signature));
306}
307
308module.exports = {
309  ecExportKey,
310  ecImportKey,
311  ecGenerateKey,
312  ecdsaSignVerify,
313};
314