• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ObjectDefineProperty,
5  Symbol,
6} = primordials;
7
8const {
9  KeyObjectHandle,
10  createNativeKeyObjectClass,
11  kKeyTypeSecret,
12  kKeyTypePublic,
13  kKeyTypePrivate,
14  kKeyFormatPEM,
15  kKeyFormatDER,
16  kKeyEncodingPKCS1,
17  kKeyEncodingPKCS8,
18  kKeyEncodingSPKI,
19  kKeyEncodingSEC1
20} = internalBinding('crypto');
21const {
22  ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
23  ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
24  ERR_INVALID_ARG_TYPE,
25  ERR_INVALID_ARG_VALUE,
26  ERR_INVALID_OPT_VALUE,
27  ERR_OUT_OF_RANGE
28} = require('internal/errors').codes;
29const { kHandle } = require('internal/crypto/util');
30
31const { isArrayBufferView } = require('internal/util/types');
32
33const kKeyType = Symbol('kKeyType');
34
35// Key input contexts.
36const kConsumePublic = 0;
37const kConsumePrivate = 1;
38const kCreatePublic = 2;
39const kCreatePrivate = 3;
40
41const encodingNames = [];
42for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
43                 [kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
44  encodingNames[m[0]] = m[1];
45
46// Creating the KeyObject class is a little complicated due to inheritance
47// and that fact that KeyObjects should be transferrable between threads,
48// which requires the KeyObject base class to be implemented in C++.
49// The creation requires a callback to make sure that the NativeKeyObject
50// base class cannot exist without the other KeyObject implementations.
51const {
52  0: KeyObject,
53  1: SecretKeyObject,
54  2: PublicKeyObject,
55  3: PrivateKeyObject,
56} = createNativeKeyObjectClass((NativeKeyObject) => {
57  // Publicly visible KeyObject class.
58  class KeyObject extends NativeKeyObject {
59    constructor(type, handle) {
60      if (type !== 'secret' && type !== 'public' && type !== 'private')
61        throw new ERR_INVALID_ARG_VALUE('type', type);
62      if (typeof handle !== 'object' || !(handle instanceof KeyObjectHandle))
63        throw new ERR_INVALID_ARG_TYPE('handle', 'object', handle);
64
65      super(handle);
66
67      this[kKeyType] = type;
68
69      ObjectDefineProperty(this, kHandle, {
70        value: handle,
71        enumerable: false,
72        configurable: false,
73        writable: false
74      });
75    }
76
77    get type() {
78      return this[kKeyType];
79    }
80  }
81
82  class SecretKeyObject extends KeyObject {
83    constructor(handle) {
84      super('secret', handle);
85    }
86
87    get symmetricKeySize() {
88      return this[kHandle].getSymmetricKeySize();
89    }
90
91    export() {
92      return this[kHandle].export();
93    }
94  }
95
96  const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
97
98  class AsymmetricKeyObject extends KeyObject {
99    get asymmetricKeyType() {
100      return this[kAsymmetricKeyType] ||
101             (this[kAsymmetricKeyType] = this[kHandle].getAsymmetricKeyType());
102    }
103  }
104
105  class PublicKeyObject extends AsymmetricKeyObject {
106    constructor(handle) {
107      super('public', handle);
108    }
109
110    export(encoding) {
111      const {
112        format,
113        type
114      } = parsePublicKeyEncoding(encoding, this.asymmetricKeyType);
115      return this[kHandle].export(format, type);
116    }
117  }
118
119  class PrivateKeyObject extends AsymmetricKeyObject {
120    constructor(handle) {
121      super('private', handle);
122    }
123
124    export(encoding) {
125      const {
126        format,
127        type,
128        cipher,
129        passphrase
130      } = parsePrivateKeyEncoding(encoding, this.asymmetricKeyType);
131      return this[kHandle].export(format, type, cipher, passphrase);
132    }
133  }
134
135  return [KeyObject, SecretKeyObject, PublicKeyObject, PrivateKeyObject];
136});
137
138function parseKeyFormat(formatStr, defaultFormat, optionName) {
139  if (formatStr === undefined && defaultFormat !== undefined)
140    return defaultFormat;
141  else if (formatStr === 'pem')
142    return kKeyFormatPEM;
143  else if (formatStr === 'der')
144    return kKeyFormatDER;
145  throw new ERR_INVALID_OPT_VALUE(optionName, formatStr);
146}
147
148function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
149  if (typeStr === undefined && !required) {
150    return undefined;
151  } else if (typeStr === 'pkcs1') {
152    if (keyType !== undefined && keyType !== 'rsa') {
153      throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
154        typeStr, 'can only be used for RSA keys');
155    }
156    return kKeyEncodingPKCS1;
157  } else if (typeStr === 'spki' && isPublic !== false) {
158    return kKeyEncodingSPKI;
159  } else if (typeStr === 'pkcs8' && isPublic !== true) {
160    return kKeyEncodingPKCS8;
161  } else if (typeStr === 'sec1' && isPublic !== true) {
162    if (keyType !== undefined && keyType !== 'ec') {
163      throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
164        typeStr, 'can only be used for EC keys');
165    }
166    return kKeyEncodingSEC1;
167  }
168
169  throw new ERR_INVALID_OPT_VALUE(optionName, typeStr);
170}
171
172function option(name, objName) {
173  return objName === undefined ? name : `${objName}.${name}`;
174}
175
176function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
177  const { format: formatStr, type: typeStr } = enc;
178
179  const isInput = keyType === undefined;
180  const format = parseKeyFormat(formatStr,
181                                isInput ? kKeyFormatPEM : undefined,
182                                option('format', objName));
183
184  const type = parseKeyType(typeStr,
185                            !isInput || format === kKeyFormatDER,
186                            keyType,
187                            isPublic,
188                            option('type', objName));
189
190  return { format, type };
191}
192
193function isStringOrBuffer(val) {
194  return typeof val === 'string' || isArrayBufferView(val);
195}
196
197function parseKeyEncoding(enc, keyType, isPublic, objName) {
198  if (enc === null || typeof enc !== 'object')
199    throw new ERR_INVALID_ARG_TYPE('options', 'object', enc);
200
201  const isInput = keyType === undefined;
202
203  const {
204    format,
205    type
206  } = parseKeyFormatAndType(enc, keyType, isPublic, objName);
207
208  let cipher, passphrase;
209  if (isPublic !== true) {
210    ({ cipher, passphrase } = enc);
211
212    if (!isInput) {
213      if (cipher != null) {
214        if (typeof cipher !== 'string')
215          throw new ERR_INVALID_OPT_VALUE(option('cipher', objName), cipher);
216        if (format === kKeyFormatDER &&
217            (type === kKeyEncodingPKCS1 ||
218             type === kKeyEncodingSEC1)) {
219          throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
220            encodingNames[type], 'does not support encryption');
221        }
222      } else if (passphrase !== undefined) {
223        throw new ERR_INVALID_OPT_VALUE(option('cipher', objName), cipher);
224      }
225    }
226
227    if ((isInput && passphrase !== undefined &&
228         !isStringOrBuffer(passphrase)) ||
229        (!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
230      throw new ERR_INVALID_OPT_VALUE(option('passphrase', objName),
231                                      passphrase);
232    }
233  }
234
235  return { format, type, cipher, passphrase };
236}
237
238// Parses the public key encoding based on an object. keyType must be undefined
239// when this is used to parse an input encoding and must be a valid key type if
240// used to parse an output encoding.
241function parsePublicKeyEncoding(enc, keyType, objName) {
242  return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName);
243}
244
245// Parses the private key encoding based on an object. keyType must be undefined
246// when this is used to parse an input encoding and must be a valid key type if
247// used to parse an output encoding.
248function parsePrivateKeyEncoding(enc, keyType, objName) {
249  return parseKeyEncoding(enc, keyType, false, objName);
250}
251
252function getKeyObjectHandle(key, ctx) {
253  if (ctx === kCreatePrivate) {
254    throw new ERR_INVALID_ARG_TYPE(
255      'key',
256      ['string', 'Buffer', 'TypedArray', 'DataView'],
257      key
258    );
259  }
260
261  if (key.type !== 'private') {
262    if (ctx === kConsumePrivate || ctx === kCreatePublic)
263      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'private');
264    if (key.type !== 'public') {
265      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type,
266                                                   'private or public');
267    }
268  }
269
270  return key[kHandle];
271}
272
273function prepareAsymmetricKey(key, ctx) {
274  if (isKeyObject(key)) {
275    // Best case: A key object, as simple as that.
276    return { data: getKeyObjectHandle(key, ctx) };
277  } else if (typeof key === 'string' || isArrayBufferView(key)) {
278    // Expect PEM by default, mostly for backward compatibility.
279    return { format: kKeyFormatPEM, data: key };
280  } else if (typeof key === 'object') {
281    const data = key.key;
282    // The 'key' property can be a KeyObject as well to allow specifying
283    // additional options such as padding along with the key.
284    if (isKeyObject(data))
285      return { data: getKeyObjectHandle(data, ctx) };
286    // Either PEM or DER using PKCS#1 or SPKI.
287    if (!isStringOrBuffer(data)) {
288      throw new ERR_INVALID_ARG_TYPE(
289        'key.key',
290        ['string', 'Buffer', 'TypedArray', 'DataView',
291         ...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
292        data);
293    }
294
295    const isPublic =
296      (ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined;
297    return { data, ...parseKeyEncoding(key, undefined, isPublic) };
298  }
299  throw new ERR_INVALID_ARG_TYPE(
300    'key',
301    ['string', 'Buffer', 'TypedArray', 'DataView',
302     ...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
303    key
304  );
305}
306
307function preparePrivateKey(key) {
308  return prepareAsymmetricKey(key, kConsumePrivate);
309}
310
311function preparePublicOrPrivateKey(key) {
312  return prepareAsymmetricKey(key, kConsumePublic);
313}
314
315function prepareSecretKey(key, bufferOnly = false) {
316  if (!isArrayBufferView(key) && (bufferOnly || typeof key !== 'string')) {
317    if (isKeyObject(key) && !bufferOnly) {
318      if (key.type !== 'secret')
319        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
320      return key[kHandle];
321    }
322    throw new ERR_INVALID_ARG_TYPE(
323      'key',
324      ['Buffer', 'TypedArray', 'DataView',
325       ...(bufferOnly ? [] : ['string', 'KeyObject'])],
326      key);
327  }
328  return key;
329}
330
331function createSecretKey(key) {
332  key = prepareSecretKey(key, true);
333  if (key.byteLength === 0)
334    throw new ERR_OUT_OF_RANGE('key.byteLength', '> 0', key.byteLength);
335  const handle = new KeyObjectHandle();
336  handle.init(kKeyTypeSecret, key);
337  return new SecretKeyObject(handle);
338}
339
340function createPublicKey(key) {
341  const { format, type, data } = prepareAsymmetricKey(key, kCreatePublic);
342  const handle = new KeyObjectHandle();
343  handle.init(kKeyTypePublic, data, format, type);
344  return new PublicKeyObject(handle);
345}
346
347function createPrivateKey(key) {
348  const { format, type, data, passphrase } =
349    prepareAsymmetricKey(key, kCreatePrivate);
350  const handle = new KeyObjectHandle();
351  handle.init(kKeyTypePrivate, data, format, type, passphrase);
352  return new PrivateKeyObject(handle);
353}
354
355function isKeyObject(key) {
356  return key instanceof KeyObject;
357}
358
359module.exports = {
360  // Public API.
361  createSecretKey,
362  createPublicKey,
363  createPrivateKey,
364  KeyObject,
365
366  // These are designed for internal use only and should not be exposed.
367  parsePublicKeyEncoding,
368  parsePrivateKeyEncoding,
369  preparePrivateKey,
370  preparePublicOrPrivateKey,
371  prepareSecretKey,
372  SecretKeyObject,
373  PublicKeyObject,
374  PrivateKeyObject,
375  isKeyObject
376};
377