node/lib/tls.js

241 lines
7.2 KiB
JavaScript
Raw Normal View History

'use strict';
const net = require('net');
const url = require('url');
const binding = process.binding('crypto');
const Buffer = require('buffer').Buffer;
const constants = require('constants');
// 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.
2012-02-18 23:01:35 +00:00
exports.CLIENT_RENEG_LIMIT = 3;
exports.CLIENT_RENEG_WINDOW = 600;
exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024;
exports.DEFAULT_CIPHERS = constants.defaultCipherList;
exports.DEFAULT_ECDH_CURVE = 'prime256v1';
exports.getCiphers = function() {
const names = binding.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();
};
2011-04-14 03:53:39 +00:00
// Convert protocols array into valid OpenSSL protocols list
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
function convertProtocols(protocols) {
var buff = new Buffer(protocols.reduce(function(p, c) {
return p + 1 + Buffer.byteLength(c);
}, 0));
2011-04-14 03:53:39 +00:00
protocols.reduce(function(offset, c) {
var clen = Buffer.byteLength(c);
buff[offset] = clen;
buff.write(c, offset + 1);
2011-04-14 03:53:39 +00:00
return offset + 1 + clen;
}, 0);
2011-04-14 03:53:39 +00:00
return buff;
}
exports.convertNPNProtocols = function(protocols, out) {
// If protocols is Array - translate it into buffer
if (Array.isArray(protocols)) {
protocols = convertProtocols(protocols);
}
// If it's already a Buffer - store it
if (protocols instanceof Buffer) {
out.NPNProtocols = protocols;
2011-04-14 03:53:39 +00:00
}
};
2011-04-14 03:53:39 +00:00
exports.convertALPNProtocols = function(protocols, out) {
// If protocols is Array - translate it into buffer
if (Array.isArray(protocols)) {
protocols = convertProtocols(protocols);
}
2011-04-14 03:53:39 +00:00
// If it's already a Buffer - store it
if (protocols instanceof Buffer) {
// copy new buffer not to be modified by user
out.ALPNProtocols = new Buffer(protocols);
2011-04-14 03:53:39 +00:00
}
};
2012-07-05 19:50:21 +00:00
exports.checkServerIdentity = function checkServerIdentity(host, cert) {
2012-07-11 19:54:20 +00:00
// 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) ||
2012-07-11 19:54:20 +00:00
/\*/.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(
2012-07-20 18:07:16 +00:00
/\*([a-z0-9\\-_\.])|[\.,\-\\\^\$+?*\[\]\(\):!\|{}]/g,
function(all, sub) {
if (sub) return '[a-z0-9\\-_]*' + (sub === '-' ? '\\-' : sub);
return '\\' + all;
});
2012-07-11 19:54:20 +00:00
return new RegExp('^' + re + '$', 'i');
}
var dnsNames = [];
var uriNames = [];
const ips = [];
var matchCN = true;
var valid = false;
var reason = 'Unknown reason';
2012-07-11 19:54:20 +00:00
// 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]);
2012-07-11 19:54:20 +00:00
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 = `IP: ${host} is not in the cert's list: ${ips.join(', ')}`;
}
} else if (cert.subject) {
2012-07-11 19:54:20 +00:00
// 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 (Array.isArray(commonNames)) {
for (var i = 0, k = commonNames.length; i < k; ++i) {
dnsNames.push(regexpify(commonNames[i], true));
}
} else {
dnsNames.push(regexpify(commonNames, true));
}
}
2012-07-11 19:54:20 +00:00
valid = dnsNames.some(function(re) {
return re.test(host);
});
if (!valid) {
if (cert.subjectaltname) {
reason =
`Host: ${host} is not in the cert's altnames: ` +
`${cert.subjectaltname}`;
} else {
reason = `Host: ${host} is not cert's CN: ${cert.subject.CN}`;
}
}
} else {
reason = 'Cert is empty';
2012-07-11 19:54:20 +00:00
}
if (!valid) {
var err = new Error(
`Hostname/IP doesn't match certificate's altnames: "${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 (!Array.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;
2013-07-03 07:46:01 +00:00
exports.TLSSocket = require('_tls_wrap').TLSSocket;
exports.Server = require('_tls_wrap').Server;
exports.createServer = require('_tls_wrap').createServer;
exports.connect = require('_tls_wrap').connect;
exports.createSecurePair = require('_tls_legacy').createSecurePair;