• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  FunctionPrototypeCall,
5  ObjectDefineProperty,
6  SafeArrayIterator,
7} = primordials;
8
9const {
10  DhKeyPairGenJob,
11  DsaKeyPairGenJob,
12  EcKeyPairGenJob,
13  NidKeyPairGenJob,
14  RsaKeyPairGenJob,
15  SecretKeyGenJob,
16  kCryptoJobAsync,
17  kCryptoJobSync,
18  kKeyVariantRSA_PSS,
19  kKeyVariantRSA_SSA_PKCS1_v1_5,
20  EVP_PKEY_ED25519,
21  EVP_PKEY_ED448,
22  EVP_PKEY_X25519,
23  EVP_PKEY_X448,
24  OPENSSL_EC_NAMED_CURVE,
25  OPENSSL_EC_EXPLICIT_CURVE,
26} = internalBinding('crypto');
27
28const {
29  PublicKeyObject,
30  PrivateKeyObject,
31  SecretKeyObject,
32  parsePublicKeyEncoding,
33  parsePrivateKeyEncoding,
34} = require('internal/crypto/keys');
35
36const {
37  kAesKeyLengths,
38} = require('internal/crypto/util');
39
40const {
41  customPromisifyArgs,
42  kEmptyObject,
43} = require('internal/util');
44
45const {
46  validateFunction,
47  validateBuffer,
48  validateString,
49  validateInteger,
50  validateObject,
51  validateOneOf,
52  validateInt32,
53  validateUint32,
54} = require('internal/validators');
55
56const {
57  codes: {
58    ERR_INCOMPATIBLE_OPTION_PAIR,
59    ERR_INVALID_ARG_VALUE,
60    ERR_MISSING_OPTION,
61  },
62} = require('internal/errors');
63
64const { isArrayBufferView } = require('internal/util/types');
65
66const { getOptionValue } = require('internal/options');
67
68function isJwk(obj) {
69  return obj != null && obj.kty !== undefined;
70}
71
72function wrapKey(key, ctor) {
73  if (typeof key === 'string' ||
74      isArrayBufferView(key) ||
75      isJwk(key))
76    return key;
77  return new ctor(key);
78}
79
80function generateKeyPair(type, options, callback) {
81  if (typeof options === 'function') {
82    callback = options;
83    options = undefined;
84  }
85  validateFunction(callback, 'callback');
86
87  const job = createJob(kCryptoJobAsync, type, options);
88
89  job.ondone = (error, result) => {
90    if (error) return FunctionPrototypeCall(callback, job, error);
91    // If no encoding was chosen, return key objects instead.
92    let { 0: pubkey, 1: privkey } = result;
93    pubkey = wrapKey(pubkey, PublicKeyObject);
94    privkey = wrapKey(privkey, PrivateKeyObject);
95    FunctionPrototypeCall(callback, job, null, pubkey, privkey);
96  };
97
98  job.run();
99}
100
101ObjectDefineProperty(generateKeyPair, customPromisifyArgs, {
102  __proto__: null,
103  value: ['publicKey', 'privateKey'],
104  enumerable: false,
105});
106
107function generateKeyPairSync(type, options) {
108  return handleError(createJob(kCryptoJobSync, type, options).run());
109}
110
111function handleError(ret) {
112  if (ret == null)
113    return; // async
114
115  const { 0: err, 1: keys } = ret;
116  if (err !== undefined)
117    throw err;
118
119  const { 0: publicKey, 1: privateKey } = keys;
120
121  // If no encoding was chosen, return key objects instead.
122  return {
123    publicKey: wrapKey(publicKey, PublicKeyObject),
124    privateKey: wrapKey(privateKey, PrivateKeyObject),
125  };
126}
127
128function parseKeyEncoding(keyType, options = kEmptyObject) {
129  const { publicKeyEncoding, privateKeyEncoding } = options;
130
131  let publicFormat, publicType;
132  if (publicKeyEncoding == null) {
133    publicFormat = publicType = undefined;
134  } else if (typeof publicKeyEncoding === 'object') {
135    ({
136      format: publicFormat,
137      type: publicType,
138    } = parsePublicKeyEncoding(publicKeyEncoding, keyType,
139                               'publicKeyEncoding'));
140  } else {
141    throw new ERR_INVALID_ARG_VALUE('options.publicKeyEncoding',
142                                    publicKeyEncoding);
143  }
144
145  let privateFormat, privateType, cipher, passphrase;
146  if (privateKeyEncoding == null) {
147    privateFormat = privateType = undefined;
148  } else if (typeof privateKeyEncoding === 'object') {
149    ({
150      format: privateFormat,
151      type: privateType,
152      cipher,
153      passphrase,
154    } = parsePrivateKeyEncoding(privateKeyEncoding, keyType,
155                                'privateKeyEncoding'));
156  } else {
157    throw new ERR_INVALID_ARG_VALUE('options.privateKeyEncoding',
158                                    privateKeyEncoding);
159  }
160
161  return [
162    publicFormat,
163    publicType,
164    privateFormat,
165    privateType,
166    cipher,
167    passphrase,
168  ];
169}
170
171function createJob(mode, type, options) {
172  validateString(type, 'type');
173
174  const encoding = new SafeArrayIterator(parseKeyEncoding(type, options));
175
176  if (options !== undefined)
177    validateObject(options, 'options');
178
179  switch (type) {
180    case 'rsa':
181    case 'rsa-pss':
182    {
183      validateObject(options, 'options');
184      const { modulusLength } = options;
185      validateUint32(modulusLength, 'options.modulusLength');
186
187      let { publicExponent } = options;
188      if (publicExponent == null) {
189        publicExponent = 0x10001;
190      } else {
191        validateUint32(publicExponent, 'options.publicExponent');
192      }
193
194      if (type === 'rsa') {
195        return new RsaKeyPairGenJob(
196          mode,
197          kKeyVariantRSA_SSA_PKCS1_v1_5,  // Used also for RSA-OAEP
198          modulusLength,
199          publicExponent,
200          ...encoding);
201      }
202
203      const {
204        hash, mgf1Hash, hashAlgorithm, mgf1HashAlgorithm, saltLength,
205      } = options;
206
207      const pendingDeprecation = getOptionValue('--pending-deprecation');
208
209      if (saltLength !== undefined)
210        validateInt32(saltLength, 'options.saltLength', 0);
211      if (hashAlgorithm !== undefined)
212        validateString(hashAlgorithm, 'options.hashAlgorithm');
213      if (mgf1HashAlgorithm !== undefined)
214        validateString(mgf1HashAlgorithm, 'options.mgf1HashAlgorithm');
215      if (hash !== undefined) {
216        pendingDeprecation && process.emitWarning(
217          '"options.hash" is deprecated, ' +
218          'use "options.hashAlgorithm" instead.',
219          'DeprecationWarning',
220          'DEP0154');
221        validateString(hash, 'options.hash');
222        if (hashAlgorithm && hash !== hashAlgorithm) {
223          throw new ERR_INVALID_ARG_VALUE('options.hash', hash);
224        }
225      }
226      if (mgf1Hash !== undefined) {
227        pendingDeprecation && process.emitWarning(
228          '"options.mgf1Hash" is deprecated, ' +
229          'use "options.mgf1HashAlgorithm" instead.',
230          'DeprecationWarning',
231          'DEP0154');
232        validateString(mgf1Hash, 'options.mgf1Hash');
233        if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) {
234          throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash);
235        }
236      }
237
238      return new RsaKeyPairGenJob(
239        mode,
240        kKeyVariantRSA_PSS,
241        modulusLength,
242        publicExponent,
243        hashAlgorithm || hash,
244        mgf1HashAlgorithm || mgf1Hash,
245        saltLength,
246        ...encoding);
247    }
248    case 'dsa':
249    {
250      validateObject(options, 'options');
251      const { modulusLength } = options;
252      validateUint32(modulusLength, 'options.modulusLength');
253
254      let { divisorLength } = options;
255      if (divisorLength == null) {
256        divisorLength = -1;
257      } else
258        validateInt32(divisorLength, 'options.divisorLength', 0);
259
260      return new DsaKeyPairGenJob(
261        mode,
262        modulusLength,
263        divisorLength,
264        ...encoding);
265    }
266    case 'ec':
267    {
268      validateObject(options, 'options');
269      const { namedCurve } = options;
270      validateString(namedCurve, 'options.namedCurve');
271      let { paramEncoding } = options;
272      if (paramEncoding == null || paramEncoding === 'named')
273        paramEncoding = OPENSSL_EC_NAMED_CURVE;
274      else if (paramEncoding === 'explicit')
275        paramEncoding = OPENSSL_EC_EXPLICIT_CURVE;
276      else
277        throw new ERR_INVALID_ARG_VALUE('options.paramEncoding', paramEncoding);
278
279      return new EcKeyPairGenJob(
280        mode,
281        namedCurve,
282        paramEncoding,
283        ...encoding);
284    }
285    case 'ed25519':
286    case 'ed448':
287    case 'x25519':
288    case 'x448':
289    {
290      let id;
291      switch (type) {
292        case 'ed25519':
293          id = EVP_PKEY_ED25519;
294          break;
295        case 'ed448':
296          id = EVP_PKEY_ED448;
297          break;
298        case 'x25519':
299          id = EVP_PKEY_X25519;
300          break;
301        case 'x448':
302          id = EVP_PKEY_X448;
303          break;
304      }
305      return new NidKeyPairGenJob(mode, id, ...encoding);
306    }
307    case 'dh':
308    {
309      validateObject(options, 'options');
310      const { group, primeLength, prime, generator } = options;
311      if (group != null) {
312        if (prime != null)
313          throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
314        if (primeLength != null)
315          throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
316        if (generator != null)
317          throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
318
319        validateString(group, 'options.group');
320
321        return new DhKeyPairGenJob(mode, group, ...encoding);
322      }
323
324      if (prime != null) {
325        if (primeLength != null)
326          throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
327
328        validateBuffer(prime, 'options.prime');
329      } else if (primeLength != null) {
330        validateInt32(primeLength, 'options.primeLength', 0);
331      } else {
332        throw new ERR_MISSING_OPTION(
333          'At least one of the group, prime, or primeLength options');
334      }
335
336      if (generator != null) {
337        validateInt32(generator, 'options.generator', 0);
338      }
339      return new DhKeyPairGenJob(
340        mode,
341        prime != null ? prime : primeLength,
342        generator == null ? 2 : generator,
343        ...encoding);
344    }
345    default:
346      // Fall through
347  }
348  throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type');
349}
350
351// Symmetric Key Generation
352
353function generateKeyJob(mode, keyType, options) {
354  validateString(keyType, 'type');
355  validateObject(options, 'options');
356  const { length } = options;
357  switch (keyType) {
358    case 'hmac':
359      validateInteger(length, 'options.length', 8, 2 ** 31 - 1);
360      break;
361    case 'aes':
362      validateOneOf(length, 'options.length', kAesKeyLengths);
363      break;
364    default:
365      throw new ERR_INVALID_ARG_VALUE(
366        'type',
367        keyType,
368        'must be a supported key type');
369  }
370
371  return new SecretKeyGenJob(mode, length);
372}
373
374function handleGenerateKeyError(ret) {
375  if (ret === undefined)
376    return; // async
377
378  const { 0: err, 1: key } = ret;
379  if (err !== undefined)
380    throw err;
381
382  return wrapKey(key, SecretKeyObject);
383}
384
385function generateKey(type, options, callback) {
386  if (typeof options === 'function') {
387    callback = options;
388    options = undefined;
389  }
390
391  validateFunction(callback, 'callback');
392
393  const job = generateKeyJob(kCryptoJobAsync, type, options);
394
395  job.ondone = (error, key) => {
396    if (error) return FunctionPrototypeCall(callback, job, error);
397    FunctionPrototypeCall(callback, job, null, wrapKey(key, SecretKeyObject));
398  };
399
400  handleGenerateKeyError(job.run());
401}
402
403function generateKeySync(type, options) {
404  return handleGenerateKeyError(
405    generateKeyJob(kCryptoJobSync, type, options).run());
406}
407
408module.exports = {
409  generateKeyPair,
410  generateKeyPairSync,
411  generateKey,
412  generateKeySync,
413};
414