tls: introduce TLSSocket based on tls_wrap binding

Split `tls.js` into `_tls_legacy.js`, containing legacy
`createSecurePair` API, and `_tls_wrap.js` containing new code based on
`tls_wrap` binding.

Remove tests that are no longer useful/valid.
This commit is contained in:
Fedor Indutny 2013-06-13 15:36:00 +02:00
parent 03e008ddb8
commit af80e7bc6e
15 changed files with 1456 additions and 1765 deletions

View File

@ -49,7 +49,7 @@ server-side resources, which makes it a potential vector for denial-of-service
attacks. attacks.
To mitigate this, renegotiations are limited to three times every 10 minutes. An To mitigate this, renegotiations are limited to three times every 10 minutes. An
error is emitted on the [CleartextStream][] instance when the threshold is error is emitted on the [tls.TLSSocket][] instance when the threshold is
exceeded. The limits are configurable: exceeded. The limits are configurable:
- `tls.CLIENT_RENEG_LIMIT`: renegotiation limit, default is 3. - `tls.CLIENT_RENEG_LIMIT`: renegotiation limit, default is 3.
@ -188,12 +188,12 @@ Here is a simple example echo server:
ca: [ fs.readFileSync('client-cert.pem') ] ca: [ fs.readFileSync('client-cert.pem') ]
}; };
var server = tls.createServer(options, function(cleartextStream) { var server = tls.createServer(options, function(socket) {
console.log('server connected', console.log('server connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized'); socket.authorized ? 'authorized' : 'unauthorized');
cleartextStream.write("welcome!\n"); socket.write("welcome!\n");
cleartextStream.setEncoding('utf8'); socket.setEncoding('utf8');
cleartextStream.pipe(cleartextStream); socket.pipe(socket);
}); });
server.listen(8000, function() { server.listen(8000, function() {
console.log('server bound'); console.log('server bound');
@ -212,12 +212,12 @@ Or
}; };
var server = tls.createServer(options, function(cleartextStream) { var server = tls.createServer(options, function(socket) {
console.log('server connected', console.log('server connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized'); socket.authorized ? 'authorized' : 'unauthorized');
cleartextStream.write("welcome!\n"); socket.write("welcome!\n");
cleartextStream.setEncoding('utf8'); socket.setEncoding('utf8');
cleartextStream.pipe(cleartextStream); socket.pipe(socket);
}); });
server.listen(8000, function() { server.listen(8000, function() {
console.log('server bound'); console.log('server bound');
@ -228,15 +228,6 @@ You can test this server by connecting to it with `openssl s_client`:
openssl s_client -connect 127.0.0.1:8000 openssl s_client -connect 127.0.0.1:8000
## tls.SLAB_BUFFER_SIZE
Size of slab buffer used by all tls servers and clients.
Default: `10 * 1024 * 1024`.
Don't change the defaults unless you know what you are doing.
## tls.connect(options, [callback]) ## tls.connect(options, [callback])
## tls.connect(port, [host], [options], [callback]) ## tls.connect(port, [host], [options], [callback])
@ -285,7 +276,7 @@ Creates a new client connection to the given `port` and `host` (old API) or
The `callback` parameter will be added as a listener for the The `callback` parameter will be added as a listener for the
['secureConnect'][] event. ['secureConnect'][] event.
`tls.connect()` returns a [CleartextStream][] object. `tls.connect()` returns a [tls.TLSSocket][] object.
Here is an example of a client of echo server as described previously: Here is an example of a client of echo server as described previously:
@ -301,17 +292,17 @@ Here is an example of a client of echo server as described previously:
ca: [ fs.readFileSync('server-cert.pem') ] ca: [ fs.readFileSync('server-cert.pem') ]
}; };
var cleartextStream = tls.connect(8000, options, function() { var socket = tls.connect(8000, options, function() {
console.log('client connected', console.log('client connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized'); socket.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(cleartextStream); process.stdin.pipe(socket);
process.stdin.resume(); process.stdin.resume();
}); });
cleartextStream.setEncoding('utf8'); socket.setEncoding('utf8');
cleartextStream.on('data', function(data) { socket.on('data', function(data) {
console.log(data); console.log(data);
}); });
cleartextStream.on('end', function() { socket.on('end', function() {
server.close(); server.close();
}); });
@ -324,22 +315,24 @@ Or
pfx: fs.readFileSync('client.pfx') pfx: fs.readFileSync('client.pfx')
}; };
var cleartextStream = tls.connect(8000, options, function() { var socket = tls.connect(8000, options, function() {
console.log('client connected', console.log('client connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized'); socket.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(cleartextStream); process.stdin.pipe(socket);
process.stdin.resume(); process.stdin.resume();
}); });
cleartextStream.setEncoding('utf8'); socket.setEncoding('utf8');
cleartextStream.on('data', function(data) { socket.on('data', function(data) {
console.log(data); console.log(data);
}); });
cleartextStream.on('end', function() { socket.on('end', function() {
server.close(); server.close();
}); });
## tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized]) ## tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized])
** Deprecated **
Creates a new secure pair object with two streams, one of which reads/writes Creates a new secure pair object with two streams, one of which reads/writes
encrypted data, and one reads/writes cleartext data. encrypted data, and one reads/writes cleartext data.
Generally the encrypted one is piped to/from an incoming encrypted data stream, Generally the encrypted one is piped to/from an incoming encrypted data stream,
@ -357,9 +350,11 @@ and the cleartext one is used as a replacement for the initial encrypted stream.
automatically reject clients with invalid certificates. Only applies to automatically reject clients with invalid certificates. Only applies to
servers with `requestCert` enabled. servers with `requestCert` enabled.
`tls.createSecurePair()` returns a SecurePair object with [cleartext][] and `tls.createSecurePair()` returns a SecurePair object with `cleartext` and
`encrypted` stream properties. `encrypted` stream properties.
NOTE: `cleartext` has the same APIs as [tls.TLSSocket][]
## Class: SecurePair ## Class: SecurePair
Returned by tls.createSecurePair. Returned by tls.createSecurePair.
@ -381,50 +376,31 @@ connections using TLS or SSL.
### Event: 'secureConnection' ### Event: 'secureConnection'
`function (cleartextStream) {}` `function (tlsSocket) {}`
This event is emitted after a new connection has been successfully This event is emitted after a new connection has been successfully
handshaked. The argument is a instance of [CleartextStream][]. It has all the handshaked. The argument is a instance of [tls.TLSSocket][]. It has all the
common stream methods and events. common stream methods and events.
`cleartextStream.authorized` is a boolean value which indicates if the `socket.authorized` is a boolean value which indicates if the
client has verified by one of the supplied certificate authorities for the client has verified by one of the supplied certificate authorities for the
server. If `cleartextStream.authorized` is false, then server. If `socket.authorized` is false, then
`cleartextStream.authorizationError` is set to describe how authorization `socket.authorizationError` is set to describe how authorization
failed. Implied but worth mentioning: depending on the settings of the TLS failed. Implied but worth mentioning: depending on the settings of the TLS
server, you unauthorized connections may be accepted. server, you unauthorized connections may be accepted.
`cleartextStream.npnProtocol` is a string containing selected NPN protocol. `socket.npnProtocol` is a string containing selected NPN protocol.
`cleartextStream.servername` is a string containing servername requested with `socket.servername` is a string containing servername requested with
SNI. SNI.
### Event: 'clientError' ### Event: 'clientError'
`function (exception, securePair) { }` `function (exception, tlsSocket) { }`
When a client connection emits an 'error' event before secure connection is When a client connection emits an 'error' event before secure connection is
established - it will be forwarded here. established - it will be forwarded here.
`securePair` is the `tls.SecurePair` that the error originated from. `tlsSocket` is the [tls.TLSSocket][] that the error originated from.
### Event: 'newSession'
`function (sessionId, sessionData) { }`
Emitted on creation of TLS session. May be used to store sessions in external
storage.
### Event: 'resumeSession'
`function (sessionId, callback) { }`
Emitted when client wants to resume previous TLS session. Event listener may
perform lookup in external storage using given `sessionId`, and invoke
`callback(null, sessionData)` once finished. If session can't be resumed
(i.e. doesn't exist in storage) one may call `callback(null, null)`. Calling
`callback(err)` will terminate incoming connection and destroy socket.
### server.listen(port, [host], [callback]) ### server.listen(port, [host], [callback])
@ -469,6 +445,8 @@ The number of concurrent connections on the server.
## Class: CryptoStream ## Class: CryptoStream
** Deprecated **
This is an encrypted stream. This is an encrypted stream.
### cryptoStream.bytesWritten ### cryptoStream.bytesWritten
@ -476,37 +454,35 @@ This is an encrypted stream.
A proxy to the underlying socket's bytesWritten accessor, this will return A proxy to the underlying socket's bytesWritten accessor, this will return
the total bytes written to the socket, *including the TLS overhead*. the total bytes written to the socket, *including the TLS overhead*.
## Class: tls.CleartextStream ## Class: tls.TLSSocket
This is a stream on top of the *Encrypted* stream that makes it possible to This is a wrapped version of [net.Socket][] that does transparent encryption
read/write an encrypted data as a cleartext data. of written data and all required TLS negotiation.
This instance implements a duplex [Stream][] interfaces. It has all the This instance implements a duplex [Stream][] interfaces. It has all the
common stream methods and events. common stream methods and events.
A ClearTextStream is the `clear` member of a SecurePair object.
### Event: 'secureConnect' ### Event: 'secureConnect'
This event is emitted after a new connection has been successfully handshaked. This event is emitted after a new connection has been successfully handshaked.
The listener will be called no matter if the server's certificate was The listener will be called no matter if the server's certificate was
authorized or not. It is up to the user to test `cleartextStream.authorized` authorized or not. It is up to the user to test `tlsSocket.authorized`
to see if the server certificate was signed by one of the specified CAs. to see if the server certificate was signed by one of the specified CAs.
If `cleartextStream.authorized === false` then the error can be found in If `tlsSocket.authorized === false` then the error can be found in
`cleartextStream.authorizationError`. Also if NPN was used - you can check `tlsSocket.authorizationError`. Also if NPN was used - you can check
`cleartextStream.npnProtocol` for negotiated protocol. `tlsSocket.npnProtocol` for negotiated protocol.
### cleartextStream.authorized ### tlsSocket.authorized
A boolean that is `true` if the peer certificate was signed by one of the A boolean that is `true` if the peer certificate was signed by one of the
specified CAs, otherwise `false` specified CAs, otherwise `false`
### cleartextStream.authorizationError ### tlsSocket.authorizationError
The reason why the peer's certificate has not been verified. This property The reason why the peer's certificate has not been verified. This property
becomes available only when `cleartextStream.authorized === false`. becomes available only when `tlsSocket.authorized === false`.
### cleartextStream.getPeerCertificate() ### tlsSocket.getPeerCertificate()
Returns an object representing the peer's certificate. The returned object has Returns an object representing the peer's certificate. The returned object has
some properties corresponding to the field of the certificate. some properties corresponding to the field of the certificate.
@ -534,7 +510,7 @@ Example:
If the peer does not provide a certificate, it returns `null` or an empty If the peer does not provide a certificate, it returns `null` or an empty
object. object.
### cleartextStream.getCipher() ### tlsSocket.getCipher()
Returns an object representing the cipher name and the SSL/TLS Returns an object representing the cipher name and the SSL/TLS
protocol version of the current connection. protocol version of the current connection.
@ -545,33 +521,33 @@ See SSL_CIPHER_get_name() and SSL_CIPHER_get_version() in
http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS for more http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS for more
information. information.
### cleartextStream.address() ### tlsSocket.address()
Returns the bound address, the address family name and port of the Returns the bound address, the address family name and port of the
underlying socket as reported by the operating system. Returns an underlying socket as reported by the operating system. Returns an
object with three properties, e.g. object with three properties, e.g.
`{ port: 12346, family: 'IPv4', address: '127.0.0.1' }` `{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`
### cleartextStream.remoteAddress ### tlsSocket.remoteAddress
The string representation of the remote IP address. For example, The string representation of the remote IP address. For example,
`'74.125.127.100'` or `'2001:4860:a005::68'`. `'74.125.127.100'` or `'2001:4860:a005::68'`.
### cleartextStream.remotePort ### tlsSocket.remotePort
The numeric representation of the remote port. For example, `443`. The numeric representation of the remote port. For example, `443`.
### cleartextStream.localAddress ### tlsSocket.localAddress
The string representation of the local IP address. The string representation of the local IP address.
### cleartextStream.localPort ### tlsSocket.localPort
The numeric representation of the local port. The numeric representation of the local port.
[OpenSSL cipher list format documentation]: http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT [OpenSSL cipher list format documentation]: http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT
[BEAST attacks]: http://blog.ivanristic.com/2011/10/mitigating-the-beast-attack-on-tls.html [BEAST attacks]: http://blog.ivanristic.com/2011/10/mitigating-the-beast-attack-on-tls.html
[CleartextStream]: #tls_class_tls_cleartextstream [tls.TLSSocket]: #tls_class_tls_tlssocket
[net.Server.address()]: net.html#net_server_address [net.Server.address()]: net.html#net_server_address
['secureConnect']: #tls_event_secureconnect ['secureConnect']: #tls_event_secureconnect
[secureConnection]: #tls_event_secureconnection [secureConnection]: #tls_event_secureconnection

802
lib/_tls_legacy.js Normal file
View File

@ -0,0 +1,802 @@
var assert = require('assert');
var crypto = require('crypto');
var events = require('events');
var stream = require('stream');
var tls = require('tls');
var util = require('util');
var Timer = process.binding('timer_wrap').Timer;
var Connection = null;
try {
Connection = process.binding('crypto').Connection;
} catch (e) {
throw new Error('node.js not compiled with openssl crypto support.');
}
var debug = util.debuglog('tls');
function SlabBuffer() {
this.create();
}
SlabBuffer.prototype.create = function create() {
this.isFull = false;
this.pool = new Buffer(tls.SLAB_BUFFER_SIZE);
this.offset = 0;
this.remaining = this.pool.length;
};
SlabBuffer.prototype.use = function use(context, fn, size) {
if (this.remaining === 0) {
this.isFull = true;
return 0;
}
var actualSize = this.remaining;
if (size !== null) actualSize = Math.min(size, actualSize);
var bytes = fn.call(context, this.pool, this.offset, actualSize);
if (bytes > 0) {
this.offset += bytes;
this.remaining -= bytes;
}
assert(this.remaining >= 0);
return bytes;
};
var slabBuffer = null;
// Base class of both CleartextStream and EncryptedStream
function CryptoStream(pair, options) {
stream.Duplex.call(this, options);
this.pair = pair;
this._pending = null;
this._pendingEncoding = '';
this._pendingCallback = null;
this._doneFlag = false;
this._retryAfterPartial = false;
this._halfRead = false;
this._sslOutCb = null;
this._resumingSession = false;
this._reading = true;
this._destroyed = false;
this._ended = false;
this._finished = false;
this._opposite = null;
if (slabBuffer === null) slabBuffer = new SlabBuffer();
this._buffer = slabBuffer;
this.once('finish', onCryptoStreamFinish);
// net.Socket calls .onend too
this.once('end', onCryptoStreamEnd);
}
util.inherits(CryptoStream, stream.Duplex);
function onCryptoStreamFinish() {
this._finished = true;
if (this === this.pair.cleartext) {
debug('cleartext.onfinish');
if (this.pair.ssl) {
// Generate close notify
// NOTE: first call checks if client has sent us shutdown,
// second call enqueues shutdown into the BIO.
if (this.pair.ssl.shutdown() !== 1) {
if (this.pair.ssl && this.pair.ssl.error)
return this.pair.error();
this.pair.ssl.shutdown();
}
if (this.pair.ssl && this.pair.ssl.error)
return this.pair.error();
}
} else {
debug('encrypted.onfinish');
}
// Try to read just to get sure that we won't miss EOF
if (this._opposite.readable) this._opposite.read(0);
if (this._opposite._ended) {
this._done();
// No half-close, sorry
if (this === this.pair.cleartext) this._opposite._done();
}
}
function onCryptoStreamEnd() {
this._ended = true;
if (this === this.pair.cleartext) {
debug('cleartext.onend');
} else {
debug('encrypted.onend');
}
if (this.onend) this.onend();
}
// NOTE: Called once `this._opposite` is set.
CryptoStream.prototype.init = function init() {
var self = this;
this._opposite.on('sslOutEnd', function() {
if (self._sslOutCb) {
var cb = self._sslOutCb;
self._sslOutCb = null;
cb(null);
}
});
};
CryptoStream.prototype._write = function write(data, encoding, cb) {
assert(this._pending === null);
// Black-hole data
if (!this.pair.ssl) return cb(null);
// When resuming session don't accept any new data.
// And do not put too much data into openssl, before writing it from encrypted
// side.
//
// TODO(indutny): Remove magic number, use watermark based limits
if (!this._resumingSession &&
this._opposite._internallyPendingBytes() < 128 * 1024) {
// Write current buffer now
var written;
if (this === this.pair.cleartext) {
debug('cleartext.write called with %d bytes', data.length);
written = this.pair.ssl.clearIn(data, 0, data.length);
} else {
debug('encrypted.write called with %d bytes', data.length);
written = this.pair.ssl.encIn(data, 0, data.length);
}
// Handle and report errors
if (this.pair.ssl && this.pair.ssl.error) {
return cb(this.pair.error(true));
}
// Force SSL_read call to cycle some states/data inside OpenSSL
this.pair.cleartext.read(0);
// Cycle encrypted data
if (this.pair.encrypted._internallyPendingBytes())
this.pair.encrypted.read(0);
// Get NPN and Server name when ready
this.pair.maybeInitFinished();
// Whole buffer was written
if (written === data.length) {
if (this === this.pair.cleartext) {
debug('cleartext.write succeed with ' + written + ' bytes');
} else {
debug('encrypted.write succeed with ' + written + ' bytes');
}
// Invoke callback only when all data read from opposite stream
if (this._opposite._halfRead) {
assert(this._sslOutCb === null);
this._sslOutCb = cb;
} else {
cb(null);
}
return;
} else if (written !== 0 && written !== -1) {
assert(!this._retryAfterPartial);
this._retryAfterPartial = true;
this._write(data.slice(written), encoding, cb);
this._retryAfterPartial = false;
return;
}
} else {
debug('cleartext.write queue is full');
// Force SSL_read call to cycle some states/data inside OpenSSL
this.pair.cleartext.read(0);
}
// No write has happened
this._pending = data;
this._pendingEncoding = encoding;
this._pendingCallback = cb;
if (this === this.pair.cleartext) {
debug('cleartext.write queued with %d bytes', data.length);
} else {
debug('encrypted.write queued with %d bytes', data.length);
}
};
CryptoStream.prototype._writePending = function writePending() {
var data = this._pending,
encoding = this._pendingEncoding,
cb = this._pendingCallback;
this._pending = null;
this._pendingEncoding = '';
this._pendingCallback = null;
this._write(data, encoding, cb);
};
CryptoStream.prototype._read = function read(size) {
// XXX: EOF?!
if (!this.pair.ssl) return this.push(null);
// Wait for session to be resumed
// Mark that we're done reading, but don't provide data or EOF
if (this._resumingSession || !this._reading) return this.push('');
var out;
if (this === this.pair.cleartext) {
debug('cleartext.read called with %d bytes', size);
out = this.pair.ssl.clearOut;
} else {
debug('encrypted.read called with %d bytes', size);
out = this.pair.ssl.encOut;
}
var bytesRead = 0,
start = this._buffer.offset;
do {
var read = this._buffer.use(this.pair.ssl, out, size);
if (read > 0) {
bytesRead += read;
size -= read;
}
// Handle and report errors
if (this.pair.ssl && this.pair.ssl.error) {
this.pair.error();
break;
}
// Get NPN and Server name when ready
this.pair.maybeInitFinished();
} while (read > 0 && !this._buffer.isFull && bytesRead < size);
// Create new buffer if previous was filled up
var pool = this._buffer.pool;
if (this._buffer.isFull) this._buffer.create();
assert(bytesRead >= 0);
if (this === this.pair.cleartext) {
debug('cleartext.read succeed with %d bytes', bytesRead);
} else {
debug('encrypted.read succeed with %d bytes', bytesRead);
}
// Try writing pending data
if (this._pending !== null) this._writePending();
if (this._opposite._pending !== null) this._opposite._writePending();
if (bytesRead === 0) {
// EOF when cleartext has finished and we have nothing to read
if (this._opposite._finished && this._internallyPendingBytes() === 0) {
// Perform graceful shutdown
this._done();
// No half-open, sorry!
if (this === this.pair.cleartext)
this._opposite._done();
// EOF
this.push(null);
} else {
// Bail out
this.push('');
}
} else {
// Give them requested data
if (this.ondata) {
var self = this;
this.ondata(pool, start, start + bytesRead);
// Consume data automatically
// simple/test-https-drain fails without it
process.nextTick(function() {
self.read(bytesRead);
});
}
this.push(pool.slice(start, start + bytesRead));
}
// Let users know that we've some internal data to read
var halfRead = this._internallyPendingBytes() !== 0;
// Smart check to avoid invoking 'sslOutEnd' in the most of the cases
if (this._halfRead !== halfRead) {
this._halfRead = halfRead;
// Notify listeners about internal data end
if (!halfRead) {
if (this === this.pair.cleartext) {
debug('cleartext.sslOutEnd');
} else {
debug('encrypted.sslOutEnd');
}
this.emit('sslOutEnd');
}
}
};
CryptoStream.prototype.setTimeout = function(timeout, callback) {
if (this.socket) this.socket.setTimeout(timeout, callback);
};
CryptoStream.prototype.setNoDelay = function(noDelay) {
if (this.socket) this.socket.setNoDelay(noDelay);
};
CryptoStream.prototype.setKeepAlive = function(enable, initialDelay) {
if (this.socket) this.socket.setKeepAlive(enable, initialDelay);
};
CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
return this.socket ? this.socket.bytesWritten : 0;
});
CryptoStream.prototype.getPeerCertificate = function() {
if (this.pair.ssl) {
var c = this.pair.ssl.getPeerCertificate();
if (c) {
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
if (c.subject) c.subject = tls.parseCertString(c.subject);
return c;
}
}
return null;
};
CryptoStream.prototype.getSession = function() {
if (this.pair.ssl) {
return this.pair.ssl.getSession();
}
return null;
};
CryptoStream.prototype.isSessionReused = function() {
if (this.pair.ssl) {
return this.pair.ssl.isSessionReused();
}
return null;
};
CryptoStream.prototype.getCipher = function(err) {
if (this.pair.ssl) {
return this.pair.ssl.getCurrentCipher();
} else {
return null;
}
};
CryptoStream.prototype.end = function(chunk, encoding) {
if (this === this.pair.cleartext) {
debug('cleartext.end');
} else {
debug('encrypted.end');
}
// Write pending data first
if (this._pending !== null) this._writePending();
this.writable = false;
stream.Duplex.prototype.end.call(this, chunk, encoding);
};
CryptoStream.prototype.destroySoon = function(err) {
if (this === this.pair.cleartext) {
debug('cleartext.destroySoon');
} else {
debug('encrypted.destroySoon');
}
if (this.writable)
this.end();
if (this._writableState.finished && this._opposite._ended) {
this.destroy();
} else {
// Wait for both `finish` and `end` events to ensure that all data that
// was written on this side was read from the other side.
var self = this;
var waiting = 2;
function finish() {
if (--waiting === 0) self.destroy();
}
this._opposite.once('end', finish);
this.once('finish', finish);
}
};
CryptoStream.prototype.destroy = function(err) {
if (this._destroyed) return;
this._destroyed = true;
this.readable = this.writable = false;
// Destroy both ends
if (this === this.pair.cleartext) {
debug('cleartext.destroy');
} else {
debug('encrypted.destroy');
}
this._opposite.destroy();
var self = this;
process.nextTick(function() {
// Force EOF
self.push(null);
// Emit 'close' event
self.emit('close', err ? true : false);
});
};
CryptoStream.prototype._done = function() {
this._doneFlag = true;
if (this === this.pair.encrypted && !this.pair._secureEstablished)
return this.pair.error();
if (this.pair.cleartext._doneFlag &&
this.pair.encrypted._doneFlag &&
!this.pair._doneFlag) {
// If both streams are done:
this.pair.destroy();
}
};
// readyState is deprecated. Don't use it.
Object.defineProperty(CryptoStream.prototype, 'readyState', {
get: function() {
if (this._connecting) {
return 'opening';
} else if (this.readable && this.writable) {
return 'open';
} else if (this.readable && !this.writable) {
return 'readOnly';
} else if (!this.readable && this.writable) {
return 'writeOnly';
} else {
return 'closed';
}
}
});
function CleartextStream(pair, options) {
CryptoStream.call(this, pair, options);
// This is a fake kludge to support how the http impl sits
// on top of net Sockets
var self = this;
this._handle = {
readStop: function() {
self._reading = false;
},
readStart: function() {
if (self._reading && self._readableState.length > 0) return;
self._reading = true;
self.read(0);
if (self._opposite.readable) self._opposite.read(0);
}
};
}
util.inherits(CleartextStream, CryptoStream);
CleartextStream.prototype._internallyPendingBytes = function() {
if (this.pair.ssl) {
return this.pair.ssl.clearPending();
} else {
return 0;
}
};
CleartextStream.prototype.address = function() {
return this.socket && this.socket.address();
};
CleartextStream.prototype.__defineGetter__('remoteAddress', function() {
return this.socket && this.socket.remoteAddress;
});
CleartextStream.prototype.__defineGetter__('remotePort', function() {
return this.socket && this.socket.remotePort;
});
CleartextStream.prototype.__defineGetter__('localAddress', function() {
return this.socket && this.socket.localAddress;
});
CleartextStream.prototype.__defineGetter__('localPort', function() {
return this.socket && this.socket.localPort;
});
function EncryptedStream(pair, options) {
CryptoStream.call(this, pair, options);
}
util.inherits(EncryptedStream, CryptoStream);
EncryptedStream.prototype._internallyPendingBytes = function() {
if (this.pair.ssl) {
return this.pair.ssl.encPending();
} else {
return 0;
}
};
function onhandshakestart() {
debug('onhandshakestart');
var self = this;
var ssl = self.ssl;
var now = Timer.now();
assert(now >= ssl.lastHandshakeTime);
if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
ssl.handshakes = 0;
}
var first = (ssl.lastHandshakeTime === 0);
ssl.lastHandshakeTime = now;
if (first) return;
if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) {
// Defer the error event to the next tick. We're being called from OpenSSL's
// state machine and OpenSSL is not re-entrant. We cannot allow the user's
// callback to destroy the connection right now, it would crash and burn.
setImmediate(function() {
var err = new Error('TLS session renegotiation attack detected.');
if (self.cleartext) self.cleartext.emit('error', err);
});
}
}
function onhandshakedone() {
// for future use
debug('onhandshakedone');
}
function onclienthello(hello) {
var self = this,
once = false;
this._resumingSession = true;
function callback(err, session) {
if (once) return;
once = true;
if (err) return self.socket.destroy(err);
self.ssl.loadSession(session);
// Cycle data
self._resumingSession = false;
self.cleartext.read(0);
self.encrypted.read(0);
}
if (hello.sessionId.length <= 0 ||
!this.server ||
!this.server.emit('resumeSession', hello.sessionId, callback)) {
callback(null, null);
}
}
function onnewsession(key, session) {
if (!this.server) return;
this.server.emit('newSession', key, session);
}
/**
* Provides a pair of streams to do encrypted communication.
*/
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
options) {
if (!(this instanceof SecurePair)) {
return new SecurePair(credentials,
isServer,
requestCert,
rejectUnauthorized,
options);
}
var self = this;
options || (options = {});
events.EventEmitter.call(this);
this.server = options.server;
this._secureEstablished = false;
this._isServer = isServer ? true : false;
this._encWriteState = true;
this._clearWriteState = true;
this._doneFlag = false;
this._destroying = false;
if (!credentials) {
this.credentials = crypto.createCredentials();
} else {
this.credentials = credentials;
}
if (!this._isServer) {
// For clients, we will always have either a given ca list or be using
// default one
requestCert = true;
}
this._rejectUnauthorized = rejectUnauthorized ? true : false;
this._requestCert = requestCert ? true : false;
this.ssl = new Connection(this.credentials.context,
this._isServer ? true : false,
this._isServer ? this._requestCert :
options.servername,
this._rejectUnauthorized);
if (this._isServer) {
this.ssl.onhandshakestart = onhandshakestart.bind(this);
this.ssl.onhandshakedone = onhandshakedone.bind(this);
this.ssl.onclienthello = onclienthello.bind(this);
this.ssl.onnewsession = onnewsession.bind(this);
this.ssl.lastHandshakeTime = 0;
this.ssl.handshakes = 0;
}
if (process.features.tls_sni) {
if (this._isServer && options.SNICallback) {
this.ssl.setSNICallback(options.SNICallback);
}
this.servername = null;
}
if (process.features.tls_npn && options.NPNProtocols) {
this.ssl.setNPNProtocols(options.NPNProtocols);
this.npnProtocol = null;
}
/* Acts as a r/w stream to the cleartext side of the stream. */
this.cleartext = new CleartextStream(this, options.cleartext);
/* Acts as a r/w stream to the encrypted side of the stream. */
this.encrypted = new EncryptedStream(this, options.encrypted);
/* Let streams know about each other */
this.cleartext._opposite = this.encrypted;
this.encrypted._opposite = this.cleartext;
this.cleartext.init();
this.encrypted.init();
process.nextTick(function() {
/* The Connection may be destroyed by an abort call */
if (self.ssl) {
self.ssl.start();
}
});
}
util.inherits(SecurePair, events.EventEmitter);
exports.createSecurePair = function(credentials,
isServer,
requestCert,
rejectUnauthorized) {
var pair = new SecurePair(credentials,
isServer,
requestCert,
rejectUnauthorized);
return pair;
};
SecurePair.prototype.maybeInitFinished = function() {
if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) {
if (process.features.tls_npn) {
this.npnProtocol = this.ssl.getNegotiatedProtocol();
}
if (process.features.tls_sni) {
this.servername = this.ssl.getServername();
}
this._secureEstablished = true;
debug('secure established');
this.emit('secure');
}
};
SecurePair.prototype.destroy = function() {
if (this._destroying) return;
if (!this._doneFlag) {
debug('SecurePair.destroy');
this._destroying = true;
// SecurePair should be destroyed only after it's streams
this.cleartext.destroy();
this.encrypted.destroy();
this._doneFlag = true;
this.ssl.error = null;
this.ssl.close();
this.ssl = null;
}
};
SecurePair.prototype.error = function(returnOnly) {
var err = this.ssl.error;
this.ssl.error = null;
if (!this._secureEstablished) {
// Emit ECONNRESET instead of zero return
if (!err || err.message === 'ZERO_RETURN') {
var connReset = new Error('socket hang up');
connReset.code = 'ECONNRESET';
connReset.sslError = err && err.message;
err = connReset;
}
this.destroy();
if (!returnOnly) this.emit('error', err);
} else if (this._isServer &&
this._rejectUnauthorized &&
/peer did not return a certificate/.test(err.message)) {
// Not really an error.
this.destroy();
} else {
if (!returnOnly) this.cleartext.emit('error', err);
}
return err;
};

571
lib/_tls_wrap.js Normal file
View File

@ -0,0 +1,571 @@
var assert = require('assert');
var constants = require('constants');
var crypto = require('crypto');
var events = require('events');
var net = require('net');
var tls = require('tls');
var util = require('util');
var Timer = process.binding('timer_wrap').Timer;
var tls_wrap = process.binding('tls_wrap');
var debug = util.debuglog('tls');
function onhandshakestart() {
debug('onhandshakestart');
var self = this;
var ssl = self.ssl;
var now = Timer.now();
assert(now >= ssl.lastHandshakeTime);
if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
ssl.handshakes = 0;
}
var first = (ssl.lastHandshakeTime === 0);
ssl.lastHandshakeTime = now;
if (first) return;
if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) {
// Defer the error event to the next tick. We're being called from OpenSSL's
// state machine and OpenSSL is not re-entrant. We cannot allow the user's
// callback to destroy the connection right now, it would crash and burn.
setImmediate(function() {
var err = new Error('TLS session renegotiation attack detected.');
self._tlsError(err);
});
}
}
function onhandshakedone() {
// for future use
debug('onhandshakedone');
this._finishInit();
}
/**
* Provides a wrap of socket stream to do encrypted communication.
*/
function TLSSocket(socket, options) {
net.Socket.call(this, socket && {
handle: socket._handle,
allowHalfOpen: socket.allowHalfOpen,
readable: socket.readable,
writable: socket.writable
});
var self = this;
this._tlsOptions = options;
this._secureEstablished = false;
this._controlReleased = false;
this.ssl = null;
this.servername = null;
this.npnProtocol = null;
this.authorized = false;
this.authorizationError = null;
if (!this._handle)
this.once('connect', this._init.bind(this));
else
this._init();
}
util.inherits(TLSSocket, net.Socket);
TLSSocket.prototype._init = function() {
assert(this._handle);
// lib/net.js expect this value to be non-zero if write hasn't been flushed
// immediately
// TODO(indutny): rewise this solution, it might be 1 before handshake and
// repersent real writeQueueSize during regular writes.
this._handle.writeQueueSize = 1;
var self = this;
var options = this._tlsOptions;
// Wrap socket's handle
var credentials = options.credentials || crypto.createCredentials();
this.ssl = tls_wrap.wrap(this._handle, credentials.context, options.isServer);
// For clients, we will always have either a given ca list or be using
// default one
var requestCert = !!options.requestCert || !options.isServer,
rejectUnauthorized = !!options.rejectUnauthorized;
if (requestCert || rejectUnauthorized)
this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
if (options.isServer) {
this.ssl.onhandshakestart = onhandshakestart.bind(this);
this.ssl.onhandshakedone = onhandshakedone.bind(this);
this.ssl.lastHandshakeTime = 0;
this.ssl.handshakes = 0;
} else {
this.ssl.onhandshakestart = function() {};
this.ssl.onhandshakedone = this._finishInit.bind(this);
}
this.ssl.onerror = function(err) {
// Destroy socket if error happened before handshake's finish
if (!this._secureEstablished) {
self._tlsError(err);
self.destroy();
} else if (options.isServer &&
rejectUnauthorized &&
/peer did not return a certificate/.test(err.message)) {
// Ignore server's authorization errors
self.destroy();
} else {
// Throw error
self._tlsError(err);
}
};
if (process.features.tls_sni &&
options.isServer &&
options.SNICallback && options.server._contexts.length) {
this.ssl.onsniselect = options.SNICallback;
}
if (process.features.tls_npn && options.NPNProtocols)
this.ssl.setNPNProtocols(options.NPNProtocols);
};
TLSSocket.prototype._tlsError = function(err) {
this.emit('_tlsError', err);
if (this._controlReleased)
this.emit('error', err);
};
TLSSocket.prototype._finishInit = function() {
if (process.features.tls_npn) {
this.npnProtocol = this.ssl.getNegotiatedProtocol();
}
if (process.features.tls_sni && this._tlsOptions.isServer) {
this.servername = this.ssl.getServername();
}
debug('secure established');
this._secureEstablished = true;
this.emit('secure');
};
TLSSocket.prototype._start = function() {
this.ssl.start();
};
TLSSocket.prototype.setServername = function(name) {
this.ssl.setServername(name);
};
TLSSocket.prototype.setSession = function(session) {
if (typeof session === 'string')
session = new Buffer(session, 'binary');
this.ssl.setSession(session);
};
TLSSocket.prototype.getPeerCertificate = function() {
if (this.ssl) {
var c = this.ssl.getPeerCertificate();
if (c) {
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
if (c.subject) c.subject = tls.parseCertString(c.subject);
return c;
}
}
return null;
};
TLSSocket.prototype.getSession = function() {
if (this.ssl) {
return this.ssl.getSession();
}
return null;
};
TLSSocket.prototype.isSessionReused = function() {
if (this.ssl) {
return this.ssl.isSessionReused();
}
return null;
};
TLSSocket.prototype.getCipher = function(err) {
if (this.ssl) {
return this.ssl.getCurrentCipher();
} else {
return null;
}
};
// TODO: support anonymous (nocert) and PSK
// AUTHENTICATION MODES
//
// There are several levels of authentication that TLS/SSL supports.
// Read more about this in "man SSL_set_verify".
//
// 1. The server sends a certificate to the client but does not request a
// cert from the client. This is common for most HTTPS servers. The browser
// can verify the identity of the server, but the server does not know who
// the client is. Authenticating the client is usually done over HTTP using
// login boxes and cookies and stuff.
//
// 2. The server sends a cert to the client and requests that the client
// also send it a cert. The client knows who the server is and the server is
// requesting the client also identify themselves. There are several
// outcomes:
//
// A) verifyError returns null meaning the client's certificate is signed
// by one of the server's CAs. The server know's the client idenity now
// and the client is authorized.
//
// B) For some reason the client's certificate is not acceptable -
// verifyError returns a string indicating the problem. The server can
// either (i) reject the client or (ii) allow the client to connect as an
// unauthorized connection.
//
// The mode is controlled by two boolean variables.
//
// requestCert
// If true the server requests a certificate from client connections. For
// the common HTTPS case, users will want this to be false, which is what
// it defaults to.
//
// rejectUnauthorized
// If true clients whose certificates are invalid for any reason will not
// be allowed to make connections. If false, they will simply be marked as
// unauthorized but secure communication will continue. By default this is
// true.
//
//
//
// Options:
// - requestCert. Send verify request. Default to false.
// - rejectUnauthorized. Boolean, default to true.
// - key. string.
// - cert: string.
// - ca: string or array of strings.
// - sessionTimeout: integer.
//
// emit 'secureConnection'
// function (tlsSocket) { }
//
// "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
// "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
// "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
// "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
// "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
// "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
// "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
// "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
// "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
// "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
// "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
// "CERT_REJECTED"
//
function Server(/* [options], listener */) {
var options, listener;
if (typeof arguments[0] == 'object') {
options = arguments[0];
listener = arguments[1];
} else if (typeof arguments[0] == 'function') {
options = {};
listener = arguments[0];
}
if (!(this instanceof Server)) return new Server(options, listener);
this._contexts = [];
var self = this;
// Handle option defaults:
this.setOptions(options);
if (!self.pfx && (!self.cert || !self.key)) {
throw new Error('Missing PFX or certificate + private key.');
}
var sharedCreds = crypto.createCredentials({
pfx: self.pfx,
key: self.key,
passphrase: self.passphrase,
cert: self.cert,
ca: self.ca,
ciphers: self.ciphers || tls.DEFAULT_CIPHERS,
secureProtocol: self.secureProtocol,
secureOptions: self.secureOptions,
crl: self.crl,
sessionIdContext: self.sessionIdContext
});
var timeout = options.handshakeTimeout || (120 * 1000);
if (typeof timeout !== 'number') {
throw new TypeError('handshakeTimeout must be a number');
}
if (self.sessionTimeout) {
sharedCreds.context.setSessionTimeout(self.sessionTimeout);
}
// constructor call
net.Server.call(this, function(raw_socket) {
var socket = new TLSSocket(raw_socket, {
credentials: sharedCreds,
isServer: true,
server: self,
requestCert: self.requestCert,
rejectUnauthorized: self.rejectUnauthorized,
NPNProtocols: self.NPNProtocols,
SNICallback: self.SNICallback
});
function listener() {
socket._tlsError(new Error('TLS handshake timeout'));
}
if (timeout > 0) {
socket.setTimeout(timeout, listener);
}
socket.once('secure', function() {
socket.setTimeout(0, listener);
if (self.requestCert) {
var verifyError = socket.ssl.verifyError();
if (verifyError) {
socket.authorizationError = verifyError.message;
if (self.rejectUnauthorized)
socket.destroy();
} else {
socket.authorized = true;
}
}
if (!socket.destroyed) {
socket._controlReleased = true;
self.emit('secureConnection', socket);
}
});
socket.on('_tlsError', function(err) {
if (!socket._controlReleased)
self.emit('clientError', err, socket);
});
});
if (listener) {
this.on('secureConnection', listener);
}
}
util.inherits(Server, net.Server);
exports.Server = Server;
exports.createServer = function(options, listener) {
return new Server(options, listener);
};
Server.prototype.setOptions = function(options) {
if (typeof options.requestCert == 'boolean') {
this.requestCert = options.requestCert;
} else {
this.requestCert = false;
}
if (typeof options.rejectUnauthorized == 'boolean') {
this.rejectUnauthorized = options.rejectUnauthorized;
} else {
this.rejectUnauthorized = false;
}
if (options.pfx) this.pfx = options.pfx;
if (options.key) this.key = options.key;
if (options.passphrase) this.passphrase = options.passphrase;
if (options.cert) this.cert = options.cert;
if (options.ca) this.ca = options.ca;
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
if (options.crl) this.crl = options.crl;
if (options.ciphers) this.ciphers = options.ciphers;
if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;
var secureOptions = options.secureOptions || 0;
if (options.honorCipherOrder) {
secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE;
}
if (secureOptions) this.secureOptions = secureOptions;
if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this);
if (options.SNICallback) {
this.SNICallback = options.SNICallback;
} else {
this.SNICallback = this.SNICallback.bind(this);
}
if (options.sessionIdContext) {
this.sessionIdContext = options.sessionIdContext;
} else if (this.requestCert) {
this.sessionIdContext = crypto.createHash('md5')
.update(process.argv.join(' '))
.digest('hex');
}
};
// SNI Contexts High-Level API
Server.prototype.addContext = function(servername, credentials) {
if (!servername) {
throw 'Servername is required parameter for Server.addContext';
}
var re = new RegExp('^' +
servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1')
.replace(/\*/g, '.*') +
'$');
this._contexts.push([re, crypto.createCredentials(credentials).context]);
};
Server.prototype.SNICallback = function(servername) {
var ctx;
this._contexts.some(function(elem) {
if (servername.match(elem[0]) !== null) {
ctx = elem[1];
return true;
}
});
return ctx;
};
// Target API:
//
// var s = tls.connect({port: 8000, host: "google.com"}, function() {
// if (!s.authorized) {
// s.destroy();
// return;
// }
//
// // s.socket;
//
// s.end("hello world\n");
// });
//
//
function normalizeConnectArgs(listArgs) {
var args = net._normalizeConnectArgs(listArgs);
var options = args[0];
var cb = args[1];
if (typeof listArgs[1] === 'object') {
options = util._extend(options, listArgs[1]);
} else if (typeof listArgs[2] === 'object') {
options = util._extend(options, listArgs[2]);
}
return (cb) ? [options, cb] : [options];
}
exports.connect = function(/* [port, host], options, cb */) {
var args = normalizeConnectArgs(arguments);
var options = args[0];
var cb = args[1];
var defaults = {
rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED
};
options = util._extend(defaults, options || {});
var hostname = options.servername || options.host || 'localhost',
NPN = {};
tls.convertNPNProtocols(options.NPNProtocols, NPN);
var socket = new TLSSocket(options.socket, {
credentials: crypto.createCredentials(options),
isServer: false,
requestCert: true,
rejectUnauthorized: options.rejectUnauthorized,
NPNProtocols: NPN.NPNProtocols
});
function onHandle() {
socket._controlReleased = true;
if (options.session)
socket.setSession(options.session);
if (options.servername)
socket.setServername(options.servername);
socket._start();
socket.on('secure', function() {
var verifyError = socket.ssl.verifyError();
// Verify that server's identity matches it's certificate's names
if (!verifyError) {
var validCert = tls.checkServerIdentity(hostname,
socket.getPeerCertificate());
if (!validCert) {
verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' +
'altnames');
}
}
if (verifyError) {
socket.authorizationError = verifyError.message;
if (options.rejectUnauthorized) {
socket.emit('error', verifyError);
socket.destroy();
return;
} else {
socket.emit('secureConnect');
}
} else {
socket.authorized = true;
socket.emit('secureConnect');
}
// Uncork incoming data
socket.removeListener('end', onHangUp);
});
function onHangUp() {
var error = new Error('socket hang up');
error.code = 'ECONNRESET';
socket.destroy();
socket.emit('error', error);
}
socket.once('end', onHangUp);
}
if (socket._handle)
onHandle();
else
socket.once('connect', onHandle);
if (cb)
socket.once('secureConnect', cb);
if (!options.socket) {
var connect_opt = (options.path && !options.port) ? {path: options.path} : {
port: options.port,
host: options.host,
localAddress: options.localAddress
};
socket.connect(connect_opt);
}
return socket;
};

View File

@ -41,7 +41,7 @@ function Server(opts, requestListener) {
} }
this.addListener('clientError', function(err, conn) { this.addListener('clientError', function(err, conn) {
conn.destroy(err); conn.destroy();
}); });
this.timeout = 2 * 60 * 1000; this.timeout = 2 * 60 * 1000;

1269
lib/tls.js

File diff suppressed because it is too large Load Diff

View File

@ -59,6 +59,8 @@
'lib/sys.js', 'lib/sys.js',
'lib/timers.js', 'lib/timers.js',
'lib/tls.js', 'lib/tls.js',
'lib/_tls_legacy.js',
'lib/_tls_wrap.js',
'lib/tty.js', 'lib/tty.js',
'lib/url.js', 'lib/url.js',
'lib/util.js', 'lib/util.js',

View File

@ -37,7 +37,7 @@ testURL.rejectUnauthorized = false;
function check(request) { function check(request) {
// assert that I'm https // assert that I'm https
assert.ok(request.socket.encrypted); assert.ok(request.socket._secureEstablished);
} }
var server = https.createServer(httpsOptions, function(request, response) { var server = https.createServer(httpsOptions, function(request, response) {

View File

@ -86,7 +86,7 @@ server.listen(common.PORT, function() {
bodyBuffer += s; bodyBuffer += s;
}); });
res.on('close', function() { res.on('end', function() {
console.log('5) Client got "end" event.'); console.log('5) Client got "end" event.');
gotEnd = true; gotEnd = true;
}); });

View File

@ -35,8 +35,8 @@ var options = {
}; };
var server = https.createServer(options, function (req, res) { var server = https.createServer(options, function (req, res) {
console.log("Connect from: " + req.connection.socket.remoteAddress); console.log("Connect from: " + req.connection.remoteAddress);
assert.equal('127.0.0.2', req.connection.socket.remoteAddress); assert.equal('127.0.0.2', req.connection.remoteAddress);
req.on('end', function() { req.on('end', function() {
res.writeHead(200, { 'Content-Type': 'text/plain' }); res.writeHead(200, { 'Content-Type': 'text/plain' });

View File

@ -47,8 +47,6 @@ server.on('clientError', function(err, conn) {
// the cleartext object ever changes. We're checking that the https.Server // the cleartext object ever changes. We're checking that the https.Server
// has closed the client connection. // has closed the client connection.
assert.equal(conn._secureEstablished, false); assert.equal(conn._secureEstablished, false);
assert.equal(conn._doneFlag, true);
assert.equal(conn.ssl, null);
server.close(); server.close();
clientErrors++; clientErrors++;
}); });

View File

@ -1,68 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
if (!process.versions.openssl) {
console.error('Skipping because node compiled without OpenSSL.');
process.exit(0);
}
var common = require('../common');
var common = require('../common');
var tls = require('tls');
var fs = require('fs');
var assert = require('assert');
var options = {
key: fs.readFileSync(common.fixturesDir + '/test_key.pem'),
cert: fs.readFileSync(common.fixturesDir + '/test_cert.pem')
};
var gotError = 0,
gotRequest = 0,
connected = 0;
var server = tls.createServer(options, function(c) {
gotRequest++;
c.on('data', function(data) {
console.log(data.toString());
});
c.on('close', function() {
server.close();
});
}).listen(common.PORT, function() {
var c = tls.connect(common.PORT, { rejectUnauthorized: false }, function() {
connected++;
c.pair.ssl.shutdown();
c.write('123');
c.destroy();
});
c.once('error', function() {
gotError++;
});
});
process.once('exit', function() {
assert.equal(gotError, 1);
assert.equal(gotRequest, 1);
assert.equal(connected, 1);
});

View File

@ -1,103 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
if (!process.versions.openssl) {
console.error('Skipping because node compiled without OpenSSL.');
process.exit(0);
}
var common = require('../common');
var assert = require('assert');
var tls = require('tls');
var fs = require('fs');
var path = require('path');
var serverClosed = false;
var serverSocketClosed = false;
var clientClosed = false;
var clientSocketClosed = false;
var options = {
key: fs.readFileSync(path.join(common.fixturesDir, 'test_key.pem')),
cert: fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem'))
};
var server = tls.createServer(options, function(s) {
console.log('server connected');
s.socket.on('end', function() {
console.log('server socket ended');
});
s.socket.on('close', function() {
console.log('server socket closed');
serverSocketClosed = true;
});
s.on('end', function() {
console.log('server ended');
});
s.on('close', function() {
console.log('server closed');
serverClosed = true;
});
s.pause();
console.log('server paused');
process.nextTick(function() {
s.resume();
console.log('server resumed');
});
s.end();
});
server.listen(common.PORT, function() {
var c = tls.connect({
port: common.PORT,
rejectUnauthorized: false
}, function() {
console.log('client connected');
c.socket.on('end', function() {
console.log('client socket ended');
});
c.socket.on('close', function() {
console.log('client socket closed');
clientSocketClosed = true;
});
c.pause();
console.log('client paused');
process.nextTick(function() {
c.resume();
console.log('client resumed');
});
});
c.on('end', function() {
console.log('client ended');
});
c.on('close', function() {
console.log('client closed');
clientClosed = true;
server.close();
});
});
process.on('exit', function() {
assert(serverClosed);
assert(serverSocketClosed);
assert(clientClosed);
assert(clientSocketClosed);
});

View File

@ -1,68 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
if (!process.versions.openssl) {
console.error('Skipping because node compiled without OpenSSL.');
process.exit(0);
}
var common = require('../common');
var assert = require('assert');
var tls = require('tls');
var fs = require('fs');
var path = require('path');
var options = {
key: fs.readFileSync(path.join(common.fixturesDir, 'test_key.pem')),
cert: fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem'))
};
var server = tls.Server(options, function(s) {
assert.equal(s.address().address, s.socket.address().address);
assert.equal(s.address().port, s.socket.address().port);
assert.equal(s.remoteAddress, s.socket.remoteAddress);
assert.equal(s.remotePort, s.socket.remotePort);
assert.equal(s.localAddress, s.socket.localAddress);
assert.equal(s.localPort, s.socket.localPort);
s.end();
});
server.listen(common.PORT, '127.0.0.1', function() {
assert.equal(server.address().address, '127.0.0.1');
assert.equal(server.address().port, common.PORT);
var c = tls.connect({
host: '127.0.0.1',
port: common.PORT,
rejectUnauthorized: false
}, function() {
assert.equal(c.address().address, c.socket.address().address);
assert.equal(c.address().port, c.socket.address().port);
assert.equal(c.remoteAddress, '127.0.0.1');
assert.equal(c.remotePort, common.PORT);
});
c.on('end', function() {
server.close();
});
});

View File

@ -1,66 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var tls = require('tls');
var fs = require('fs');
var clientConnected = 0;
var serverConnected = 0;
var options = {
key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem')
};
tls.SLAB_BUFFER_SIZE = 100 * 1024;
var server = tls.Server(options, function(socket) {
assert(socket._buffer.pool.length == tls.SLAB_BUFFER_SIZE);
if (++serverConnected === 2) {
server.close();
}
});
server.listen(common.PORT, function() {
var client1 = tls.connect({
port: common.PORT,
rejectUnauthorized: false
}, function() {
++clientConnected;
client1.end();
});
var client2 = tls.connect({
port: common.PORT,
rejectUnauthorized: false
});
client2.on('secureConnect', function() {
++clientConnected;
client2.end();
});
});
process.on('exit', function() {
assert.equal(clientConnected, 2);
assert.equal(serverConnected, 2);
});

View File

@ -1,118 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
if (!process.versions.openssl) {
console.error('Skipping because node compiled without OpenSSL.');
process.exit(0);
}
require('child_process').exec('openssl version', function(err) {
if (err !== null) {
console.error('Skipping because openssl command is not available.');
process.exit(0);
}
doTest();
});
function doTest() {
var common = require('../common');
var assert = require('assert');
var tls = require('tls');
var fs = require('fs');
var join = require('path').join;
var spawn = require('child_process').spawn;
var keyFile = join(common.fixturesDir, 'agent.key');
var certFile = join(common.fixturesDir, 'agent.crt');
var key = fs.readFileSync(keyFile);
var cert = fs.readFileSync(certFile);
var options = {
key: key,
cert: cert,
ca: [cert],
requestCert: true
};
var requestCount = 0;
var session;
var badOpenSSL = false;
var server = tls.createServer(options, function(cleartext) {
cleartext.on('error', function(er) {
// We're ok with getting ECONNRESET in this test, but it's
// timing-dependent, and thus unreliable. Any other errors
// are just failures, though.
if (er.code !== 'ECONNRESET')
throw er;
});
++requestCount;
cleartext.end();
});
server.on('newSession', function(id, data) {
assert.ok(!session);
session = {
id: id,
data: data
};
});
server.on('resumeSession', function(id, callback) {
assert.ok(session);
assert.equal(session.id.toString('hex'), id.toString('hex'));
// Just to check that async really works there
setTimeout(function() {
callback(null, session.data);
}, 100);
});
server.listen(common.PORT, function() {
var client = spawn('openssl', [
's_client',
'-connect', 'localhost:' + common.PORT,
'-key', join(common.fixturesDir, 'agent.key'),
'-cert', join(common.fixturesDir, 'agent.crt'),
'-reconnect',
'-no_ticket'
], {
stdio: [ 0, 1, 'pipe' ]
});
var err = '';
client.stderr.setEncoding('utf8');
client.stderr.on('data', function(chunk) {
err += chunk;
});
client.on('exit', function(code) {
if (/^unknown option/.test(err)) {
// using an incompatible version of openssl
assert(code);
badOpenSSL = true;
} else
assert.equal(code, 0);
server.close();
});
});
process.on('exit', function() {
if (!badOpenSSL) {
assert.ok(session);
// initial request + reconnect requests (5 times)
assert.equal(requestCount, 6);
}
});
}