• 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  ArrayPrototypeForEach,
28  ArrayPrototypeIncludes,
29  ArrayPrototypeJoin,
30  ArrayPrototypePush,
31  ArrayPrototypeReduce,
32  ArrayPrototypeSome,
33  JSONParse,
34  ObjectDefineProperty,
35  ObjectFreeze,
36  RegExpPrototypeExec,
37  RegExpPrototypeSymbolReplace,
38  StringFromCharCode,
39  StringPrototypeCharCodeAt,
40  StringPrototypeEndsWith,
41  StringPrototypeIncludes,
42  StringPrototypeIndexOf,
43  StringPrototypeSlice,
44  StringPrototypeSplit,
45  StringPrototypeStartsWith,
46  StringPrototypeSubstring,
47} = primordials;
48
49const {
50  ERR_TLS_CERT_ALTNAME_FORMAT,
51  ERR_TLS_CERT_ALTNAME_INVALID,
52  ERR_OUT_OF_RANGE,
53} = require('internal/errors').codes;
54const internalUtil = require('internal/util');
55internalUtil.assertCrypto();
56const {
57  isArrayBufferView,
58  isUint8Array,
59} = require('internal/util/types');
60
61const net = require('net');
62const { getOptionValue } = require('internal/options');
63const { getRootCertificates, getSSLCiphers } = internalBinding('crypto');
64const { Buffer } = require('buffer');
65const { canonicalizeIP } = internalBinding('cares_wrap');
66const _tls_common = require('_tls_common');
67const _tls_wrap = require('_tls_wrap');
68const { createSecurePair } = require('internal/tls/secure-pair');
69
70// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
71// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
72// renegotiations are seen. The settings are applied to all remote client
73// connections.
74exports.CLIENT_RENEG_LIMIT = 3;
75exports.CLIENT_RENEG_WINDOW = 600;
76
77exports.DEFAULT_CIPHERS = getOptionValue('--tls-cipher-list');
78
79exports.DEFAULT_ECDH_CURVE = 'auto';
80
81if (getOptionValue('--tls-min-v1.0'))
82  exports.DEFAULT_MIN_VERSION = 'TLSv1';
83else if (getOptionValue('--tls-min-v1.1'))
84  exports.DEFAULT_MIN_VERSION = 'TLSv1.1';
85else if (getOptionValue('--tls-min-v1.2'))
86  exports.DEFAULT_MIN_VERSION = 'TLSv1.2';
87else if (getOptionValue('--tls-min-v1.3'))
88  exports.DEFAULT_MIN_VERSION = 'TLSv1.3';
89else
90  exports.DEFAULT_MIN_VERSION = 'TLSv1.2';
91
92if (getOptionValue('--tls-max-v1.3'))
93  exports.DEFAULT_MAX_VERSION = 'TLSv1.3';
94else if (getOptionValue('--tls-max-v1.2'))
95  exports.DEFAULT_MAX_VERSION = 'TLSv1.2';
96else
97  exports.DEFAULT_MAX_VERSION = 'TLSv1.3'; // Will depend on node version.
98
99
100exports.getCiphers = internalUtil.cachedResult(
101  () => internalUtil.filterDuplicateStrings(getSSLCiphers(), true),
102);
103
104let rootCertificates;
105
106function cacheRootCertificates() {
107  rootCertificates = ObjectFreeze(getRootCertificates());
108}
109
110ObjectDefineProperty(exports, 'rootCertificates', {
111  __proto__: null,
112  configurable: false,
113  enumerable: true,
114  get: () => {
115    // Out-of-line caching to promote inlining the getter.
116    if (!rootCertificates) cacheRootCertificates();
117    return rootCertificates;
118  },
119});
120
121// Convert protocols array into valid OpenSSL protocols list
122// ("\x06spdy/2\x08http/1.1\x08http/1.0")
123function convertProtocols(protocols) {
124  const lens = new Array(protocols.length);
125  const buff = Buffer.allocUnsafe(ArrayPrototypeReduce(protocols, (p, c, i) => {
126    const len = Buffer.byteLength(c);
127    if (len > 255) {
128      throw new ERR_OUT_OF_RANGE('The byte length of the protocol at index ' +
129        `${i} exceeds the maximum length.`, '<= 255', len, true);
130    }
131    lens[i] = len;
132    return p + 1 + len;
133  }, 0));
134
135  let offset = 0;
136  for (let i = 0, c = protocols.length; i < c; i++) {
137    buff[offset++] = lens[i];
138    buff.write(protocols[i], offset);
139    offset += lens[i];
140  }
141
142  return buff;
143}
144
145exports.convertALPNProtocols = function convertALPNProtocols(protocols, out) {
146  // If protocols is Array - translate it into buffer
147  if (ArrayIsArray(protocols)) {
148    out.ALPNProtocols = convertProtocols(protocols);
149  } else if (isUint8Array(protocols)) {
150    // Copy new buffer not to be modified by user.
151    out.ALPNProtocols = Buffer.from(protocols);
152  } else if (isArrayBufferView(protocols)) {
153    out.ALPNProtocols = Buffer.from(protocols.buffer.slice(
154      protocols.byteOffset,
155      protocols.byteOffset + protocols.byteLength,
156    ));
157  }
158};
159
160function unfqdn(host) {
161  return RegExpPrototypeSymbolReplace(/[.]$/, host, '');
162}
163
164// String#toLowerCase() is locale-sensitive so we use
165// a conservative version that only lowercases A-Z.
166function toLowerCase(c) {
167  return StringFromCharCode(32 + StringPrototypeCharCodeAt(c, 0));
168}
169
170function splitHost(host) {
171  return StringPrototypeSplit(
172    RegExpPrototypeSymbolReplace(/[A-Z]/g, unfqdn(host), toLowerCase),
173    '.',
174  );
175}
176
177function check(hostParts, pattern, wildcards) {
178  // Empty strings, null, undefined, etc. never match.
179  if (!pattern)
180    return false;
181
182  const patternParts = splitHost(pattern);
183
184  if (hostParts.length !== patternParts.length)
185    return false;
186
187  // Pattern has empty components, e.g. "bad..example.com".
188  if (ArrayPrototypeIncludes(patternParts, ''))
189    return false;
190
191  // RFC 6125 allows IDNA U-labels (Unicode) in names but we have no
192  // good way to detect their encoding or normalize them so we simply
193  // reject them.  Control characters and blanks are rejected as well
194  // because nothing good can come from accepting them.
195  const isBad = (s) => RegExpPrototypeExec(/[^\u0021-\u007F]/u, s) !== null;
196  if (ArrayPrototypeSome(patternParts, isBad))
197    return false;
198
199  // Check host parts from right to left first.
200  for (let i = hostParts.length - 1; i > 0; i -= 1) {
201    if (hostParts[i] !== patternParts[i])
202      return false;
203  }
204
205  const hostSubdomain = hostParts[0];
206  const patternSubdomain = patternParts[0];
207  const patternSubdomainParts = StringPrototypeSplit(patternSubdomain, '*');
208
209  // Short-circuit when the subdomain does not contain a wildcard.
210  // RFC 6125 does not allow wildcard substitution for components
211  // containing IDNA A-labels (Punycode) so match those verbatim.
212  if (patternSubdomainParts.length === 1 ||
213      StringPrototypeIncludes(patternSubdomain, 'xn--'))
214    return hostSubdomain === patternSubdomain;
215
216  if (!wildcards)
217    return false;
218
219  // More than one wildcard is always wrong.
220  if (patternSubdomainParts.length > 2)
221    return false;
222
223  // *.tld wildcards are not allowed.
224  if (patternParts.length <= 2)
225    return false;
226
227  const { 0: prefix, 1: suffix } = patternSubdomainParts;
228
229  if (prefix.length + suffix.length > hostSubdomain.length)
230    return false;
231
232  if (!StringPrototypeStartsWith(hostSubdomain, prefix))
233    return false;
234
235  if (!StringPrototypeEndsWith(hostSubdomain, suffix))
236    return false;
237
238  return true;
239}
240
241// This pattern is used to determine the length of escaped sequences within
242// the subject alt names string. It allows any valid JSON string literal.
243// This MUST match the JSON specification (ECMA-404 / RFC8259) exactly.
244const jsonStringPattern =
245  // eslint-disable-next-line no-control-regex
246  /^"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/;
247
248function splitEscapedAltNames(altNames) {
249  const result = [];
250  let currentToken = '';
251  let offset = 0;
252  while (offset !== altNames.length) {
253    const nextSep = StringPrototypeIndexOf(altNames, ', ', offset);
254    const nextQuote = StringPrototypeIndexOf(altNames, '"', offset);
255    if (nextQuote !== -1 && (nextSep === -1 || nextQuote < nextSep)) {
256      // There is a quote character and there is no separator before the quote.
257      currentToken += StringPrototypeSubstring(altNames, offset, nextQuote);
258      const match = RegExpPrototypeExec(
259        jsonStringPattern, StringPrototypeSubstring(altNames, nextQuote));
260      if (!match) {
261        throw new ERR_TLS_CERT_ALTNAME_FORMAT();
262      }
263      currentToken += JSONParse(match[0]);
264      offset = nextQuote + match[0].length;
265    } else if (nextSep !== -1) {
266      // There is a separator and no quote before it.
267      currentToken += StringPrototypeSubstring(altNames, offset, nextSep);
268      ArrayPrototypePush(result, currentToken);
269      currentToken = '';
270      offset = nextSep + 2;
271    } else {
272      currentToken += StringPrototypeSubstring(altNames, offset);
273      offset = altNames.length;
274    }
275  }
276  ArrayPrototypePush(result, currentToken);
277  return result;
278}
279
280exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
281  const subject = cert.subject;
282  const altNames = cert.subjectaltname;
283  const dnsNames = [];
284  const ips = [];
285
286  hostname = '' + hostname;
287
288  if (altNames) {
289    const splitAltNames = StringPrototypeIncludes(altNames, '"') ?
290      splitEscapedAltNames(altNames) :
291      StringPrototypeSplit(altNames, ', ');
292    ArrayPrototypeForEach(splitAltNames, (name) => {
293      if (StringPrototypeStartsWith(name, 'DNS:')) {
294        ArrayPrototypePush(dnsNames, StringPrototypeSlice(name, 4));
295      } else if (StringPrototypeStartsWith(name, 'IP Address:')) {
296        ArrayPrototypePush(ips, canonicalizeIP(StringPrototypeSlice(name, 11)));
297      }
298    });
299  }
300
301  let valid = false;
302  let reason = 'Unknown reason';
303
304  hostname = unfqdn(hostname);  // Remove trailing dot for error messages.
305
306  if (net.isIP(hostname)) {
307    valid = ArrayPrototypeIncludes(ips, canonicalizeIP(hostname));
308    if (!valid)
309      reason = `IP: ${hostname} is not in the cert's list: ` +
310               ArrayPrototypeJoin(ips, ', ');
311  } else if (dnsNames.length > 0 || subject?.CN) {
312    const hostParts = splitHost(hostname);
313    const wildcard = (pattern) => check(hostParts, pattern, true);
314
315    if (dnsNames.length > 0) {
316      valid = ArrayPrototypeSome(dnsNames, wildcard);
317      if (!valid)
318        reason =
319          `Host: ${hostname}. is not in the cert's altnames: ${altNames}`;
320    } else {
321      // Match against Common Name only if no supported identifiers exist.
322      const cn = subject.CN;
323
324      if (ArrayIsArray(cn))
325        valid = ArrayPrototypeSome(cn, wildcard);
326      else if (cn)
327        valid = wildcard(cn);
328
329      if (!valid)
330        reason = `Host: ${hostname}. is not cert's CN: ${cn}`;
331    }
332  } else {
333    reason = 'Cert does not contain a DNS name';
334  }
335
336  if (!valid) {
337    return new ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert);
338  }
339};
340
341exports.createSecureContext = _tls_common.createSecureContext;
342exports.SecureContext = _tls_common.SecureContext;
343exports.TLSSocket = _tls_wrap.TLSSocket;
344exports.Server = _tls_wrap.Server;
345exports.createServer = _tls_wrap.createServer;
346exports.connect = _tls_wrap.connect;
347
348exports.createSecurePair = internalUtil.deprecate(
349  createSecurePair,
350  'tls.createSecurePair() is deprecated. Please use ' +
351  'tls.TLSSocket instead.', 'DEP0064');
352