• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23
24const {
25  Array,
26  ArrayIsArray,
27  ObjectDefineProperty,
28  ObjectFreeze,
29} = primordials;
30
31const {
32  ERR_TLS_CERT_ALTNAME_INVALID,
33  ERR_OUT_OF_RANGE
34} = require('internal/errors').codes;
35const internalUtil = require('internal/util');
36const internalTLS = require('internal/tls');
37internalUtil.assertCrypto();
38const { isArrayBufferView } = require('internal/util/types');
39
40const net = require('net');
41const { getOptionValue } = require('internal/options');
42const url = require('url');
43const { getRootCertificates, getSSLCiphers } = internalBinding('crypto');
44const { Buffer } = require('buffer');
45const EventEmitter = require('events');
46const { URL } = require('internal/url');
47const DuplexPair = require('internal/streams/duplexpair');
48const { canonicalizeIP } = internalBinding('cares_wrap');
49const _tls_common = require('_tls_common');
50const _tls_wrap = require('_tls_wrap');
51
52// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
53// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
54// renegotiations are seen. The settings are applied to all remote client
55// connections.
56exports.CLIENT_RENEG_LIMIT = 3;
57exports.CLIENT_RENEG_WINDOW = 600;
58
59exports.DEFAULT_CIPHERS = getOptionValue('--tls-cipher-list');
60
61exports.DEFAULT_ECDH_CURVE = 'auto';
62
63if (getOptionValue('--tls-min-v1.0'))
64  exports.DEFAULT_MIN_VERSION = 'TLSv1';
65else if (getOptionValue('--tls-min-v1.1'))
66  exports.DEFAULT_MIN_VERSION = 'TLSv1.1';
67else if (getOptionValue('--tls-min-v1.2'))
68  exports.DEFAULT_MIN_VERSION = 'TLSv1.2';
69else if (getOptionValue('--tls-min-v1.3'))
70  exports.DEFAULT_MIN_VERSION = 'TLSv1.3';
71else
72  exports.DEFAULT_MIN_VERSION = 'TLSv1.2';
73
74if (getOptionValue('--tls-max-v1.3'))
75  exports.DEFAULT_MAX_VERSION = 'TLSv1.3';
76else if (getOptionValue('--tls-max-v1.2'))
77  exports.DEFAULT_MAX_VERSION = 'TLSv1.2';
78else
79  exports.DEFAULT_MAX_VERSION = 'TLSv1.3'; // Will depend on node version.
80
81
82exports.getCiphers = internalUtil.cachedResult(
83  () => internalUtil.filterDuplicateStrings(getSSLCiphers(), true)
84);
85
86let rootCertificates;
87
88function cacheRootCertificates() {
89  rootCertificates = ObjectFreeze(getRootCertificates());
90}
91
92ObjectDefineProperty(exports, 'rootCertificates', {
93  configurable: false,
94  enumerable: true,
95  get: () => {
96    // Out-of-line caching to promote inlining the getter.
97    if (!rootCertificates) cacheRootCertificates();
98    return rootCertificates;
99  },
100});
101
102// Convert protocols array into valid OpenSSL protocols list
103// ("\x06spdy/2\x08http/1.1\x08http/1.0")
104function convertProtocols(protocols) {
105  const lens = new Array(protocols.length);
106  const buff = Buffer.allocUnsafe(protocols.reduce((p, c, i) => {
107    const len = Buffer.byteLength(c);
108    if (len > 255) {
109      throw new ERR_OUT_OF_RANGE('The byte length of the protocol at index ' +
110        `${i} exceeds the maximum length.`, '<= 255', len, true);
111    }
112    lens[i] = len;
113    return p + 1 + len;
114  }, 0));
115
116  let offset = 0;
117  for (let i = 0, c = protocols.length; i < c; i++) {
118    buff[offset++] = lens[i];
119    buff.write(protocols[i], offset);
120    offset += lens[i];
121  }
122
123  return buff;
124}
125
126exports.convertALPNProtocols = function convertALPNProtocols(protocols, out) {
127  // If protocols is Array - translate it into buffer
128  if (ArrayIsArray(protocols)) {
129    out.ALPNProtocols = convertProtocols(protocols);
130  } else if (isArrayBufferView(protocols)) {
131    // Copy new buffer not to be modified by user.
132    out.ALPNProtocols = Buffer.from(protocols);
133  }
134};
135
136function unfqdn(host) {
137  return host.replace(/[.]$/, '');
138}
139
140function splitHost(host) {
141  // String#toLowerCase() is locale-sensitive so we use
142  // a conservative version that only lowercases A-Z.
143  const replacer = (c) => String.fromCharCode(32 + c.charCodeAt(0));
144  return unfqdn(host).replace(/[A-Z]/g, replacer).split('.');
145}
146
147function check(hostParts, pattern, wildcards) {
148  // Empty strings, null, undefined, etc. never match.
149  if (!pattern)
150    return false;
151
152  const patternParts = splitHost(pattern);
153
154  if (hostParts.length !== patternParts.length)
155    return false;
156
157  // Pattern has empty components, e.g. "bad..example.com".
158  if (patternParts.includes(''))
159    return false;
160
161  // RFC 6125 allows IDNA U-labels (Unicode) in names but we have no
162  // good way to detect their encoding or normalize them so we simply
163  // reject them.  Control characters and blanks are rejected as well
164  // because nothing good can come from accepting them.
165  const isBad = (s) => /[^\u0021-\u007F]/u.test(s);
166  if (patternParts.some(isBad))
167    return false;
168
169  // Check host parts from right to left first.
170  for (let i = hostParts.length - 1; i > 0; i -= 1) {
171    if (hostParts[i] !== patternParts[i])
172      return false;
173  }
174
175  const hostSubdomain = hostParts[0];
176  const patternSubdomain = patternParts[0];
177  const patternSubdomainParts = patternSubdomain.split('*');
178
179  // Short-circuit when the subdomain does not contain a wildcard.
180  // RFC 6125 does not allow wildcard substitution for components
181  // containing IDNA A-labels (Punycode) so match those verbatim.
182  if (patternSubdomainParts.length === 1 || patternSubdomain.includes('xn--'))
183    return hostSubdomain === patternSubdomain;
184
185  if (!wildcards)
186    return false;
187
188  // More than one wildcard is always wrong.
189  if (patternSubdomainParts.length > 2)
190    return false;
191
192  // *.tld wildcards are not allowed.
193  if (patternParts.length <= 2)
194    return false;
195
196  const [prefix, suffix] = patternSubdomainParts;
197
198  if (prefix.length + suffix.length > hostSubdomain.length)
199    return false;
200
201  if (!hostSubdomain.startsWith(prefix))
202    return false;
203
204  if (!hostSubdomain.endsWith(suffix))
205    return false;
206
207  return true;
208}
209
210let urlWarningEmitted = false;
211exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
212  const subject = cert.subject;
213  const altNames = cert.subjectaltname;
214  const dnsNames = [];
215  const uriNames = [];
216  const ips = [];
217
218  hostname = '' + hostname;
219
220  if (altNames) {
221    for (const name of altNames.split(', ')) {
222      if (name.startsWith('DNS:')) {
223        dnsNames.push(name.slice(4));
224      } else if (name.startsWith('URI:')) {
225        let uri;
226        try {
227          uri = new URL(name.slice(4));
228        } catch {
229          uri = url.parse(name.slice(4));
230          if (!urlWarningEmitted && !process.noDeprecation) {
231            urlWarningEmitted = true;
232            process.emitWarning(
233              `The URI ${name.slice(4)} found in cert.subjectaltname ` +
234              'is not a valid URI, and is supported in the tls module ' +
235              'solely for compatibility.',
236              'DeprecationWarning', 'DEP0109');
237          }
238        }
239
240        uriNames.push(uri.hostname);  // TODO(bnoordhuis) Also use scheme.
241      } else if (name.startsWith('IP Address:')) {
242        ips.push(canonicalizeIP(name.slice(11)));
243      }
244    }
245  }
246
247  let valid = false;
248  let reason = 'Unknown reason';
249
250  const hasAltNames =
251    dnsNames.length > 0 || ips.length > 0 || uriNames.length > 0;
252
253  hostname = unfqdn(hostname);  // Remove trailing dot for error messages.
254
255  if (net.isIP(hostname)) {
256    valid = ips.includes(canonicalizeIP(hostname));
257    if (!valid)
258      reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`;
259    // TODO(bnoordhuis) Also check URI SANs that are IP addresses.
260  } else if (hasAltNames || subject) {
261    const hostParts = splitHost(hostname);
262    const wildcard = (pattern) => check(hostParts, pattern, true);
263
264    if (hasAltNames) {
265      const noWildcard = (pattern) => check(hostParts, pattern, false);
266      valid = dnsNames.some(wildcard) || uriNames.some(noWildcard);
267      if (!valid)
268        reason =
269          `Host: ${hostname}. is not in the cert's altnames: ${altNames}`;
270    } else {
271      // Match against Common Name only if no supported identifiers exist.
272      const cn = subject.CN;
273
274      if (ArrayIsArray(cn))
275        valid = cn.some(wildcard);
276      else if (cn)
277        valid = wildcard(cn);
278
279      if (!valid)
280        reason = `Host: ${hostname}. is not cert's CN: ${cn}`;
281    }
282  } else {
283    reason = 'Cert is empty';
284  }
285
286  if (!valid) {
287    return new ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert);
288  }
289};
290
291
292class SecurePair extends EventEmitter {
293  constructor(secureContext = exports.createSecureContext(),
294              isServer = false,
295              requestCert = !isServer,
296              rejectUnauthorized = false,
297              options = {}) {
298    super();
299    const { socket1, socket2 } = new DuplexPair();
300
301    this.server = options.server;
302    this.credentials = secureContext;
303
304    this.encrypted = socket1;
305    this.cleartext = new exports.TLSSocket(socket2, {
306      secureContext,
307      isServer,
308      requestCert,
309      rejectUnauthorized,
310      ...options
311    });
312    this.cleartext.once('secure', () => this.emit('secure'));
313  }
314
315  destroy() {
316    this.cleartext.destroy();
317    this.encrypted.destroy();
318  }
319}
320
321
322exports.parseCertString = internalUtil.deprecate(
323  internalTLS.parseCertString,
324  'tls.parseCertString() is deprecated. ' +
325  'Please use querystring.parse() instead.',
326  'DEP0076');
327
328exports.createSecureContext = _tls_common.createSecureContext;
329exports.SecureContext = _tls_common.SecureContext;
330exports.TLSSocket = _tls_wrap.TLSSocket;
331exports.Server = _tls_wrap.Server;
332exports.createServer = _tls_wrap.createServer;
333exports.connect = _tls_wrap.connect;
334
335exports.createSecurePair = internalUtil.deprecate(
336  function createSecurePair(...args) {
337    return new SecurePair(...args);
338  },
339  'tls.createSecurePair() is deprecated. Please use ' +
340  'tls.TLSSocket instead.', 'DEP0064');
341