Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
OpenSSL NPN in node.js
Browse files Browse the repository at this point in the history
closes #926.
  • Loading branch information
indutny authored and ry committed Apr 19, 2011
1 parent 9e6498d commit c9b40da
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 4 deletions.
10 changes: 10 additions & 0 deletions lib/https.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ var tls = require('tls');
var http = require('http');
var inherits = require('util').inherits;

var NPN_ENABLED = process.binding('constants').NPN_ENABLED;

function Server(opts, requestListener) {
if (!(this instanceof Server)) return new Server(opts, requestListener);

if (NPN_ENABLED && !opts.NPNProtocols) {
opts.NPNProtocols = ['http/1.1', 'http/1.0'];
}

tls.Server.call(this, opts, http._connectionListener);

this.httpAllowHalfOpen = false;
Expand Down Expand Up @@ -58,6 +64,10 @@ Agent.prototype.defaultPort = 443;


Agent.prototype._getConnection = function(host, port, cb) {
if (NPN_ENABLED && !this.options.NPNProtocols) {
this.options.NPNProtocols = ['http/1.1', 'http/1.0'];
}

var s = tls.connect(port, host, this.options, function() {
// do other checks here?
if (cb) cb();
Expand Down
53 changes: 49 additions & 4 deletions lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ var stream = require('stream');
var END_OF_FILE = 42;
var assert = require('assert').ok;

var NPN_ENABLED = process.binding('constants').NPN_ENABLED;

var debug;
if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
debug = function(a) { console.error('TLS:', a); };
Expand All @@ -38,10 +40,36 @@ if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
var Connection = null;
try {
Connection = process.binding('crypto').Connection;
exports.NPN_ENABLED = NPN_ENABLED;
} catch (e) {
throw new Error('node.js not compiled with openssl crypto support.');
}

// Convert protocols array into valid OpenSSL protocols list
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
function convertNPNProtocols(NPNProtocols, out) {
// If NPNProtocols is Array - translate it into buffer
if (Array.isArray(NPNProtocols)) {
var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
return p + 1 + Buffer.byteLength(c);
}, 0));

NPNProtocols.reduce(function(offset, c) {
var clen = Buffer.byteLength(c);
buff[offset] = clen;
buff.write(c, offset + 1);

return offset + 1 + clen;
}, 0);

NPNProtocols = buff;
}

// If it's already a Buffer - store it
if (Buffer.isBuffer(NPNProtocols)) {
out.NPNProtocols = NPNProtocols;
}
};

// Base class of both CleartextStream and EncryptedStream
function CryptoStream(pair) {
Expand Down Expand Up @@ -437,12 +465,14 @@ EncryptedStream.prototype._pusher = function(pool, offset, length) {
* Provides a pair of streams to do encrypted communication.
*/

function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) {
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
NPNProtocols) {
if (!(this instanceof SecurePair)) {
return new SecurePair(credentials,
isServer,
requestCert,
rejectUnauthorized);
rejectUnauthorized,
NPNProtocols);
}

var self = this;
Expand Down Expand Up @@ -478,6 +508,10 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) {
this._requestCert,
this._rejectUnauthorized);

if (NPN_ENABLED && NPNProtocols) {
this._ssl.setNPNProtocols(NPNProtocols);
this.npnProtocol = null;
}

/* Acts as a r/w stream to the cleartext side of the stream. */
this.cleartext = new CleartextStream(this);
Expand Down Expand Up @@ -588,6 +622,10 @@ SecurePair.prototype._cycle = function(depth) {

SecurePair.prototype._maybeInitFinished = function() {
if (this._ssl && !this._secureEstablished && this._ssl.isInitFinished()) {
if (NPN_ENABLED) {
this.npnProtocol = this._ssl.getNegotiatedProtocol();
}

this._secureEstablished = true;
debug('secure established');
this.emit('secure');
Expand Down Expand Up @@ -745,13 +783,15 @@ function Server(/* [options], listener */) {
var pair = new SecurePair(creds,
true,
self.requestCert,
self.rejectUnauthorized);
self.rejectUnauthorized,
self.NPNProtocols);

var cleartext = pipe(pair, socket);
cleartext._controlReleased = false;

pair.on('secure', function() {
pair.cleartext.authorized = false;
pair.cleartext.npnProtocol = pair.npnProtocol;
if (!self.requestCert) {
cleartext._controlReleased = true;
self.emit('secureConnection', pair.cleartext, pair.encrypted);
Expand Down Expand Up @@ -812,6 +852,7 @@ Server.prototype.setOptions = function(options) {
if (options.ciphers) this.ciphers = options.ciphers;
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
if (options.secureOptions) this.secureOptions = options.secureOptions;
if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this);
};


Expand Down Expand Up @@ -854,7 +895,9 @@ exports.connect = function(port /* host, options, cb */) {
var sslcontext = crypto.createCredentials(options);
//sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');

var pair = new SecurePair(sslcontext, false);
convertNPNProtocols(options.NPNProtocols, this);
var pair = new SecurePair(sslcontext, false, true, false,
this.NPNProtocols);

var cleartext = pipe(pair, socket);

Expand All @@ -863,6 +906,8 @@ exports.connect = function(port /* host, options, cb */) {
pair.on('secure', function() {
var verifyError = pair._ssl.verifyError();

cleartext.npnProtocol = pair.npnProtocol;

if (verifyError) {
cleartext.authorized = false;
cleartext.authorizationError = verifyError;
Expand Down
5 changes: 5 additions & 0 deletions src/node_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,11 @@ void DefineConstants(Handle<Object> target) {
#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
NODE_DEFINE_CONSTANT(target, SSL_OP_CRYPTOPRO_TLSEXT_BUG);
#endif

#ifdef OPENSSL_NPN_NEGOTIATED
#define NPN_ENABLED 1
NODE_DEFINE_CONSTANT(target, NPN_ENABLED);
#endif
}

} // namespace node
134 changes: 134 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,11 @@ void Connection::Initialize(Handle<Object> target) {
NODE_SET_PROTOTYPE_METHOD(t, "receivedShutdown", Connection::ReceivedShutdown);
NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close);

#ifdef OPENSSL_NPN_NEGOTIATED
NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", Connection::GetNegotiatedProto);
NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", Connection::SetNPNProtocols);
#endif

target->Set(String::NewSymbol("Connection"), t->GetFunction());
}

Expand Down Expand Up @@ -614,6 +619,76 @@ static int VerifyCallback(int preverify_ok, X509_STORE_CTX *ctx) {
return 1;
}

#ifdef OPENSSL_NPN_NEGOTIATED

int Connection::AdvertiseNextProtoCallback_(SSL *s,
const unsigned char **data,
unsigned int *len,
void *arg) {

Connection *p = static_cast<Connection*>(SSL_get_app_data(s));

if (p->npnProtos_.IsEmpty()) {
// No initialization - no NPN protocols
*data = reinterpret_cast<const unsigned char*>("");
*len = 0;
} else {
*data = reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_));
*len = Buffer::Length(p->npnProtos_);
}

return SSL_TLSEXT_ERR_OK;
}

int Connection::SelectNextProtoCallback_(SSL *s,
unsigned char **out, unsigned char *outlen,
const unsigned char* in,
unsigned int inlen, void *arg) {
Connection *p = static_cast<Connection*> SSL_get_app_data(s);

// Release old protocol handler if present
if (!p->selectedNPNProto_.IsEmpty()) {
p->selectedNPNProto_.Dispose();
}

if (p->npnProtos_.IsEmpty()) {
// We should at least select one protocol
// If server is using NPN
*out = reinterpret_cast<unsigned char*>(const_cast<char*>("http/1.1"));
*outlen = 8;

// set status unsupported
p->selectedNPNProto_ = Persistent<Value>::New(False());

return SSL_TLSEXT_ERR_OK;
}

const unsigned char* npnProtos =
reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_));

int status = SSL_select_next_proto(out, outlen, in, inlen, npnProtos,
Buffer::Length(p->npnProtos_));

switch (status) {
case OPENSSL_NPN_UNSUPPORTED:
p->selectedNPNProto_ = Persistent<Value>::New(Null());
break;
case OPENSSL_NPN_NEGOTIATED:
p->selectedNPNProto_ = Persistent<Value>::New(String::New(
reinterpret_cast<const char*>(*out), *outlen
));
break;
case OPENSSL_NPN_NO_OVERLAP:
p->selectedNPNProto_ = Persistent<Value>::New(False());
break;
default:
break;
}

return SSL_TLSEXT_ERR_OK;
}
#endif


Handle<Value> Connection::New(const Arguments& args) {
HandleScope scope;
Expand All @@ -633,6 +708,23 @@ Handle<Value> Connection::New(const Arguments& args) {
p->ssl_ = SSL_new(sc->ctx_);
p->bio_read_ = BIO_new(BIO_s_mem());
p->bio_write_ = BIO_new(BIO_s_mem());

#ifdef OPENSSL_NPN_NEGOTIATED
SSL_set_app_data(p->ssl_, p);
if (is_server) {
// Server should advertise NPN protocols
SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
AdvertiseNextProtoCallback_,
NULL);
} else {
// Client should select protocol from advertised
// If server supports NPN
SSL_CTX_set_next_proto_select_cb(sc->ctx_,
SelectNextProtoCallback_,
NULL);
}
#endif

SSL_set_bio(p->ssl_, p->bio_read_, p->bio_write_);

#ifdef SSL_MODE_RELEASE_BUFFERS
Expand Down Expand Up @@ -1184,6 +1276,48 @@ Handle<Value> Connection::Close(const Arguments& args) {
return True();
}

#ifdef OPENSSL_NPN_NEGOTIATED
Handle<Value> Connection::GetNegotiatedProto(const Arguments& args) {
HandleScope scope;

Connection *ss = Connection::Unwrap(args);

if (ss->is_server_) {
const unsigned char *npn_proto;
unsigned int npn_proto_len;

SSL_get0_next_proto_negotiated(ss->ssl_, &npn_proto, &npn_proto_len);

if (!npn_proto) {
return False();
}

return String::New((const char*) npn_proto, npn_proto_len);
} else {
return ss->selectedNPNProto_;
}
}

Handle<Value> Connection::SetNPNProtocols(const Arguments& args) {
HandleScope scope;

Connection *ss = Connection::Unwrap(args);

if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
return ThrowException(Exception::Error(String::New(
"Must give a Buffer as first argument")));
}

// Release old handle
if (!ss->npnProtos_.IsEmpty()) {
ss->npnProtos_.Dispose();
}
ss->npnProtos_ = Persistent<Object>::New(args[0]->ToObject());

return True();
};
#endif


static void HexEncode(unsigned char *md_value,
int md_len,
Expand Down
25 changes: 25 additions & 0 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#define SRC_NODE_CRYPTO_H_

#include <node.h>

#include <node_object_wrap.h>
#include <v8.h>

Expand All @@ -33,6 +34,10 @@
#include <openssl/x509.h>
#include <openssl/hmac.h>

#ifdef OPENSSL_NPN_NEGOTIATED
#include <node_buffer.h>
#endif

#define EVP_F_EVP_DECRYPTFINAL 101


Expand Down Expand Up @@ -94,6 +99,11 @@ class Connection : ObjectWrap {
public:
static void Initialize(v8::Handle<v8::Object> target);

#ifdef OPENSSL_NPN_NEGOTIATED
v8::Persistent<v8::Object> npnProtos_;
v8::Persistent<v8::Value> selectedNPNProto_;
#endif

protected:
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> EncIn(const v8::Arguments& args);
Expand All @@ -111,6 +121,20 @@ class Connection : ObjectWrap {
static v8::Handle<v8::Value> Start(const v8::Arguments& args);
static v8::Handle<v8::Value> Close(const v8::Arguments& args);

#ifdef OPENSSL_NPN_NEGOTIATED
// NPN
static v8::Handle<v8::Value> GetNegotiatedProto(const v8::Arguments& args);
static v8::Handle<v8::Value> SetNPNProtocols(const v8::Arguments& args);
static int AdvertiseNextProtoCallback_(SSL *s,
const unsigned char **data,
unsigned int *len,
void *arg);
static int SelectNextProtoCallback_(SSL *s,
unsigned char **out, unsigned char *outlen,
const unsigned char* in,
unsigned int inlen, void *arg);
#endif

int HandleBIOError(BIO *bio, const char* func, int rv);
int HandleSSLError(const char* func, int rv);

Expand Down Expand Up @@ -139,6 +163,7 @@ class Connection : ObjectWrap {
BIO *bio_read_;
BIO *bio_write_;
SSL *ssl_;

bool is_server_; /* coverity[member_decl] */
};

Expand Down
Loading

0 comments on commit c9b40da

Please sign in to comment.