The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.
Please select the desired protocol below to get the URL.
This URL has Read-Only access.
main_repo / lib / tls.js @ 595b5974
History | View | Annotate | Download (38.7 KB)
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 crypto = require('crypto'); |
23 |
var util = require('util'); |
24 |
var net = require('net'); |
25 |
var url = require('url'); |
26 |
var events = require('events'); |
27 |
var Stream = require('stream'); |
28 |
var END_OF_FILE = 42; |
29 |
var assert = require('assert').ok; |
30 |
var constants = require('constants'); |
31 |
|
32 |
var DEFAULT_CIPHERS = 'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' + // TLS 1.2 |
33 |
'RC4:HIGH:!MD5:!aNULL:!EDH'; // TLS 1.0 |
34 |
|
35 |
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
|
36 |
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
|
37 |
// renegotations are seen. The settings are applied to all remote client
|
38 |
// connections.
|
39 |
exports.CLIENT_RENEG_LIMIT = 3;
|
40 |
exports.CLIENT_RENEG_WINDOW = 600;
|
41 |
|
42 |
exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024; |
43 |
|
44 |
|
45 |
var debug;
|
46 |
if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) { |
47 |
debug = function(a) { console.error('TLS:', a); }; |
48 |
} else {
|
49 |
debug = function() { }; |
50 |
} |
51 |
|
52 |
|
53 |
var Connection = null; |
54 |
try {
|
55 |
Connection = process.binding('crypto').Connection;
|
56 |
} catch (e) {
|
57 |
throw new Error('node.js not compiled with openssl crypto support.'); |
58 |
} |
59 |
|
60 |
// Convert protocols array into valid OpenSSL protocols list
|
61 |
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
|
62 |
function convertNPNProtocols(NPNProtocols, out) { |
63 |
// If NPNProtocols is Array - translate it into buffer
|
64 |
if (Array.isArray(NPNProtocols)) {
|
65 |
var buff = new Buffer(NPNProtocols.reduce(function(p, c) { |
66 |
return p + 1 + Buffer.byteLength(c); |
67 |
}, 0));
|
68 |
|
69 |
NPNProtocols.reduce(function(offset, c) {
|
70 |
var clen = Buffer.byteLength(c);
|
71 |
buff[offset] = clen; |
72 |
buff.write(c, offset + 1);
|
73 |
|
74 |
return offset + 1 + clen; |
75 |
}, 0);
|
76 |
|
77 |
NPNProtocols = buff; |
78 |
} |
79 |
|
80 |
// If it's already a Buffer - store it
|
81 |
if (Buffer.isBuffer(NPNProtocols)) {
|
82 |
out.NPNProtocols = NPNProtocols; |
83 |
} |
84 |
} |
85 |
|
86 |
|
87 |
function checkServerIdentity(host, cert) { |
88 |
// Create regexp to much hostnames
|
89 |
function regexpify(host, wildcards) { |
90 |
// Add trailing dot (make hostnames uniform)
|
91 |
if (!/\.$/.test(host)) host += '.'; |
92 |
|
93 |
// The same applies to hostname with more than one wildcard,
|
94 |
// if hostname has wildcard when wildcards are not allowed,
|
95 |
// or if there are less than two dots after wildcard (i.e. *.com or *d.com)
|
96 |
//
|
97 |
// also
|
98 |
//
|
99 |
// "The client SHOULD NOT attempt to match a presented identifier in
|
100 |
// which the wildcard character comprises a label other than the
|
101 |
// left-most label (e.g., do not match bar.*.example.net)."
|
102 |
// RFC6125
|
103 |
if (!wildcards && /\*/.test(host) || /[\.\*].*\*/.test(host) || |
104 |
/\*/.test(host) && !/\*.*\..+\..+/.test(host)) { |
105 |
return /$./; |
106 |
} |
107 |
|
108 |
// Replace wildcard chars with regexp's wildcard and
|
109 |
// escape all characters that have special meaning in regexps
|
110 |
// (i.e. '.', '[', '{', '*', and others)
|
111 |
var re = host.replace(
|
112 |
/\*([a-z0-9\\-_\.])|[\.,\-\\\^\$+?*\[\]\(\):!\|{}]/g,
|
113 |
function(all, sub) {
|
114 |
if (sub) return '[a-z0-9\\-_]*' + (sub === '-' ? '\\-' : sub); |
115 |
return '\\' + all; |
116 |
}); |
117 |
|
118 |
return new RegExp('^' + re + '$', 'i'); |
119 |
} |
120 |
|
121 |
var dnsNames = [],
|
122 |
uriNames = [], |
123 |
ips = [], |
124 |
matchCN = true,
|
125 |
valid = false;
|
126 |
|
127 |
// There're several names to perform check against:
|
128 |
// CN and altnames in certificate extension
|
129 |
// (DNS names, IP addresses, and URIs)
|
130 |
//
|
131 |
// Walk through altnames and generate lists of those names
|
132 |
if (cert.subjectaltname) {
|
133 |
matchCN = false;
|
134 |
cert.subjectaltname.split(/, /g).forEach(function(altname) { |
135 |
if (/^DNS:/.test(altname)) { |
136 |
dnsNames.push(altname.slice(4));
|
137 |
} else if (/^IP Address:/.test(altname)) { |
138 |
ips.push(altname.slice(11));
|
139 |
} else if (/^URI:/.test(altname)) { |
140 |
var uri = url.parse(altname.slice(4)); |
141 |
if (uri) uriNames.push(uri.hostname);
|
142 |
} |
143 |
}); |
144 |
} |
145 |
|
146 |
// If hostname is an IP address, it should be present in the list of IP
|
147 |
// addresses.
|
148 |
if (net.isIP(host)) {
|
149 |
valid = ips.some(function(ip) {
|
150 |
return ip === host;
|
151 |
}); |
152 |
} else {
|
153 |
// Transform hostname to canonical form
|
154 |
if (!/\.$/.test(host)) host += '.'; |
155 |
|
156 |
// Otherwise check all DNS/URI records from certificate
|
157 |
// (with allowed wildcards)
|
158 |
dnsNames = dnsNames.map(function(name) {
|
159 |
return regexpify(name, true); |
160 |
}); |
161 |
|
162 |
// Wildcards ain't allowed in URI names
|
163 |
uriNames = uriNames.map(function(name) {
|
164 |
return regexpify(name, false); |
165 |
}); |
166 |
|
167 |
dnsNames = dnsNames.concat(uriNames); |
168 |
|
169 |
if (dnsNames.length > 0) matchCN = false; |
170 |
|
171 |
// Match against Common Name (CN) only if altnames are not present.
|
172 |
//
|
173 |
// "As noted, a client MUST NOT seek a match for a reference identifier
|
174 |
// of CN-ID if the presented identifiers include a DNS-ID, SRV-ID,
|
175 |
// URI-ID, or any application-specific identifier types supported by the
|
176 |
// client."
|
177 |
// RFC6125
|
178 |
if (matchCN) {
|
179 |
var commonNames = cert.subject.CN;
|
180 |
if (Array.isArray(commonNames)) {
|
181 |
for (var i = 0, k = commonNames.length; i < k; ++i) { |
182 |
dnsNames.push(regexpify(commonNames[i], true));
|
183 |
} |
184 |
} else {
|
185 |
dnsNames.push(regexpify(commonNames, true));
|
186 |
} |
187 |
} |
188 |
|
189 |
valid = dnsNames.some(function(re) {
|
190 |
return re.test(host);
|
191 |
}); |
192 |
} |
193 |
|
194 |
return valid;
|
195 |
} |
196 |
exports.checkServerIdentity = checkServerIdentity; |
197 |
|
198 |
|
199 |
function SlabBuffer() { |
200 |
this.create();
|
201 |
} |
202 |
|
203 |
|
204 |
SlabBuffer.prototype.create = function create() { |
205 |
this.isFull = false; |
206 |
this.pool = new Buffer(exports.SLAB_BUFFER_SIZE); |
207 |
this.offset = 0; |
208 |
this.remaining = this.pool.length; |
209 |
}; |
210 |
|
211 |
|
212 |
SlabBuffer.prototype.use = function use(context, fn) { |
213 |
if (this.remaining === 0) { |
214 |
this.isFull = true; |
215 |
return 0; |
216 |
} |
217 |
|
218 |
var bytes = fn.call(context, this.pool, this.offset, this.remaining); |
219 |
|
220 |
if (bytes > 0) { |
221 |
this.offset += bytes;
|
222 |
this.remaining -= bytes;
|
223 |
} |
224 |
|
225 |
assert(this.remaining >= 0); |
226 |
|
227 |
return bytes;
|
228 |
}; |
229 |
|
230 |
|
231 |
var slabBuffer = null; |
232 |
|
233 |
|
234 |
// Base class of both CleartextStream and EncryptedStream
|
235 |
function CryptoStream(pair) { |
236 |
Stream.call(this);
|
237 |
|
238 |
this.pair = pair;
|
239 |
|
240 |
this.readable = this.writable = true; |
241 |
|
242 |
this._paused = false; |
243 |
this._needDrain = false; |
244 |
this._pending = [];
|
245 |
this._pendingCallbacks = [];
|
246 |
this._pendingBytes = 0; |
247 |
if (slabBuffer === null) slabBuffer = new SlabBuffer(); |
248 |
this._buffer = slabBuffer;
|
249 |
} |
250 |
util.inherits(CryptoStream, Stream); |
251 |
|
252 |
|
253 |
CryptoStream.prototype.write = function(data /* , encoding, cb */) { |
254 |
if (this == this.pair.cleartext) { |
255 |
debug('cleartext.write called with ' + data.length + ' bytes'); |
256 |
} else {
|
257 |
debug('encrypted.write called with ' + data.length + ' bytes'); |
258 |
} |
259 |
|
260 |
if (!this.writable) { |
261 |
throw new Error('CryptoStream is not writable'); |
262 |
} |
263 |
|
264 |
var encoding, cb;
|
265 |
|
266 |
// parse arguments
|
267 |
if (typeof arguments[1] == 'string') { |
268 |
encoding = arguments[1]; |
269 |
cb = arguments[2]; |
270 |
} else {
|
271 |
cb = arguments[1]; |
272 |
} |
273 |
|
274 |
|
275 |
// Transform strings into buffers.
|
276 |
if (typeof data == 'string') { |
277 |
data = new Buffer(data, encoding);
|
278 |
} |
279 |
|
280 |
debug((this === this.pair.cleartext ? 'clear' : 'encrypted') + 'In data'); |
281 |
|
282 |
this._pending.push(data);
|
283 |
this._pendingCallbacks.push(cb);
|
284 |
this._pendingBytes += data.length;
|
285 |
|
286 |
this.pair._writeCalled = true; |
287 |
this.pair.cycle();
|
288 |
|
289 |
// In the following cases, write() should return a false,
|
290 |
// then this stream should eventually emit 'drain' event.
|
291 |
//
|
292 |
// 1. There are pending data more than 128k bytes.
|
293 |
// 2. A forward stream shown below is paused.
|
294 |
// A) EncryptedStream for CleartextStream.write().
|
295 |
// B) CleartextStream for EncryptedStream.write().
|
296 |
//
|
297 |
if (!this._needDrain) { |
298 |
if (this._pendingBytes >= 128 * 1024) { |
299 |
this._needDrain = true; |
300 |
} else {
|
301 |
if (this === this.pair.cleartext) { |
302 |
this._needDrain = this.pair.encrypted._paused; |
303 |
} else {
|
304 |
this._needDrain = this.pair.cleartext._paused; |
305 |
} |
306 |
} |
307 |
} |
308 |
return !this._needDrain; |
309 |
}; |
310 |
|
311 |
|
312 |
CryptoStream.prototype.pause = function() { |
313 |
debug('paused ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted')); |
314 |
this._paused = true; |
315 |
}; |
316 |
|
317 |
|
318 |
CryptoStream.prototype.resume = function() { |
319 |
debug('resume ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted')); |
320 |
this._paused = false; |
321 |
this.pair.cycle();
|
322 |
}; |
323 |
|
324 |
|
325 |
CryptoStream.prototype.setTimeout = function(timeout, callback) { |
326 |
if (this.socket) this.socket.setTimeout(timeout, callback); |
327 |
}; |
328 |
|
329 |
|
330 |
CryptoStream.prototype.setNoDelay = function(noDelay) { |
331 |
if (this.socket) this.socket.setNoDelay(noDelay); |
332 |
}; |
333 |
|
334 |
|
335 |
CryptoStream.prototype.setKeepAlive = function(enable, initialDelay) { |
336 |
if (this.socket) this.socket.setKeepAlive(enable, initialDelay); |
337 |
}; |
338 |
|
339 |
CryptoStream.prototype.__defineGetter__('bytesWritten', function() { |
340 |
return this.socket ? this.socket.bytesWritten : 0; |
341 |
}); |
342 |
|
343 |
CryptoStream.prototype.setEncoding = function(encoding) { |
344 |
var StringDecoder = require('string_decoder').StringDecoder; // lazy load |
345 |
this._decoder = new StringDecoder(encoding); |
346 |
}; |
347 |
|
348 |
|
349 |
// Example:
|
350 |
// C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org
|
351 |
function parseCertString(s) { |
352 |
var out = {};
|
353 |
var parts = s.split('\n'); |
354 |
for (var i = 0, len = parts.length; i < len; i++) { |
355 |
var sepIndex = parts[i].indexOf('='); |
356 |
if (sepIndex > 0) { |
357 |
var key = parts[i].slice(0, sepIndex); |
358 |
var value = parts[i].slice(sepIndex + 1); |
359 |
if (key in out) { |
360 |
if (!Array.isArray(out[key])) {
|
361 |
out[key] = [out[key]]; |
362 |
} |
363 |
out[key].push(value); |
364 |
} else {
|
365 |
out[key] = value; |
366 |
} |
367 |
} |
368 |
} |
369 |
return out;
|
370 |
} |
371 |
|
372 |
|
373 |
CryptoStream.prototype.getPeerCertificate = function() { |
374 |
if (this.pair.ssl) { |
375 |
var c = this.pair.ssl.getPeerCertificate(); |
376 |
|
377 |
if (c) {
|
378 |
if (c.issuer) c.issuer = parseCertString(c.issuer);
|
379 |
if (c.subject) c.subject = parseCertString(c.subject);
|
380 |
return c;
|
381 |
} |
382 |
} |
383 |
|
384 |
return null; |
385 |
}; |
386 |
|
387 |
CryptoStream.prototype.getSession = function() { |
388 |
if (this.pair.ssl) { |
389 |
return this.pair.ssl.getSession(); |
390 |
} |
391 |
|
392 |
return null; |
393 |
}; |
394 |
|
395 |
CryptoStream.prototype.isSessionReused = function() { |
396 |
if (this.pair.ssl) { |
397 |
return this.pair.ssl.isSessionReused(); |
398 |
} |
399 |
|
400 |
return null; |
401 |
}; |
402 |
|
403 |
CryptoStream.prototype.getCipher = function(err) { |
404 |
if (this.pair.ssl) { |
405 |
return this.pair.ssl.getCurrentCipher(); |
406 |
} else {
|
407 |
return null; |
408 |
} |
409 |
}; |
410 |
|
411 |
|
412 |
CryptoStream.prototype.end = function(d) { |
413 |
if (this.pair._doneFlag) return; |
414 |
if (!this.writable) return; |
415 |
|
416 |
if (d) {
|
417 |
this.write(d);
|
418 |
} |
419 |
|
420 |
this._pending.push(END_OF_FILE);
|
421 |
this._pendingCallbacks.push(null); |
422 |
|
423 |
// If this is an encrypted stream then we need to disable further 'data'
|
424 |
// events.
|
425 |
|
426 |
this.writable = false; |
427 |
|
428 |
this.pair.cycle();
|
429 |
}; |
430 |
|
431 |
|
432 |
CryptoStream.prototype.destroySoon = function(err) { |
433 |
if (this.writable) { |
434 |
this.end();
|
435 |
} else {
|
436 |
this.destroy();
|
437 |
} |
438 |
}; |
439 |
|
440 |
|
441 |
CryptoStream.prototype.destroy = function(err) { |
442 |
if (this.pair._doneFlag) return; |
443 |
this.pair.destroy();
|
444 |
}; |
445 |
|
446 |
|
447 |
CryptoStream.prototype._done = function() { |
448 |
this._doneFlag = true; |
449 |
|
450 |
if (this.pair.cleartext._doneFlag && |
451 |
this.pair.encrypted._doneFlag &&
|
452 |
!this.pair._doneFlag) {
|
453 |
// If both streams are done:
|
454 |
if (!this.pair._secureEstablished) { |
455 |
this.pair.error();
|
456 |
} else {
|
457 |
this.pair.destroy();
|
458 |
} |
459 |
} |
460 |
}; |
461 |
|
462 |
|
463 |
// readyState is deprecated. Don't use it.
|
464 |
Object.defineProperty(CryptoStream.prototype, 'readyState', {
|
465 |
get: function() { |
466 |
if (this._connecting) { |
467 |
return 'opening'; |
468 |
} else if (this.readable && this.writable) { |
469 |
return 'open'; |
470 |
} else if (this.readable && !this.writable) { |
471 |
return 'readOnly'; |
472 |
} else if (!this.readable && this.writable) { |
473 |
return 'writeOnly'; |
474 |
} else {
|
475 |
return 'closed'; |
476 |
} |
477 |
} |
478 |
}); |
479 |
|
480 |
|
481 |
// Move decrypted, clear data out into the application.
|
482 |
// From the user's perspective this occurs as a 'data' event
|
483 |
// on the pair.cleartext.
|
484 |
// also
|
485 |
// Move encrypted data to the stream. From the user's perspective this
|
486 |
// occurs as a 'data' event on the pair.encrypted. Usually the application
|
487 |
// will have some code which pipes the stream to a socket:
|
488 |
//
|
489 |
// pair.encrypted.on('data', function (d) {
|
490 |
// socket.write(d);
|
491 |
// });
|
492 |
//
|
493 |
CryptoStream.prototype._push = function() { |
494 |
if (this == this.pair.encrypted && !this.writable) { |
495 |
// If the encrypted side got EOF, we do not attempt
|
496 |
// to write out data anymore.
|
497 |
return;
|
498 |
} |
499 |
|
500 |
while (!this._paused) { |
501 |
var chunkBytes = 0, |
502 |
bytesRead = 0,
|
503 |
start = this._buffer.offset;
|
504 |
|
505 |
do {
|
506 |
chunkBytes = this._buffer.use(this, this._pusher); |
507 |
if (chunkBytes > 0) bytesRead += chunkBytes; |
508 |
|
509 |
if (this.pair.ssl && this.pair.ssl.error) { |
510 |
this.pair.error();
|
511 |
return;
|
512 |
} |
513 |
|
514 |
this.pair.maybeInitFinished();
|
515 |
|
516 |
} while (chunkBytes > 0 && !this._buffer.isFull); |
517 |
|
518 |
var pool = this._buffer.pool; |
519 |
|
520 |
// Create new buffer if previous was filled up
|
521 |
if (this._buffer.isFull) this._buffer.create(); |
522 |
|
523 |
assert(bytesRead >= 0);
|
524 |
|
525 |
// Bail out if we didn't read any data.
|
526 |
if (bytesRead == 0) { |
527 |
if (this._internallyPendingBytes() == 0 && this._destroyAfterPush) { |
528 |
this._done();
|
529 |
} |
530 |
return;
|
531 |
} |
532 |
|
533 |
var chunk = pool.slice(start, start + bytesRead);
|
534 |
|
535 |
if (this === this.pair.cleartext) { |
536 |
debug('cleartext emit "data" with ' + bytesRead + ' bytes'); |
537 |
} else {
|
538 |
debug('encrypted emit "data" with ' + bytesRead + ' bytes'); |
539 |
} |
540 |
|
541 |
if (this._decoder) { |
542 |
var string = this._decoder.write(chunk); |
543 |
if (string.length) this.emit('data', string); |
544 |
} else {
|
545 |
this.emit('data', chunk); |
546 |
} |
547 |
|
548 |
// Optimization: emit the original buffer with end points
|
549 |
if (this.ondata) this.ondata(pool, start, start + bytesRead); |
550 |
} |
551 |
}; |
552 |
|
553 |
|
554 |
// Push in any clear data coming from the application.
|
555 |
// This arrives via some code like this:
|
556 |
//
|
557 |
// pair.cleartext.write("hello world");
|
558 |
//
|
559 |
// also
|
560 |
//
|
561 |
// Push in incoming encrypted data from the socket.
|
562 |
// This arrives via some code like this:
|
563 |
//
|
564 |
// socket.on('data', function (d) {
|
565 |
// pair.encrypted.write(d)
|
566 |
// });
|
567 |
//
|
568 |
CryptoStream.prototype._pull = function() { |
569 |
var havePending = this._pending.length > 0; |
570 |
|
571 |
assert(havePending || this._pendingBytes == 0); |
572 |
|
573 |
while (this._pending.length > 0) { |
574 |
if (!this.pair.ssl) break; |
575 |
|
576 |
var tmp = this._pending.shift(); |
577 |
var cb = this._pendingCallbacks.shift(); |
578 |
|
579 |
assert(this._pending.length === this._pendingCallbacks.length); |
580 |
|
581 |
if (tmp === END_OF_FILE) {
|
582 |
// Sending EOF
|
583 |
if (this === this.pair.encrypted) { |
584 |
debug('end encrypted ' + this.pair.fd); |
585 |
this.pair.cleartext._destroyAfterPush = true; |
586 |
} else {
|
587 |
// CleartextStream
|
588 |
assert(this === this.pair.cleartext); |
589 |
debug('end cleartext');
|
590 |
|
591 |
this.pair.ssl.shutdown();
|
592 |
|
593 |
// TODO check if we get EAGAIN From shutdown, would have to do it
|
594 |
// again. should unshift END_OF_FILE back onto pending and wait for
|
595 |
// next cycle.
|
596 |
|
597 |
this.pair.encrypted._destroyAfterPush = true; |
598 |
} |
599 |
this.pair.cycle();
|
600 |
this._done();
|
601 |
return;
|
602 |
} |
603 |
|
604 |
if (tmp.length == 0) continue; |
605 |
|
606 |
var rv = this._puller(tmp); |
607 |
|
608 |
if (this.pair.ssl && this.pair.ssl.error) { |
609 |
this.pair.error();
|
610 |
return;
|
611 |
} |
612 |
|
613 |
this.pair.maybeInitFinished();
|
614 |
|
615 |
if (rv === 0 || rv < 0) { |
616 |
this._pending.unshift(tmp);
|
617 |
this._pendingCallbacks.unshift(cb);
|
618 |
break;
|
619 |
} |
620 |
|
621 |
this._pendingBytes -= tmp.length;
|
622 |
assert(this._pendingBytes >= 0); |
623 |
|
624 |
if (cb) cb();
|
625 |
|
626 |
assert(rv === tmp.length); |
627 |
} |
628 |
|
629 |
// If pending data has cleared, 'drain' event should be emitted
|
630 |
// after write() returns a false.
|
631 |
// Except when a forward stream shown below is paused.
|
632 |
// A) EncryptedStream for CleartextStream._pull().
|
633 |
// B) CleartextStream for EncryptedStream._pull().
|
634 |
//
|
635 |
if (this._needDrain && this._pending.length === 0) { |
636 |
var paused;
|
637 |
if (this === this.pair.cleartext) { |
638 |
paused = this.pair.encrypted._paused;
|
639 |
} else {
|
640 |
paused = this.pair.cleartext._paused;
|
641 |
} |
642 |
if (!paused) {
|
643 |
debug('drain ' + (this === this.pair.cleartext ? 'clear' : 'encrypted')); |
644 |
var self = this; |
645 |
process.nextTick(function() {
|
646 |
self.emit('drain');
|
647 |
}); |
648 |
this._needDrain = false; |
649 |
if (this.__destroyOnDrain) this.end(); |
650 |
} |
651 |
} |
652 |
}; |
653 |
|
654 |
|
655 |
function CleartextStream(pair) { |
656 |
CryptoStream.call(this, pair);
|
657 |
} |
658 |
util.inherits(CleartextStream, CryptoStream); |
659 |
|
660 |
|
661 |
CleartextStream.prototype._internallyPendingBytes = function() { |
662 |
if (this.pair.ssl) { |
663 |
return this.pair.ssl.clearPending(); |
664 |
} else {
|
665 |
return 0; |
666 |
} |
667 |
}; |
668 |
|
669 |
|
670 |
CleartextStream.prototype._puller = function(b) { |
671 |
debug('clearIn ' + b.length + ' bytes'); |
672 |
return this.pair.ssl.clearIn(b, 0, b.length); |
673 |
}; |
674 |
|
675 |
|
676 |
CleartextStream.prototype._pusher = function(pool, offset, length) { |
677 |
debug('reading from clearOut');
|
678 |
if (!this.pair.ssl) return -1; |
679 |
return this.pair.ssl.clearOut(pool, offset, length); |
680 |
}; |
681 |
|
682 |
CleartextStream.prototype.address = function() { |
683 |
return this.socket && this.socket.address(); |
684 |
}; |
685 |
|
686 |
CleartextStream.prototype.__defineGetter__('remoteAddress', function() { |
687 |
return this.socket && this.socket.remoteAddress; |
688 |
}); |
689 |
|
690 |
|
691 |
CleartextStream.prototype.__defineGetter__('remotePort', function() { |
692 |
return this.socket && this.socket.remotePort; |
693 |
}); |
694 |
|
695 |
function EncryptedStream(pair) { |
696 |
CryptoStream.call(this, pair);
|
697 |
} |
698 |
util.inherits(EncryptedStream, CryptoStream); |
699 |
|
700 |
|
701 |
EncryptedStream.prototype._internallyPendingBytes = function() { |
702 |
if (this.pair.ssl) { |
703 |
return this.pair.ssl.encPending(); |
704 |
} else {
|
705 |
return 0; |
706 |
} |
707 |
}; |
708 |
|
709 |
|
710 |
EncryptedStream.prototype._puller = function(b) { |
711 |
debug('writing from encIn');
|
712 |
return this.pair.ssl.encIn(b, 0, b.length); |
713 |
}; |
714 |
|
715 |
|
716 |
EncryptedStream.prototype._pusher = function(pool, offset, length) { |
717 |
debug('reading from encOut');
|
718 |
if (!this.pair.ssl) return -1; |
719 |
return this.pair.ssl.encOut(pool, offset, length); |
720 |
}; |
721 |
|
722 |
|
723 |
function onhandshakestart() { |
724 |
debug('onhandshakestart');
|
725 |
|
726 |
var self = this; |
727 |
var ssl = self.ssl;
|
728 |
var now = Date.now();
|
729 |
|
730 |
assert(now >= ssl.lastHandshakeTime); |
731 |
|
732 |
if ((now - ssl.lastHandshakeTime) >= exports.CLIENT_RENEG_WINDOW * 1000) { |
733 |
ssl.handshakes = 0;
|
734 |
} |
735 |
|
736 |
var first = (ssl.lastHandshakeTime === 0); |
737 |
ssl.lastHandshakeTime = now; |
738 |
if (first) return; |
739 |
|
740 |
if (++ssl.handshakes > exports.CLIENT_RENEG_LIMIT) {
|
741 |
// Defer the error event to the next tick. We're being called from OpenSSL's
|
742 |
// state machine and OpenSSL is not re-entrant. We cannot allow the user's
|
743 |
// callback to destroy the connection right now, it would crash and burn.
|
744 |
setImmediate(function() {
|
745 |
var err = new Error('TLS session renegotiation attack detected.'); |
746 |
if (self.cleartext) self.cleartext.emit('error', err); |
747 |
}); |
748 |
} |
749 |
} |
750 |
|
751 |
|
752 |
function onhandshakedone() { |
753 |
// for future use
|
754 |
debug('onhandshakedone');
|
755 |
} |
756 |
|
757 |
function onclienthello(hello) { |
758 |
var self = this, |
759 |
once = false;
|
760 |
|
761 |
this.encrypted.pause();
|
762 |
this.cleartext.pause();
|
763 |
function callback(err, session) { |
764 |
if (once) return; |
765 |
once = true;
|
766 |
|
767 |
if (err) return self.socket.destroy(err); |
768 |
|
769 |
self.ssl.loadSession(session); |
770 |
|
771 |
self.encrypted.resume(); |
772 |
self.cleartext.resume(); |
773 |
} |
774 |
|
775 |
if (hello.sessionId.length <= 0 || |
776 |
!this.server ||
|
777 |
!this.server.emit('resumeSession', hello.sessionId, callback)) { |
778 |
callback(null, null); |
779 |
} |
780 |
} |
781 |
|
782 |
|
783 |
function onnewsession(key, session) { |
784 |
if (!this.server) return; |
785 |
this.server.emit('newSession', key, session); |
786 |
} |
787 |
|
788 |
|
789 |
/**
|
790 |
* Provides a pair of streams to do encrypted communication.
|
791 |
*/
|
792 |
|
793 |
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized, |
794 |
options) { |
795 |
if (!(this instanceof SecurePair)) { |
796 |
return new SecurePair(credentials, |
797 |
isServer, |
798 |
requestCert, |
799 |
rejectUnauthorized, |
800 |
options); |
801 |
} |
802 |
|
803 |
var self = this; |
804 |
|
805 |
options || (options = {}); |
806 |
|
807 |
events.EventEmitter.call(this);
|
808 |
|
809 |
this.server = options.server;
|
810 |
this._secureEstablished = false; |
811 |
this._isServer = isServer ? true : false; |
812 |
this._encWriteState = true; |
813 |
this._clearWriteState = true; |
814 |
this._doneFlag = false; |
815 |
|
816 |
if (!credentials) {
|
817 |
this.credentials = crypto.createCredentials();
|
818 |
} else {
|
819 |
this.credentials = credentials;
|
820 |
} |
821 |
|
822 |
if (!this._isServer) { |
823 |
// For clients, we will always have either a given ca list or be using
|
824 |
// default one
|
825 |
requestCert = true;
|
826 |
} |
827 |
|
828 |
this._rejectUnauthorized = rejectUnauthorized ? true : false; |
829 |
this._requestCert = requestCert ? true : false; |
830 |
|
831 |
this.ssl = new Connection(this.credentials.context, |
832 |
this._isServer ? true : false, |
833 |
this._isServer ? this._requestCert : |
834 |
options.servername, |
835 |
this._rejectUnauthorized);
|
836 |
|
837 |
if (this._isServer) { |
838 |
this.ssl.onhandshakestart = onhandshakestart.bind(this); |
839 |
this.ssl.onhandshakedone = onhandshakedone.bind(this); |
840 |
this.ssl.onclienthello = onclienthello.bind(this); |
841 |
this.ssl.onnewsession = onnewsession.bind(this); |
842 |
this.ssl.lastHandshakeTime = 0; |
843 |
this.ssl.handshakes = 0; |
844 |
} |
845 |
|
846 |
if (process.features.tls_sni) {
|
847 |
if (this._isServer && options.SNICallback) { |
848 |
this.ssl.setSNICallback(options.SNICallback);
|
849 |
} |
850 |
this.servername = null; |
851 |
} |
852 |
|
853 |
if (process.features.tls_npn && options.NPNProtocols) {
|
854 |
this.ssl.setNPNProtocols(options.NPNProtocols);
|
855 |
this.npnProtocol = null; |
856 |
} |
857 |
|
858 |
/* Acts as a r/w stream to the cleartext side of the stream. */
|
859 |
this.cleartext = new CleartextStream(this); |
860 |
|
861 |
/* Acts as a r/w stream to the encrypted side of the stream. */
|
862 |
this.encrypted = new EncryptedStream(this); |
863 |
|
864 |
process.nextTick(function() {
|
865 |
/* The Connection may be destroyed by an abort call */
|
866 |
if (self.ssl) {
|
867 |
self.ssl.start(); |
868 |
} |
869 |
self.cycle(); |
870 |
}); |
871 |
} |
872 |
|
873 |
util.inherits(SecurePair, events.EventEmitter); |
874 |
|
875 |
|
876 |
exports.createSecurePair = function(credentials, |
877 |
isServer, |
878 |
requestCert, |
879 |
rejectUnauthorized) { |
880 |
var pair = new SecurePair(credentials, |
881 |
isServer, |
882 |
requestCert, |
883 |
rejectUnauthorized); |
884 |
return pair;
|
885 |
}; |
886 |
|
887 |
|
888 |
|
889 |
|
890 |
/* Attempt to cycle OpenSSLs buffers in various directions.
|
891 |
*
|
892 |
* An SSL Connection can be viewed as four separate piplines,
|
893 |
* interacting with one has no connection to the behavoir of
|
894 |
* any of the other 3 -- This might not sound reasonable,
|
895 |
* but consider things like mid-stream renegotiation of
|
896 |
* the ciphers.
|
897 |
*
|
898 |
* The four pipelines, using terminology of the client (server is just
|
899 |
* reversed):
|
900 |
* (1) Encrypted Output stream (Writing encrypted data to peer)
|
901 |
* (2) Encrypted Input stream (Reading encrypted data from peer)
|
902 |
* (3) Cleartext Output stream (Decrypted content from the peer)
|
903 |
* (4) Cleartext Input stream (Cleartext content to send to the peer)
|
904 |
*
|
905 |
* This function attempts to pull any available data out of the Cleartext
|
906 |
* input stream (4), and the Encrypted input stream (2). Then it pushes any
|
907 |
* data available from the cleartext output stream (3), and finally from the
|
908 |
* Encrypted output stream (1)
|
909 |
*
|
910 |
* It is called whenever we do something with OpenSSL -- post reciving
|
911 |
* content, trying to flush, trying to change ciphers, or shutting down the
|
912 |
* connection.
|
913 |
*
|
914 |
* Because it is also called everywhere, we also check if the connection has
|
915 |
* completed negotiation and emit 'secure' from here if it has.
|
916 |
*/
|
917 |
SecurePair.prototype.cycle = function(depth) { |
918 |
if (this._doneFlag) return; |
919 |
|
920 |
depth = depth ? depth : 0;
|
921 |
|
922 |
if (depth == 0) this._writeCalled = false; |
923 |
|
924 |
var established = this._secureEstablished; |
925 |
|
926 |
if (!this.cycleEncryptedPullLock) { |
927 |
this.cycleEncryptedPullLock = true; |
928 |
debug('encrypted._pull');
|
929 |
this.encrypted._pull();
|
930 |
this.cycleEncryptedPullLock = false; |
931 |
} |
932 |
|
933 |
if (!this.cycleCleartextPullLock) { |
934 |
this.cycleCleartextPullLock = true; |
935 |
debug('cleartext._pull');
|
936 |
this.cleartext._pull();
|
937 |
this.cycleCleartextPullLock = false; |
938 |
} |
939 |
|
940 |
if (!this.cycleCleartextPushLock) { |
941 |
this.cycleCleartextPushLock = true; |
942 |
debug('cleartext._push');
|
943 |
this.cleartext._push();
|
944 |
this.cycleCleartextPushLock = false; |
945 |
} |
946 |
|
947 |
if (!this.cycleEncryptedPushLock) { |
948 |
this.cycleEncryptedPushLock = true; |
949 |
debug('encrypted._push');
|
950 |
this.encrypted._push();
|
951 |
this.cycleEncryptedPushLock = false; |
952 |
} |
953 |
|
954 |
if ((!established && this._secureEstablished) || |
955 |
(depth == 0 && this._writeCalled)) { |
956 |
// If we were not established but now we are, let's cycle again.
|
957 |
// Or if there is some data to write...
|
958 |
this.cycle(depth + 1); |
959 |
} |
960 |
}; |
961 |
|
962 |
|
963 |
SecurePair.prototype.maybeInitFinished = function() { |
964 |
if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) { |
965 |
if (process.features.tls_npn) {
|
966 |
this.npnProtocol = this.ssl.getNegotiatedProtocol(); |
967 |
} |
968 |
|
969 |
if (process.features.tls_sni) {
|
970 |
this.servername = this.ssl.getServername(); |
971 |
} |
972 |
|
973 |
this._secureEstablished = true; |
974 |
debug('secure established');
|
975 |
this.emit('secure'); |
976 |
} |
977 |
}; |
978 |
|
979 |
|
980 |
SecurePair.prototype.destroy = function() { |
981 |
var self = this; |
982 |
|
983 |
if (!this._doneFlag) { |
984 |
this._doneFlag = true; |
985 |
this.ssl.error = null; |
986 |
this.ssl.close();
|
987 |
this.ssl = null; |
988 |
|
989 |
self.encrypted.writable = self.encrypted.readable = false;
|
990 |
self.cleartext.writable = self.cleartext.readable = false;
|
991 |
|
992 |
process.nextTick(function() {
|
993 |
if (self.cleartext._decoder) {
|
994 |
var ret = self.cleartext._decoder.end();
|
995 |
if (ret)
|
996 |
self.cleartext.emit('data', ret);
|
997 |
} |
998 |
self.cleartext.emit('end');
|
999 |
self.encrypted.emit('close');
|
1000 |
self.cleartext.emit('close');
|
1001 |
}); |
1002 |
} |
1003 |
}; |
1004 |
|
1005 |
|
1006 |
SecurePair.prototype.error = function() { |
1007 |
if (!this._secureEstablished) { |
1008 |
var error = this.ssl.error; |
1009 |
if (!error) {
|
1010 |
error = new Error('socket hang up'); |
1011 |
error.code = 'ECONNRESET';
|
1012 |
} |
1013 |
this.destroy();
|
1014 |
this.emit('error', error); |
1015 |
} else {
|
1016 |
var err = this.ssl.error; |
1017 |
this.ssl.error = null; |
1018 |
|
1019 |
if (this._isServer && |
1020 |
this._rejectUnauthorized &&
|
1021 |
/peer did not return a certificate/.test(err.message)) {
|
1022 |
// Not really an error.
|
1023 |
this.destroy();
|
1024 |
} else {
|
1025 |
this.cleartext.emit('error', err); |
1026 |
} |
1027 |
} |
1028 |
}; |
1029 |
|
1030 |
// TODO: support anonymous (nocert) and PSK
|
1031 |
|
1032 |
|
1033 |
// AUTHENTICATION MODES
|
1034 |
//
|
1035 |
// There are several levels of authentication that TLS/SSL supports.
|
1036 |
// Read more about this in "man SSL_set_verify".
|
1037 |
//
|
1038 |
// 1. The server sends a certificate to the client but does not request a
|
1039 |
// cert from the client. This is common for most HTTPS servers. The browser
|
1040 |
// can verify the identity of the server, but the server does not know who
|
1041 |
// the client is. Authenticating the client is usually done over HTTP using
|
1042 |
// login boxes and cookies and stuff.
|
1043 |
//
|
1044 |
// 2. The server sends a cert to the client and requests that the client
|
1045 |
// also send it a cert. The client knows who the server is and the server is
|
1046 |
// requesting the client also identify themselves. There are several
|
1047 |
// outcomes:
|
1048 |
//
|
1049 |
// A) verifyError returns null meaning the client's certificate is signed
|
1050 |
// by one of the server's CAs. The server know's the client idenity now
|
1051 |
// and the client is authorized.
|
1052 |
//
|
1053 |
// B) For some reason the client's certificate is not acceptable -
|
1054 |
// verifyError returns a string indicating the problem. The server can
|
1055 |
// either (i) reject the client or (ii) allow the client to connect as an
|
1056 |
// unauthorized connection.
|
1057 |
//
|
1058 |
// The mode is controlled by two boolean variables.
|
1059 |
//
|
1060 |
// requestCert
|
1061 |
// If true the server requests a certificate from client connections. For
|
1062 |
// the common HTTPS case, users will want this to be false, which is what
|
1063 |
// it defaults to.
|
1064 |
//
|
1065 |
// rejectUnauthorized
|
1066 |
// If true clients whose certificates are invalid for any reason will not
|
1067 |
// be allowed to make connections. If false, they will simply be marked as
|
1068 |
// unauthorized but secure communication will continue. By default this is
|
1069 |
// true.
|
1070 |
//
|
1071 |
//
|
1072 |
//
|
1073 |
// Options:
|
1074 |
// - requestCert. Send verify request. Default to false.
|
1075 |
// - rejectUnauthorized. Boolean, default to true.
|
1076 |
// - key. string.
|
1077 |
// - cert: string.
|
1078 |
// - ca: string or array of strings.
|
1079 |
//
|
1080 |
// emit 'secureConnection'
|
1081 |
// function (cleartextStream, encryptedStream) { }
|
1082 |
//
|
1083 |
// 'cleartextStream' has the boolean property 'authorized' to determine if
|
1084 |
// it was verified by the CA. If 'authorized' is false, a property
|
1085 |
// 'authorizationError' is set on cleartextStream and has the possible
|
1086 |
// values:
|
1087 |
//
|
1088 |
// "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
|
1089 |
// "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
|
1090 |
// "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
|
1091 |
// "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
|
1092 |
// "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
|
1093 |
// "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
|
1094 |
// "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
|
1095 |
// "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
|
1096 |
// "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
1097 |
// "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
|
1098 |
// "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
|
1099 |
// "CERT_REJECTED"
|
1100 |
//
|
1101 |
//
|
1102 |
// TODO:
|
1103 |
// cleartext.credentials (by mirroring from pair object)
|
1104 |
// cleartext.getCertificate() (by mirroring from pair.credentials.context)
|
1105 |
function Server(/* [options], listener */) { |
1106 |
var options, listener;
|
1107 |
if (typeof arguments[0] == 'object') { |
1108 |
options = arguments[0]; |
1109 |
listener = arguments[1]; |
1110 |
} else if (typeof arguments[0] == 'function') { |
1111 |
options = {}; |
1112 |
listener = arguments[0]; |
1113 |
} |
1114 |
|
1115 |
if (!(this instanceof Server)) return new Server(options, listener); |
1116 |
|
1117 |
this._contexts = [];
|
1118 |
|
1119 |
var self = this; |
1120 |
|
1121 |
// Handle option defaults:
|
1122 |
this.setOptions(options);
|
1123 |
|
1124 |
if (!self.pfx && (!self.cert || !self.key)) {
|
1125 |
throw new Error('Missing PFX or certificate + private key.'); |
1126 |
} |
1127 |
|
1128 |
var sharedCreds = crypto.createCredentials({
|
1129 |
pfx: self.pfx,
|
1130 |
key: self.key,
|
1131 |
passphrase: self.passphrase,
|
1132 |
cert: self.cert,
|
1133 |
ca: self.ca,
|
1134 |
ciphers: self.ciphers || DEFAULT_CIPHERS,
|
1135 |
secureProtocol: self.secureProtocol,
|
1136 |
secureOptions: self.secureOptions,
|
1137 |
crl: self.crl,
|
1138 |
sessionIdContext: self.sessionIdContext
|
1139 |
}); |
1140 |
|
1141 |
var timeout = options.handshakeTimeout || (120 * 1000); |
1142 |
|
1143 |
if (typeof timeout !== 'number') { |
1144 |
throw new TypeError('handshakeTimeout must be a number'); |
1145 |
} |
1146 |
|
1147 |
// constructor call
|
1148 |
net.Server.call(this, function(socket) { |
1149 |
var creds = crypto.createCredentials(null, sharedCreds.context); |
1150 |
|
1151 |
var pair = new SecurePair(creds, |
1152 |
true,
|
1153 |
self.requestCert, |
1154 |
self.rejectUnauthorized, |
1155 |
{ |
1156 |
server: self,
|
1157 |
NPNProtocols: self.NPNProtocols,
|
1158 |
SNICallback: self.SNICallback
|
1159 |
}); |
1160 |
|
1161 |
var cleartext = pipe(pair, socket);
|
1162 |
cleartext._controlReleased = false;
|
1163 |
|
1164 |
function listener() { |
1165 |
pair.emit('error', new Error('TLS handshake timeout')); |
1166 |
} |
1167 |
|
1168 |
if (timeout > 0) { |
1169 |
socket.setTimeout(timeout, listener); |
1170 |
} |
1171 |
|
1172 |
pair.once('secure', function() { |
1173 |
socket.setTimeout(0, listener);
|
1174 |
|
1175 |
pair.cleartext.authorized = false;
|
1176 |
pair.cleartext.npnProtocol = pair.npnProtocol; |
1177 |
pair.cleartext.servername = pair.servername; |
1178 |
|
1179 |
if (!self.requestCert) {
|
1180 |
cleartext._controlReleased = true;
|
1181 |
self.emit('secureConnection', pair.cleartext, pair.encrypted);
|
1182 |
} else {
|
1183 |
var verifyError = pair.ssl.verifyError();
|
1184 |
if (verifyError) {
|
1185 |
pair.cleartext.authorizationError = verifyError.message; |
1186 |
|
1187 |
if (self.rejectUnauthorized) {
|
1188 |
socket.destroy(); |
1189 |
pair.destroy(); |
1190 |
} else {
|
1191 |
cleartext._controlReleased = true;
|
1192 |
self.emit('secureConnection', pair.cleartext, pair.encrypted);
|
1193 |
} |
1194 |
} else {
|
1195 |
pair.cleartext.authorized = true;
|
1196 |
cleartext._controlReleased = true;
|
1197 |
self.emit('secureConnection', pair.cleartext, pair.encrypted);
|
1198 |
} |
1199 |
} |
1200 |
}); |
1201 |
pair.on('error', function(err) { |
1202 |
self.emit('clientError', err, this); |
1203 |
}); |
1204 |
}); |
1205 |
|
1206 |
if (listener) {
|
1207 |
this.on('secureConnection', listener); |
1208 |
} |
1209 |
} |
1210 |
|
1211 |
util.inherits(Server, net.Server); |
1212 |
exports.Server = Server; |
1213 |
exports.createServer = function(options, listener) { |
1214 |
return new Server(options, listener); |
1215 |
}; |
1216 |
|
1217 |
|
1218 |
Server.prototype.setOptions = function(options) { |
1219 |
if (typeof options.requestCert == 'boolean') { |
1220 |
this.requestCert = options.requestCert;
|
1221 |
} else {
|
1222 |
this.requestCert = false; |
1223 |
} |
1224 |
|
1225 |
if (typeof options.rejectUnauthorized == 'boolean') { |
1226 |
this.rejectUnauthorized = options.rejectUnauthorized;
|
1227 |
} else {
|
1228 |
this.rejectUnauthorized = false; |
1229 |
} |
1230 |
|
1231 |
if (options.pfx) this.pfx = options.pfx; |
1232 |
if (options.key) this.key = options.key; |
1233 |
if (options.passphrase) this.passphrase = options.passphrase; |
1234 |
if (options.cert) this.cert = options.cert; |
1235 |
if (options.ca) this.ca = options.ca; |
1236 |
if (options.secureProtocol) this.secureProtocol = options.secureProtocol; |
1237 |
if (options.crl) this.crl = options.crl; |
1238 |
if (options.ciphers) this.ciphers = options.ciphers; |
1239 |
var secureOptions = options.secureOptions || 0; |
1240 |
if (options.honorCipherOrder) {
|
1241 |
secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE; |
1242 |
} |
1243 |
if (secureOptions) this.secureOptions = secureOptions; |
1244 |
if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this); |
1245 |
if (options.SNICallback) {
|
1246 |
this.SNICallback = options.SNICallback;
|
1247 |
} else {
|
1248 |
this.SNICallback = this.SNICallback.bind(this); |
1249 |
} |
1250 |
if (options.sessionIdContext) {
|
1251 |
this.sessionIdContext = options.sessionIdContext;
|
1252 |
} else if (this.requestCert) { |
1253 |
this.sessionIdContext = crypto.createHash('md5') |
1254 |
.update(process.argv.join(' '))
|
1255 |
.digest('hex');
|
1256 |
} |
1257 |
}; |
1258 |
|
1259 |
// SNI Contexts High-Level API
|
1260 |
Server.prototype.addContext = function(servername, credentials) { |
1261 |
if (!servername) {
|
1262 |
throw 'Servername is required parameter for Server.addContext'; |
1263 |
} |
1264 |
|
1265 |
var re = new RegExp('^' + |
1266 |
servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1') |
1267 |
.replace(/\*/g, '.*') + |
1268 |
'$');
|
1269 |
this._contexts.push([re, crypto.createCredentials(credentials).context]);
|
1270 |
}; |
1271 |
|
1272 |
Server.prototype.SNICallback = function(servername) { |
1273 |
var ctx;
|
1274 |
|
1275 |
this._contexts.some(function(elem) { |
1276 |
if (servername.match(elem[0]) !== null) { |
1277 |
ctx = elem[1];
|
1278 |
return true; |
1279 |
} |
1280 |
}); |
1281 |
|
1282 |
return ctx;
|
1283 |
}; |
1284 |
|
1285 |
|
1286 |
// Target API:
|
1287 |
//
|
1288 |
// var s = tls.connect({port: 8000, host: "google.com"}, function() {
|
1289 |
// if (!s.authorized) {
|
1290 |
// s.destroy();
|
1291 |
// return;
|
1292 |
// }
|
1293 |
//
|
1294 |
// // s.socket;
|
1295 |
//
|
1296 |
// s.end("hello world\n");
|
1297 |
// });
|
1298 |
//
|
1299 |
//
|
1300 |
function normalizeConnectArgs(listArgs) { |
1301 |
var args = net._normalizeConnectArgs(listArgs);
|
1302 |
var options = args[0]; |
1303 |
var cb = args[1]; |
1304 |
|
1305 |
if (typeof listArgs[1] === 'object') { |
1306 |
options = util._extend(options, listArgs[1]);
|
1307 |
} else if (typeof listArgs[2] === 'object') { |
1308 |
options = util._extend(options, listArgs[2]);
|
1309 |
} |
1310 |
|
1311 |
return (cb) ? [options, cb] : [options];
|
1312 |
} |
1313 |
|
1314 |
exports.connect = function(/* [port, host], options, cb */) { |
1315 |
var args = normalizeConnectArgs(arguments); |
1316 |
var options = args[0]; |
1317 |
var cb = args[1]; |
1318 |
|
1319 |
var defaults = {
|
1320 |
rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED |
1321 |
}; |
1322 |
options = util._extend(defaults, options || {}); |
1323 |
|
1324 |
var socket = options.socket ? options.socket : new net.Stream(); |
1325 |
|
1326 |
var sslcontext = crypto.createCredentials(options);
|
1327 |
|
1328 |
convertNPNProtocols(options.NPNProtocols, this);
|
1329 |
var hostname = options.servername || options.host || 'localhost', |
1330 |
pair = new SecurePair(sslcontext, false, true, |
1331 |
options.rejectUnauthorized === true ? true : false, |
1332 |
{ |
1333 |
NPNProtocols: this.NPNProtocols, |
1334 |
servername: hostname
|
1335 |
}); |
1336 |
|
1337 |
if (options.session) {
|
1338 |
var session = options.session;
|
1339 |
if (typeof session === 'string') |
1340 |
session = new Buffer(session, 'binary'); |
1341 |
pair.ssl.setSession(session); |
1342 |
} |
1343 |
|
1344 |
var cleartext = pipe(pair, socket);
|
1345 |
if (cb) {
|
1346 |
cleartext.once('secureConnect', cb);
|
1347 |
} |
1348 |
|
1349 |
if (!options.socket) {
|
1350 |
var connect_opt = (options.path && !options.port) ? {path: options.path} : { |
1351 |
port: options.port,
|
1352 |
host: options.host,
|
1353 |
localAddress: options.localAddress
|
1354 |
}; |
1355 |
socket.connect(connect_opt); |
1356 |
} |
1357 |
|
1358 |
pair.on('secure', function() { |
1359 |
var verifyError = pair.ssl.verifyError();
|
1360 |
|
1361 |
cleartext.npnProtocol = pair.npnProtocol; |
1362 |
|
1363 |
// Verify that server's identity matches it's certificate's names
|
1364 |
if (!verifyError) {
|
1365 |
var validCert = checkServerIdentity(hostname,
|
1366 |
pair.cleartext.getPeerCertificate()); |
1367 |
if (!validCert) {
|
1368 |
verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' + |
1369 |
'altnames');
|
1370 |
} |
1371 |
} |
1372 |
|
1373 |
if (verifyError) {
|
1374 |
cleartext.authorized = false;
|
1375 |
cleartext.authorizationError = verifyError.message; |
1376 |
|
1377 |
if (pair._rejectUnauthorized) {
|
1378 |
cleartext.emit('error', verifyError);
|
1379 |
pair.destroy(); |
1380 |
} else {
|
1381 |
cleartext.emit('secureConnect');
|
1382 |
} |
1383 |
} else {
|
1384 |
cleartext.authorized = true;
|
1385 |
cleartext.emit('secureConnect');
|
1386 |
} |
1387 |
}); |
1388 |
pair.on('error', function(err) { |
1389 |
cleartext.emit('error', err);
|
1390 |
}); |
1391 |
|
1392 |
cleartext._controlReleased = true;
|
1393 |
return cleartext;
|
1394 |
}; |
1395 |
|
1396 |
|
1397 |
function pipe(pair, socket) { |
1398 |
pair.encrypted.pipe(socket); |
1399 |
socket.pipe(pair.encrypted); |
1400 |
|
1401 |
pair.fd = socket.fd; |
1402 |
var cleartext = pair.cleartext;
|
1403 |
cleartext.socket = socket; |
1404 |
cleartext.encrypted = pair.encrypted; |
1405 |
cleartext.authorized = false;
|
1406 |
|
1407 |
function onerror(e) { |
1408 |
if (cleartext._controlReleased) {
|
1409 |
cleartext.emit('error', e);
|
1410 |
} |
1411 |
} |
1412 |
|
1413 |
function onclose() { |
1414 |
socket.removeListener('error', onerror);
|
1415 |
socket.removeListener('timeout', ontimeout);
|
1416 |
} |
1417 |
|
1418 |
function ontimeout() { |
1419 |
cleartext.emit('timeout');
|
1420 |
} |
1421 |
|
1422 |
socket.on('error', onerror);
|
1423 |
socket.on('close', onclose);
|
1424 |
socket.on('timeout', ontimeout);
|
1425 |
|
1426 |
return cleartext;
|
1427 |
} |