* Provides a pair of streams to do encrypted communication.
*/
-function SecurePair(credentials, isServer, shouldVerifyPeer) {
+function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) {
if (!(this instanceof SecurePair)) {
- return new SecurePair(credentials, isServer, shouldVerifyPeer);
+ return new SecurePair(credentials, isServer, requestCert, rejectUnauthorized);
}
var self = this;
if (!this._isServer) {
// For clients, we will always have either a given ca list or be using
// default one
- shouldVerifyPeer = true;
+ requestCert = true;
}
this._secureEstablished = false;
this._encInPending = [];
this._clearInPending = [];
+ this._rejectUnauthorized = rejectUnauthorized ? true : false;
+ this._requestCert = requestCert ? true : false;
+
this._ssl = new SecureStream(this.credentials.context,
this._isServer ? true : false,
- shouldVerifyPeer ? true : false);
+ this._requestCert,
+ this._rejectUnauthorized);
/* Acts as a r/w stream to the cleartext side of the stream. */
self._destroy();
});
- this.encrypted.on('end', function() {
- if (!self._done) {
- self._error(
- new Error('Encrypted stream ended before secure pair was done'));
- }
- });
-
- this.encrypted.on('close', function() {
- if (!self._done) {
- self._error(
- new Error('Encrypted stream closed before secure pair was done'));
- }
- });
-
this.cleartext.on('drain', function() {
debug('source drain');
self._cycle();
util.inherits(SecurePair, events.EventEmitter);
-exports.createSecurePair = function(credentials, isServer, shouldVerifyPeer) {
- var pair = new SecurePair(credentials, isServer, shouldVerifyPeer);
+exports.createSecurePair = function(credentials,
+ isServer,
+ requestCert,
+ rejectUnauthorized) {
+ var pair = new SecurePair(credentials,
+ isServer,
+ requestCert,
+ rejectUnauthorized);
return pair;
};
mover(
function(pool, offset, length) {
debug('reading from encOut');
+ if (!self._ssl) return -1;
return self._ssl.encOut(pool, offset, length);
},
function(chunk) {
self.encrypted.emit('data', chunk);
},
function(bytesRead) {
+ if (!self._ssl) return false;
return bytesRead > 0 && self._encryptedWriteState === true;
});
- if (!this._secureEstablished && this._ssl.isInitFinished()) {
+
+
+ if (this._ssl && !this._secureEstablished && this._ssl.isInitFinished()) {
this._secureEstablished = true;
debug('secure established');
this.emit('secure');
this._done = true;
this._ssl.close();
this._ssl = null;
+ this.encrypted.emit('close');
+ this.cleartext.emit('close');
this.emit('end', err);
}
};
SecurePair.prototype._error = function(err) {
- this.emit('error', err);
+ if (this._isServer &&
+ this._rejectUnauthorized &&
+ /peer did not return a certificate/.test(err.message)) {
+ // Not really an error.
+ this._destroy();
+ } else {
+ this.emit('error', err);
+ }
};
var crypto = require('crypto');
+var securepair = require('securepair');
var net = require('net');
var events = require('events');
var inherits = require('util').inherits;
+var assert = process.assert;
+
// TODO: support anonymous (nocert) and PSK
// TODO: how to proxy maxConnections?
+// 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
+// false.
+//
+//
+//
// Options:
-// - unauthorizedPeers. Boolean, default to false.
+// - requestCert. Send verify request. Default to false.
+// - rejectUnauthorized. Boolean, default to false.
// - key. string.
// - cert: string.
// - ca: string or array of strings.
{ key: self.key, cert: self.cert, ca: self.ca });
creds.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
- var pair = crypto.createPair(creds,
- true,
- !self.unauthorizedPeers);
+ var pair = securepair.createSecurePair(creds,
+ true,
+ self.requestCert,
+ self.rejectUnauthorized);
pair.encrypted.pipe(socket);
socket.pipe(pair.encrypted);
- pair.on('secure', function() {
- var verifyError = pair._ssl.verifyError();
-
- if (verifyError) {
- if (self.unauthorizedPeers) {
+ pair.on('secure', function(verifyError) {
+ if (!self.requestCert) {
+ self.emit('unauthorized', pair.cleartext);
+ } else {
+ var verifyError = pair._ssl.verifyError();
+ if (verifyError) {
self.emit('unauthorized', pair.cleartext, verifyError);
} else {
- console.error('REJECT PEER. verify error: %s', verifyError);
- socket.destroy();
+ self.emit('authorized', pair.cleartext);
}
- } else {
- self.emit('authorized', pair.cleartext);
}
});
}
// Handle option defaults:
-
this.setOptions(options);
}
Server.prototype.setOptions = function(options) {
- if (typeof options.unauthorizedPeers == 'boolean') {
- this.unauthorizedPeers = options.unauthorizedPeers;
+ if (typeof options.requestCert == 'boolean') {
+ this.requestCert = options.requestCert;
+ } else {
+ this.requestCert = false;
+ }
+
+ if (typeof options.rejectUnauthorized == 'boolean') {
+ this.rejectUnauthorized = options.rejectUnauthorized;
} else {
- this.unauthorizedPeers = false;
+ this.rejectUnauthorized = false;
}
if (options.key) this.key = options.key;
SSL_set_mode(p->ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
#endif
+
+ int verify_mode;
+ if (is_server) {
+ bool request_cert = args[2]->BooleanValue();
+ if (!request_cert) {
+ // Note reject_unauthorized ignored.
+ verify_mode = SSL_VERIFY_NONE;
+ } else {
+ bool reject_unauthorized = args[3]->BooleanValue();
+ verify_mode = SSL_VERIFY_PEER;
+ if (reject_unauthorized) verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ }
+ } else {
+ // Note request_cert and reject_unauthorized are ignored for clients.
+ verify_mode = SSL_VERIFY_NONE;
+ }
+
+
// Always allow a connection. We'll reject in javascript.
- SSL_set_verify(p->ssl_, SSL_VERIFY_PEER, VerifyCallback);
+ SSL_set_verify(p->ssl_, verify_mode, VerifyCallback);
if ((p->is_server_ = is_server)) {
SSL_set_accept_state(p->ssl_);
var key = fs.readFileSync(join(common.fixturesDir, 'agent.key')).toString();
var cert = fs.readFileSync(join(common.fixturesDir, 'agent.crt')).toString();
-s = tls.Server({key: key, cert: cert, unauthorizedPeers: false});
+s = tls.Server({ key: key,
+ cert: cert,
+ ca: [],
+ requestCert: true,
+ rejectUnauthorized: true });
s.listen(common.PORT, function() {
console.log('TLS server on 127.0.0.1:%d', common.PORT);