text: // Copyright Joyent, Inc. and other Node contributors.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a
  // copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to permit
  // persons to whom the Software is furnished to do so, subject to the
  // following conditions:
  //
  // The above copyright notice and this permission notice shall be included
  // in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  // USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  var net = require('net');
  var url = require('url');
  var util = require('util');
  
  // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
  // every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
  // renegotations are seen. The settings are applied to all remote client
  // connections.
  exports.CLIENT_RENEG_LIMIT = 3;
  exports.CLIENT_RENEG_WINDOW = 600;
  
  exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024;
  
  exports.DEFAULT_CIPHERS =
      'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' + // TLS 1.2
      'RC4:HIGH:!MD5:!aNULL:!EDH';                   // TLS 1.0
  
  exports.DEFAULT_ECDH_CURVE = 'prime256v1';
  
  exports.getCiphers = function() {
    var names = process.binding('crypto').getSSLCiphers();
    // Drop all-caps names in favor of their lowercase aliases,
    var ctx = {};
    names.forEach(function(name) {
      if (/^[0-9A-Z\-]+$/.test(name)) name = name.toLowerCase();
      ctx[name] = true;
    });
    return Object.getOwnPropertyNames(ctx).sort();
  };
  
  // Convert protocols array into valid OpenSSL protocols list
  // ("\x06spdy/2\x08http/1.1\x08http/1.0")
  exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) {
    // If NPNProtocols is Array - translate it into buffer
    if (util.isArray(NPNProtocols)) {
      var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
        return p + 1 + Buffer.byteLength(c);
      }, 0));
  
      NPNProtocols.reduce(function(offset, c) {
        var clen = Buffer.byteLength(c);
        buff[offset] = clen;
        buff.write(c, offset + 1);
  
        return offset + 1 + clen;
      }, 0);
  
      NPNProtocols = buff;
    }
  
    // If it's already a Buffer - store it
    if (util.isBuffer(NPNProtocols)) {
      out.NPNProtocols = NPNProtocols;
    }
  };
  
  exports.checkServerIdentity = function checkServerIdentity(host, cert) {
    // Create regexp to much hostnames
    function regexpify(host, wildcards) {
      // Add trailing dot (make hostnames uniform)
      if (!/\.$/.test(host)) host += '.';
  
      // The same applies to hostname with more than one wildcard,
      // if hostname has wildcard when wildcards are not allowed,
      // or if there are less than two dots after wildcard (i.e. *.com or *d.com)
      //
      // also
      //
      // "The client SHOULD NOT attempt to match a presented identifier in
      // which the wildcard character comprises a label other than the
      // left-most label (e.g., do not match bar.*.example.net)."
      // RFC6125
      if (!wildcards && /\*/.test(host) || /[\.\*].*\*/.test(host) ||
          /\*/.test(host) && !/\*.*\..+\..+/.test(host)) {
        return /$./;
      }
  
      // Replace wildcard chars with regexp's wildcard and
      // escape all characters that have special meaning in regexps
      // (i.e. '.', '[', '{', '*', and others)
      var re = host.replace(
          /\*([a-z0-9\\-_\.])|[\.,\-\\\^\$+?*\[\]\(\):!\|{}]/g,
          function(all, sub) {
            if (sub) return '[a-z0-9\\-_]*' + (sub === '-' ? '\\-' : sub);
            return '\\' + all;
          });
  
      return new RegExp('^' + re + '$', 'i');
    }
  
    var dnsNames = [],
        uriNames = [],
        ips = [],
        matchCN = true,
        valid = false,
        reason = 'Unknown reason';
  
    // There're several names to perform check against:
    // CN and altnames in certificate extension
    // (DNS names, IP addresses, and URIs)
    //
    // Walk through altnames and generate lists of those names
    if (cert.subjectaltname) {
      cert.subjectaltname.split(/, /g).forEach(function(altname) {
        var option = altname.match(/^(DNS|IP Address|URI):(.*)$/);
        if (!option)
          return;
        if (option[1] === 'DNS') {
          dnsNames.push(option[2]);
        } else if (option[1] === 'IP Address') {
          ips.push(option[2]);
        } else if (option[1] === 'URI') {
          var uri = url.parse(option[2]);
          if (uri) uriNames.push(uri.hostname);
        }
      });
    }
  
    // If hostname is an IP address, it should be present in the list of IP
    // addresses.
    if (net.isIP(host)) {
      valid = ips.some(function(ip) {
        return ip === host;
      });
      if (!valid) {
        reason = util.format('IP: %s is not in the cert\'s list: %s',
                             host,
                             ips.join(', '));
      }
    } else {
      // Transform hostname to canonical form
      if (!/\.$/.test(host)) host += '.';
  
      // Otherwise check all DNS/URI records from certificate
      // (with allowed wildcards)
      dnsNames = dnsNames.map(function(name) {
        return regexpify(name, true);
      });
  
      // Wildcards ain't allowed in URI names
      uriNames = uriNames.map(function(name) {
        return regexpify(name, false);
      });
  
      dnsNames = dnsNames.concat(uriNames);
  
      if (dnsNames.length > 0) matchCN = false;
  
      // Match against Common Name (CN) only if no supported identifiers are
      // present.
      //
      // "As noted, a client MUST NOT seek a match for a reference identifier
      //  of CN-ID if the presented identifiers include a DNS-ID, SRV-ID,
      //  URI-ID, or any application-specific identifier types supported by the
      //  client."
      // RFC6125
      if (matchCN) {
        var commonNames = cert.subject.CN;
        if (util.isArray(commonNames)) {
          for (var i = 0, k = commonNames.length; i < k; ++i) {
            dnsNames.push(regexpify(commonNames[i], true));
          }
        } else {
          dnsNames.push(regexpify(commonNames, true));
        }
      }
  
      valid = dnsNames.some(function(re) {
        return re.test(host);
      });
  
      if (!valid) {
        if (cert.subjectaltname) {
          reason = util.format('Host: %s is not in the cert\'s altnames: %s',
                               host,
                               cert.subjectaltname);
        } else {
          reason = util.format('Host: %s is not cert\'s CN: %s',
                               host,
                               cert.subject.CN);
        }
      }
    }
  
    if (!valid) {
      var err = new Error(
          util.format('Hostname/IP doesn\'t match certificate\'s altnames: %j',
                      reason));
      err.reason = reason;
      err.host = host;
      err.cert = cert;
      return err;
    }
  };
  
  // Example:
  // C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org
  exports.parseCertString = function parseCertString(s) {
    var out = {};
    var parts = s.split('\n');
    for (var i = 0, len = parts.length; i < len; i++) {
      var sepIndex = parts[i].indexOf('=');
      if (sepIndex > 0) {
        var key = parts[i].slice(0, sepIndex);
        var value = parts[i].slice(sepIndex + 1);
        if (key in out) {
          if (!util.isArray(out[key])) {
            out[key] = [out[key]];
          }
          out[key].push(value);
        } else {
          out[key] = value;
        }
      }
    }
    return out;
  };
  
  // Public API
  exports.createSecureContext = require('_tls_common').createSecureContext;
  exports.SecureContext = require('_tls_common').SecureContext;
  exports.TLSSocket = require('_tls_wrap').TLSSocket;
  exports.Server = require('_tls_wrap').Server;
  exports.createServer = require('_tls_wrap').createServer;
  exports.connect = require('_tls_wrap').connect;
  
  // Legacy API
  exports.__defineGetter__('createSecurePair', util.deprecate(function() {
    return require('_tls_legacy').createSecurePair;
  }, 'createSecurePair() is deprecated, use TLSSocket instead'));
  