eff5d313e9ffd7741ef2111d1e443fc04ff9d805
[platform/upstream/nodejs.git] / lib / tls.js
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 var net = require('net');
23 var url = require('url');
24 var util = require('util');
25
26 // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
27 // every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
28 // renegotations are seen. The settings are applied to all remote client
29 // connections.
30 exports.CLIENT_RENEG_LIMIT = 3;
31 exports.CLIENT_RENEG_WINDOW = 600;
32
33 exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024;
34
35 exports.DEFAULT_CIPHERS =
36     // TLS 1.2
37     'ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' +
38     // TLS 1.0
39     'RC4:HIGH:!MD5:!aNULL';
40
41 exports.DEFAULT_ECDH_CURVE = 'prime256v1';
42
43 exports.getCiphers = function() {
44   var names = process.binding('crypto').getSSLCiphers();
45   // Drop all-caps names in favor of their lowercase aliases,
46   var ctx = {};
47   names.forEach(function(name) {
48     if (/^[0-9A-Z\-]+$/.test(name)) name = name.toLowerCase();
49     ctx[name] = true;
50   });
51   return Object.getOwnPropertyNames(ctx).sort();
52 };
53
54 // Convert protocols array into valid OpenSSL protocols list
55 // ("\x06spdy/2\x08http/1.1\x08http/1.0")
56 exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) {
57   // If NPNProtocols is Array - translate it into buffer
58   if (util.isArray(NPNProtocols)) {
59     var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
60       return p + 1 + Buffer.byteLength(c);
61     }, 0));
62
63     NPNProtocols.reduce(function(offset, c) {
64       var clen = Buffer.byteLength(c);
65       buff[offset] = clen;
66       buff.write(c, offset + 1);
67
68       return offset + 1 + clen;
69     }, 0);
70
71     NPNProtocols = buff;
72   }
73
74   // If it's already a Buffer - store it
75   if (util.isBuffer(NPNProtocols)) {
76     out.NPNProtocols = NPNProtocols;
77   }
78 };
79
80 exports.checkServerIdentity = function checkServerIdentity(host, cert) {
81   // Create regexp to much hostnames
82   function regexpify(host, wildcards) {
83     // Add trailing dot (make hostnames uniform)
84     if (!/\.$/.test(host)) host += '.';
85
86     // The same applies to hostname with more than one wildcard,
87     // if hostname has wildcard when wildcards are not allowed,
88     // or if there are less than two dots after wildcard (i.e. *.com or *d.com)
89     //
90     // also
91     //
92     // "The client SHOULD NOT attempt to match a presented identifier in
93     // which the wildcard character comprises a label other than the
94     // left-most label (e.g., do not match bar.*.example.net)."
95     // RFC6125
96     if (!wildcards && /\*/.test(host) || /[\.\*].*\*/.test(host) ||
97         /\*/.test(host) && !/\*.*\..+\..+/.test(host)) {
98       return /$./;
99     }
100
101     // Replace wildcard chars with regexp's wildcard and
102     // escape all characters that have special meaning in regexps
103     // (i.e. '.', '[', '{', '*', and others)
104     var re = host.replace(
105         /\*([a-z0-9\\-_\.])|[\.,\-\\\^\$+?*\[\]\(\):!\|{}]/g,
106         function(all, sub) {
107           if (sub) return '[a-z0-9\\-_]*' + (sub === '-' ? '\\-' : sub);
108           return '\\' + all;
109         });
110
111     return new RegExp('^' + re + '$', 'i');
112   }
113
114   var dnsNames = [],
115       uriNames = [],
116       ips = [],
117       matchCN = true,
118       valid = false,
119       reason = 'Unknown reason';
120
121   // There're several names to perform check against:
122   // CN and altnames in certificate extension
123   // (DNS names, IP addresses, and URIs)
124   //
125   // Walk through altnames and generate lists of those names
126   if (cert.subjectaltname) {
127     cert.subjectaltname.split(/, /g).forEach(function(altname) {
128       var option = altname.match(/^(DNS|IP Address|URI):(.*)$/);
129       if (!option)
130         return;
131       if (option[1] === 'DNS') {
132         dnsNames.push(option[2]);
133       } else if (option[1] === 'IP Address') {
134         ips.push(option[2]);
135       } else if (option[1] === 'URI') {
136         var uri = url.parse(option[2]);
137         if (uri) uriNames.push(uri.hostname);
138       }
139     });
140   }
141
142   // If hostname is an IP address, it should be present in the list of IP
143   // addresses.
144   if (net.isIP(host)) {
145     valid = ips.some(function(ip) {
146       return ip === host;
147     });
148     if (!valid) {
149       reason = util.format('IP: %s is not in the cert\'s list: %s',
150                            host,
151                            ips.join(', '));
152     }
153   } else {
154     // Transform hostname to canonical form
155     if (!/\.$/.test(host)) host += '.';
156
157     // Otherwise check all DNS/URI records from certificate
158     // (with allowed wildcards)
159     dnsNames = dnsNames.map(function(name) {
160       return regexpify(name, true);
161     });
162
163     // Wildcards ain't allowed in URI names
164     uriNames = uriNames.map(function(name) {
165       return regexpify(name, false);
166     });
167
168     dnsNames = dnsNames.concat(uriNames);
169
170     if (dnsNames.length > 0) matchCN = false;
171
172     // Match against Common Name (CN) only if no supported identifiers are
173     // present.
174     //
175     // "As noted, a client MUST NOT seek a match for a reference identifier
176     //  of CN-ID if the presented identifiers include a DNS-ID, SRV-ID,
177     //  URI-ID, or any application-specific identifier types supported by the
178     //  client."
179     // RFC6125
180     if (matchCN) {
181       var commonNames = cert.subject.CN;
182       if (util.isArray(commonNames)) {
183         for (var i = 0, k = commonNames.length; i < k; ++i) {
184           dnsNames.push(regexpify(commonNames[i], true));
185         }
186       } else {
187         dnsNames.push(regexpify(commonNames, true));
188       }
189     }
190
191     valid = dnsNames.some(function(re) {
192       return re.test(host);
193     });
194
195     if (!valid) {
196       if (cert.subjectaltname) {
197         reason = util.format('Host: %s is not in the cert\'s altnames: %s',
198                              host,
199                              cert.subjectaltname);
200       } else {
201         reason = util.format('Host: %s is not cert\'s CN: %s',
202                              host,
203                              cert.subject.CN);
204       }
205     }
206   }
207
208   if (!valid) {
209     var err = new Error(
210         util.format('Hostname/IP doesn\'t match certificate\'s altnames: %j',
211                     reason));
212     err.reason = reason;
213     err.host = host;
214     err.cert = cert;
215     return err;
216   }
217 };
218
219 // Example:
220 // C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org
221 exports.parseCertString = function parseCertString(s) {
222   var out = {};
223   var parts = s.split('\n');
224   for (var i = 0, len = parts.length; i < len; i++) {
225     var sepIndex = parts[i].indexOf('=');
226     if (sepIndex > 0) {
227       var key = parts[i].slice(0, sepIndex);
228       var value = parts[i].slice(sepIndex + 1);
229       if (key in out) {
230         if (!util.isArray(out[key])) {
231           out[key] = [out[key]];
232         }
233         out[key].push(value);
234       } else {
235         out[key] = value;
236       }
237     }
238   }
239   return out;
240 };
241
242 // Public API
243 exports.createSecureContext = require('_tls_common').createSecureContext;
244 exports.SecureContext = require('_tls_common').SecureContext;
245 exports.TLSSocket = require('_tls_wrap').TLSSocket;
246 exports.Server = require('_tls_wrap').Server;
247 exports.createServer = require('_tls_wrap').createServer;
248 exports.connect = require('_tls_wrap').connect;
249
250 // Legacy API
251 exports.__defineGetter__('createSecurePair', util.deprecate(function() {
252   return require('_tls_legacy').createSecurePair;
253 }, 'createSecurePair() is deprecated, use TLSSocket instead'));