Browse Source

Server must not request cert.

v0.7.4-release
Ryan Dahl 14 years ago
parent
commit
5bca100afe
  1. 53
      lib/securepair.js
  2. 82
      lib/tls.js
  3. 20
      src/node_crypto.cc
  4. 6
      test/disabled/test-tls-server.js

53
lib/securepair.js

@ -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

@ -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 {
this.requestCert = false;
}
if (typeof options.rejectUnauthorized == 'boolean') {
this.rejectUnauthorized = options.rejectUnauthorized;
} else { } else {
this.unauthorizedPeers = false; this.rejectUnauthorized = false;
} }
if (options.key) this.key = options.key; if (options.key) this.key = options.key;

20
src/node_crypto.cc

@ -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_);

6
test/disabled/test-tls-server.js

@ -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…
Cancel
Save