Server must not request cert.
This commit is contained in:
parent
093dfaf801
commit
5bca100afe
@ -20,9 +20,9 @@ var SecureStream = null;
|
|||||||
* Provides a pair of streams to do encrypted communication.
|
* Provides a pair of streams to do encrypted communication.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function SecurePair(credentials, isServer, shouldVerifyPeer) {
|
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) {
|
||||||
if (!(this instanceof SecurePair)) {
|
if (!(this instanceof SecurePair)) {
|
||||||
return new SecurePair(credentials, isServer, shouldVerifyPeer);
|
return new SecurePair(credentials, isServer, requestCert, rejectUnauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -53,16 +53,20 @@ function SecurePair(credentials, isServer, shouldVerifyPeer) {
|
|||||||
if (!this._isServer) {
|
if (!this._isServer) {
|
||||||
// For clients, we will always have either a given ca list or be using
|
// For clients, we will always have either a given ca list or be using
|
||||||
// default one
|
// default one
|
||||||
shouldVerifyPeer = true;
|
requestCert = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._secureEstablished = false;
|
this._secureEstablished = false;
|
||||||
this._encInPending = [];
|
this._encInPending = [];
|
||||||
this._clearInPending = [];
|
this._clearInPending = [];
|
||||||
|
|
||||||
|
this._rejectUnauthorized = rejectUnauthorized ? true : false;
|
||||||
|
this._requestCert = requestCert ? true : false;
|
||||||
|
|
||||||
this._ssl = new SecureStream(this.credentials.context,
|
this._ssl = new SecureStream(this.credentials.context,
|
||||||
this._isServer ? true : false,
|
this._isServer ? true : false,
|
||||||
shouldVerifyPeer ? true : false);
|
this._requestCert,
|
||||||
|
this._rejectUnauthorized);
|
||||||
|
|
||||||
|
|
||||||
/* Acts as a r/w stream to the cleartext side of the stream. */
|
/* Acts as a r/w stream to the cleartext side of the stream. */
|
||||||
@ -144,20 +148,6 @@ function SecurePair(credentials, isServer, shouldVerifyPeer) {
|
|||||||
self._destroy();
|
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() {
|
this.cleartext.on('drain', function() {
|
||||||
debug('source drain');
|
debug('source drain');
|
||||||
self._cycle();
|
self._cycle();
|
||||||
@ -179,8 +169,14 @@ function SecurePair(credentials, isServer, shouldVerifyPeer) {
|
|||||||
util.inherits(SecurePair, events.EventEmitter);
|
util.inherits(SecurePair, events.EventEmitter);
|
||||||
|
|
||||||
|
|
||||||
exports.createSecurePair = function(credentials, isServer, shouldVerifyPeer) {
|
exports.createSecurePair = function(credentials,
|
||||||
var pair = new SecurePair(credentials, isServer, shouldVerifyPeer);
|
isServer,
|
||||||
|
requestCert,
|
||||||
|
rejectUnauthorized) {
|
||||||
|
var pair = new SecurePair(credentials,
|
||||||
|
isServer,
|
||||||
|
requestCert,
|
||||||
|
rejectUnauthorized);
|
||||||
return pair;
|
return pair;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -330,16 +326,20 @@ SecurePair.prototype._cycle = function() {
|
|||||||
mover(
|
mover(
|
||||||
function(pool, offset, length) {
|
function(pool, offset, length) {
|
||||||
debug('reading from encOut');
|
debug('reading from encOut');
|
||||||
|
if (!self._ssl) return -1;
|
||||||
return self._ssl.encOut(pool, offset, length);
|
return self._ssl.encOut(pool, offset, length);
|
||||||
},
|
},
|
||||||
function(chunk) {
|
function(chunk) {
|
||||||
self.encrypted.emit('data', chunk);
|
self.encrypted.emit('data', chunk);
|
||||||
},
|
},
|
||||||
function(bytesRead) {
|
function(bytesRead) {
|
||||||
|
if (!self._ssl) return false;
|
||||||
return bytesRead > 0 && self._encryptedWriteState === true;
|
return bytesRead > 0 && self._encryptedWriteState === true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this._secureEstablished && this._ssl.isInitFinished()) {
|
|
||||||
|
|
||||||
|
if (this._ssl && !this._secureEstablished && this._ssl.isInitFinished()) {
|
||||||
this._secureEstablished = true;
|
this._secureEstablished = true;
|
||||||
debug('secure established');
|
debug('secure established');
|
||||||
this.emit('secure');
|
this.emit('secure');
|
||||||
@ -353,13 +353,22 @@ SecurePair.prototype._destroy = function(err) {
|
|||||||
this._done = true;
|
this._done = true;
|
||||||
this._ssl.close();
|
this._ssl.close();
|
||||||
this._ssl = null;
|
this._ssl = null;
|
||||||
|
this.encrypted.emit('close');
|
||||||
|
this.cleartext.emit('close');
|
||||||
this.emit('end', err);
|
this.emit('end', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
SecurePair.prototype._error = function(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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
82
lib/tls.js
82
lib/tls.js
@ -1,14 +1,58 @@
|
|||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
|
var securepair = require('securepair');
|
||||||
var net = require('net');
|
var net = require('net');
|
||||||
var events = require('events');
|
var events = require('events');
|
||||||
var inherits = require('util').inherits;
|
var inherits = require('util').inherits;
|
||||||
|
|
||||||
|
var assert = process.assert;
|
||||||
|
|
||||||
// TODO: support anonymous (nocert) and PSK
|
// TODO: support anonymous (nocert) and PSK
|
||||||
// TODO: how to proxy maxConnections?
|
// 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:
|
// Options:
|
||||||
// - unauthorizedPeers. Boolean, default to false.
|
// - requestCert. Send verify request. Default to false.
|
||||||
|
// - rejectUnauthorized. Boolean, default to false.
|
||||||
// - key. string.
|
// - key. string.
|
||||||
// - cert: string.
|
// - cert: string.
|
||||||
// - ca: string or array of strings.
|
// - ca: string or array of strings.
|
||||||
@ -56,24 +100,23 @@ function Server(/* [options], listener */) {
|
|||||||
{ key: self.key, cert: self.cert, ca: self.ca });
|
{ key: self.key, cert: self.cert, ca: self.ca });
|
||||||
creds.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
|
creds.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
|
||||||
|
|
||||||
var pair = crypto.createPair(creds,
|
var pair = securepair.createSecurePair(creds,
|
||||||
true,
|
true,
|
||||||
!self.unauthorizedPeers);
|
self.requestCert,
|
||||||
|
self.rejectUnauthorized);
|
||||||
pair.encrypted.pipe(socket);
|
pair.encrypted.pipe(socket);
|
||||||
socket.pipe(pair.encrypted);
|
socket.pipe(pair.encrypted);
|
||||||
|
|
||||||
pair.on('secure', function() {
|
pair.on('secure', function(verifyError) {
|
||||||
var verifyError = pair._ssl.verifyError();
|
if (!self.requestCert) {
|
||||||
|
self.emit('unauthorized', pair.cleartext);
|
||||||
if (verifyError) {
|
} else {
|
||||||
if (self.unauthorizedPeers) {
|
var verifyError = pair._ssl.verifyError();
|
||||||
|
if (verifyError) {
|
||||||
self.emit('unauthorized', pair.cleartext, verifyError);
|
self.emit('unauthorized', pair.cleartext, verifyError);
|
||||||
} else {
|
} else {
|
||||||
console.error('REJECT PEER. verify error: %s', verifyError);
|
self.emit('authorized', pair.cleartext);
|
||||||
socket.destroy();
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.emit('authorized', pair.cleartext);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,7 +140,6 @@ function Server(/* [options], listener */) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle option defaults:
|
// Handle option defaults:
|
||||||
|
|
||||||
this.setOptions(options);
|
this.setOptions(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,10 +151,16 @@ exports.createServer = function(options, listener) {
|
|||||||
|
|
||||||
|
|
||||||
Server.prototype.setOptions = function(options) {
|
Server.prototype.setOptions = function(options) {
|
||||||
if (typeof options.unauthorizedPeers == 'boolean') {
|
if (typeof options.requestCert == 'boolean') {
|
||||||
this.unauthorizedPeers = options.unauthorizedPeers;
|
this.requestCert = options.requestCert;
|
||||||
} else {
|
} else {
|
||||||
this.unauthorizedPeers = false;
|
this.requestCert = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.rejectUnauthorized == 'boolean') {
|
||||||
|
this.rejectUnauthorized = options.rejectUnauthorized;
|
||||||
|
} else {
|
||||||
|
this.rejectUnauthorized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.key) this.key = options.key;
|
if (options.key) this.key = options.key;
|
||||||
|
@ -389,8 +389,26 @@ Handle<Value> SecureStream::New(const Arguments& args) {
|
|||||||
SSL_set_mode(p->ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
|
SSL_set_mode(p->ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
|
||||||
#endif
|
#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.
|
// 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)) {
|
if ((p->is_server_ = is_server)) {
|
||||||
SSL_set_accept_state(p->ssl_);
|
SSL_set_accept_state(p->ssl_);
|
||||||
|
@ -13,7 +13,11 @@ var join = require('path').join;
|
|||||||
var key = fs.readFileSync(join(common.fixturesDir, 'agent.key')).toString();
|
var key = fs.readFileSync(join(common.fixturesDir, 'agent.key')).toString();
|
||||||
var cert = fs.readFileSync(join(common.fixturesDir, 'agent.crt')).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() {
|
s.listen(common.PORT, function() {
|
||||||
console.log('TLS server on 127.0.0.1:%d', common.PORT);
|
console.log('TLS server on 127.0.0.1:%d', common.PORT);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user