• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayIsArray,
5  ArrayPrototypeFilter,
6  ArrayPrototypeForEach,
7  ArrayPrototypeJoin,
8  StringPrototypeSplit,
9  StringPrototypeStartsWith,
10} = primordials;
11
12const {
13  codes: {
14    ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
15    ERR_INVALID_ARG_TYPE,
16    ERR_INVALID_ARG_VALUE,
17  },
18} = require('internal/errors');
19
20const {
21  kEmptyObject,
22} = require('internal/util');
23
24const {
25  isArrayBufferView,
26} = require('internal/util/types');
27
28const {
29  validateBuffer,
30  validateInt32,
31  validateObject,
32  validateString,
33} = require('internal/validators');
34
35const {
36  toBuf,
37} = require('internal/crypto/util');
38
39const {
40  crypto: {
41    TLS1_2_VERSION,
42    TLS1_3_VERSION,
43  },
44} = internalBinding('constants');
45
46function getDefaultEcdhCurve() {
47  // We do it this way because DEFAULT_ECDH_CURVE can be
48  // changed by users, so we need to grab the current
49  // value, but we want the evaluation to be lazy.
50  return require('tls').DEFAULT_ECDH_CURVE || 'auto';
51}
52
53function getDefaultCiphers() {
54  // We do it this way because DEFAULT_CIPHERS can be
55  // changed by users, so we need to grab the current
56  // value, but we want the evaluation to be lazy.
57  return require('tls').DEFAULT_CIPHERS;
58}
59
60function addCACerts(context, certs, name) {
61  ArrayPrototypeForEach(certs, (cert) => {
62    validateKeyOrCertOption(name, cert);
63    context.addCACert(cert);
64  });
65}
66
67function setCerts(context, certs, name) {
68  ArrayPrototypeForEach(certs, (cert) => {
69    validateKeyOrCertOption(name, cert);
70    context.setCert(cert);
71  });
72}
73
74function validateKeyOrCertOption(name, value) {
75  if (typeof value !== 'string' && !isArrayBufferView(value)) {
76    throw new ERR_INVALID_ARG_TYPE(
77      name,
78      [
79        'string',
80        'Buffer',
81        'TypedArray',
82        'DataView',
83      ],
84      value,
85    );
86  }
87}
88
89function setKey(context, key, passphrase, name) {
90  validateKeyOrCertOption(`${name}.key`, key);
91  if (passphrase !== undefined && passphrase !== null)
92    validateString(passphrase, `${name}.passphrase`);
93  context.setKey(key, passphrase);
94}
95
96function processCiphers(ciphers, name) {
97  ciphers = StringPrototypeSplit(ciphers || getDefaultCiphers(), ':');
98
99  const cipherList =
100    ArrayPrototypeJoin(
101      ArrayPrototypeFilter(
102        ciphers,
103        (cipher) => {
104          return cipher.length > 0 &&
105            !StringPrototypeStartsWith(cipher, 'TLS_');
106        }), ':');
107
108  const cipherSuites =
109    ArrayPrototypeJoin(
110      ArrayPrototypeFilter(
111        ciphers,
112        (cipher) => {
113          return cipher.length > 0 &&
114            StringPrototypeStartsWith(cipher, 'TLS_');
115        }), ':');
116
117  // Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its
118  // not possible to handshake with no suites.
119  if (cipherSuites === '' && cipherList === '')
120    throw new ERR_INVALID_ARG_VALUE(name, ciphers);
121
122  return { cipherList, cipherSuites };
123}
124
125function configSecureContext(context, options = kEmptyObject, name = 'options') {
126  validateObject(options, name);
127
128  const {
129    ca,
130    cert,
131    ciphers = getDefaultCiphers(),
132    clientCertEngine,
133    crl,
134    dhparam,
135    ecdhCurve = getDefaultEcdhCurve(),
136    key,
137    passphrase,
138    pfx,
139    privateKeyIdentifier,
140    privateKeyEngine,
141    sessionIdContext,
142    sessionTimeout,
143    sigalgs,
144    ticketKeys,
145  } = options;
146
147  // Add CA before the cert to be able to load cert's issuer in C++ code.
148  // NOTE(@jasnell): ca, cert, and key are permitted to be falsy, so do not
149  // change the checks to !== undefined checks.
150  if (ca) {
151    addCACerts(context, ArrayIsArray(ca) ? ca : [ca], `${name}.ca`);
152  } else {
153    context.addRootCerts();
154  }
155
156  if (cert) {
157    setCerts(context, ArrayIsArray(cert) ? cert : [cert], `${name}.cert`);
158  }
159
160  // Set the key after the cert.
161  // `ssl_set_pkey` returns `0` when the key does not match the cert, but
162  // `ssl_set_cert` returns `1` and nullifies the key in the SSL structure
163  // which leads to the crash later on.
164  if (key) {
165    if (ArrayIsArray(key)) {
166      for (let i = 0; i < key.length; ++i) {
167        const val = key[i];
168        const pem = (
169          val?.pem !== undefined ? val.pem : val);
170        const pass = (
171          val?.passphrase !== undefined ? val.passphrase : passphrase);
172        setKey(context, pem, pass, name);
173      }
174    } else {
175      setKey(context, key, passphrase, name);
176    }
177  }
178
179  if (sigalgs !== undefined && sigalgs !== null) {
180    validateString(sigalgs, `${name}.sigalgs`);
181
182    if (sigalgs === '')
183      throw new ERR_INVALID_ARG_VALUE(`${name}.sigalgs`, sigalgs);
184
185    context.setSigalgs(sigalgs);
186  }
187
188  if (privateKeyIdentifier !== undefined && privateKeyIdentifier !== null) {
189    if (privateKeyEngine === undefined || privateKeyEngine === null) {
190      // Engine is required when privateKeyIdentifier is present
191      throw new ERR_INVALID_ARG_VALUE(`${name}.privateKeyEngine`,
192                                      privateKeyEngine);
193    }
194    if (key) {
195      // Both data key and engine key can't be set at the same time
196      throw new ERR_INVALID_ARG_VALUE(`${name}.privateKeyIdentifier`,
197                                      privateKeyIdentifier);
198    }
199
200    if (typeof privateKeyIdentifier === 'string' &&
201        typeof privateKeyEngine === 'string') {
202      if (context.setEngineKey)
203        context.setEngineKey(privateKeyIdentifier, privateKeyEngine);
204      else
205        throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
206    } else if (typeof privateKeyIdentifier !== 'string') {
207      throw new ERR_INVALID_ARG_TYPE(`${name}.privateKeyIdentifier`,
208                                     ['string', 'null', 'undefined'],
209                                     privateKeyIdentifier);
210    } else {
211      throw new ERR_INVALID_ARG_TYPE(`${name}.privateKeyEngine`,
212                                     ['string', 'null', 'undefined'],
213                                     privateKeyEngine);
214    }
215  }
216
217  if (ciphers !== undefined && ciphers !== null)
218    validateString(ciphers, `${name}.ciphers`);
219
220  // Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below,
221  // cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3
222  // cipher suites all have a standard name format beginning with TLS_, so split
223  // the ciphers and pass them to the appropriate API.
224  const {
225    cipherList,
226    cipherSuites,
227  } = processCiphers(ciphers, `${name}.ciphers`);
228
229  if (cipherSuites !== '')
230    context.setCipherSuites(cipherSuites);
231  context.setCiphers(cipherList);
232
233  if (cipherList === '' &&
234      context.getMinProto() < TLS1_3_VERSION &&
235      context.getMaxProto() > TLS1_2_VERSION) {
236    context.setMinProto(TLS1_3_VERSION);
237  }
238
239  validateString(ecdhCurve, `${name}.ecdhCurve`);
240  context.setECDHCurve(ecdhCurve);
241
242  if (dhparam !== undefined && dhparam !== null) {
243    validateKeyOrCertOption(`${name}.dhparam`, dhparam);
244    const warning = context.setDHParam(dhparam === 'auto' || dhparam);
245    if (warning)
246      process.emitWarning(warning, 'SecurityWarning');
247  }
248
249  if (crl !== undefined && crl !== null) {
250    if (ArrayIsArray(crl)) {
251      for (const val of crl) {
252        validateKeyOrCertOption(`${name}.crl`, val);
253        context.addCRL(val);
254      }
255    } else {
256      validateKeyOrCertOption(`${name}.crl`, crl);
257      context.addCRL(crl);
258    }
259  }
260
261  if (sessionIdContext !== undefined && sessionIdContext !== null) {
262    validateString(sessionIdContext, `${name}.sessionIdContext`);
263    context.setSessionIdContext(sessionIdContext);
264  }
265
266  if (pfx !== undefined && pfx !== null) {
267    if (ArrayIsArray(pfx)) {
268      ArrayPrototypeForEach(pfx, (val) => {
269        const raw = val.buf || val;
270        const pass = val.passphrase || passphrase;
271        if (pass !== undefined && pass !== null) {
272          context.loadPKCS12(toBuf(raw), toBuf(pass));
273        } else {
274          context.loadPKCS12(toBuf(raw));
275        }
276      });
277    } else if (passphrase) {
278      context.loadPKCS12(toBuf(pfx), toBuf(passphrase));
279    } else {
280      context.loadPKCS12(toBuf(pfx));
281    }
282  }
283
284  if (typeof clientCertEngine === 'string') {
285    if (typeof context.setClientCertEngine !== 'function')
286      throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
287    else
288      context.setClientCertEngine(clientCertEngine);
289  } else if (clientCertEngine !== undefined && clientCertEngine !== null) {
290    throw new ERR_INVALID_ARG_TYPE(`${name}.clientCertEngine`,
291                                   ['string', 'null', 'undefined'],
292                                   clientCertEngine);
293  }
294
295  if (ticketKeys !== undefined && ticketKeys !== null) {
296    validateBuffer(ticketKeys, `${name}.ticketKeys`);
297    if (ticketKeys.byteLength !== 48) {
298      throw new ERR_INVALID_ARG_VALUE(
299        `${name}.ticketKeys`,
300        ticketKeys.byteLength,
301        'must be exactly 48 bytes');
302    }
303    context.setTicketKeys(ticketKeys);
304  }
305
306  if (sessionTimeout !== undefined && sessionTimeout !== null) {
307    validateInt32(sessionTimeout, `${name}.sessionTimeout`);
308    context.setSessionTimeout(sessionTimeout);
309  }
310}
311
312module.exports = {
313  configSecureContext,
314};
315