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