diff options
author | Fedor Indutny <fedor.indutny@gmail.com> | 2012-07-11 23:54:20 +0400 |
---|---|---|
committer | Fedor Indutny <fedor.indutny@gmail.com> | 2012-07-20 00:53:36 +0400 |
commit | 8ba189b8d32e3c6489e142101d3e37dcbc551179 (patch) | |
tree | 45d3e19ccc3a8272170f53b7da622f7485966a4e /lib/tls.js | |
parent | 2b3ba3f538e9ff22cdb4172799ee38d06818165f (diff) | |
download | nodejs-8ba189b8d32e3c6489e142101d3e37dcbc551179.tar.gz nodejs-8ba189b8d32e3c6489e142101d3e37dcbc551179.tar.bz2 nodejs-8ba189b8d32e3c6489e142101d3e37dcbc551179.zip |
tls: veryify server's identity
Diffstat (limited to 'lib/tls.js')
-rw-r--r-- | lib/tls.js | 110 |
1 files changed, 107 insertions, 3 deletions
diff --git a/lib/tls.js b/lib/tls.js index 5b8f179ba..be24283f4 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -22,6 +22,7 @@ var crypto = require('crypto'); var util = require('util'); var net = require('net'); +var url = require('url'); var events = require('events'); var Stream = require('stream'); var END_OF_FILE = 42; @@ -78,6 +79,98 @@ function convertNPNProtocols(NPNProtocols, out) { } +function checkServerIdentity(host, cert) { + // Create regexp to much hostnames + function regexpify(host, wildcards) { + // Add trailing dot (make hostnames uniform) + if (!/\.$/.test(host)) host += '.'; + + // Host names with less than one dots are considered too broad, + // and should not be allowed + if (!/^.+\..+$/.test(host)) return /$./; + + // 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) + if (/\*.*\*/.test(host) || !wildcards && /\*/.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 = [], + valid = false; + + // 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) { + if (/^DNS:/.test(altname)) { + dnsNames.push(altname.slice(4)); + } else if (/^IP Address:/.test(altname)) { + ips.push(altname.slice(11)); + } else if (/^URI:/.test(altname)) { + var uri = url.parse(altname.slice(4)); + 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; + }); + } 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); + + // And only after check if hostname matches CN + // (because CN is deprecated, but should be used for compatiblity anyway) + dnsNames.push(regexpify(cert.subject.CN, false)); + + valid = dnsNames.some(function(re) { + return re.test(host); + }); + } + + return valid; +}; +exports.checkServerIdentity = checkServerIdentity; + + function SlabBuffer() { this.create(); } @@ -1149,11 +1242,12 @@ exports.connect = function(/* [port, host], options, cb */) { var sslcontext = crypto.createCredentials(options); convertNPNProtocols(options.NPNProtocols, this); - var pair = new SecurePair(sslcontext, false, true, + var hostname = options.servername || options.host, + pair = new SecurePair(sslcontext, false, true, options.rejectUnauthorized === true ? true : false, { NPNProtocols: this.NPNProtocols, - servername: options.servername || options.host + servername: hostname }); if (options.session) { @@ -1178,9 +1272,19 @@ exports.connect = function(/* [port, host], options, cb */) { cleartext.npnProtocol = pair.npnProtocol; + // Verify that server's identity matches it's certificate's names + if (!verifyError) { + var validCert = checkServerIdentity(hostname, + pair.cleartext.getPeerCertificate()); + if (!validCert) { + verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' + + 'altnames'); + } + } + if (verifyError) { cleartext.authorized = false; - cleartext.authorizationError = verifyError; + cleartext.authorizationError = verifyError.message; if (pair._rejectUnauthorized) { cleartext.emit('error', verifyError); |