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

[npn] OpenSSL NPN in node.js #926

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -429,12 +457,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 @@ -470,6 +500,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 @@ -576,6 +610,10 @@ SecurePair.prototype._cycle = function(depth) {

SecurePair.prototype._maybeInitFinished = function() {
if (this._ssl && !this._secureEstablished && this._ssl.isInitFinished()) {
if (NPN_ENABLED) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we'll have a conditions preprocessor for lib/*.js files, it can be fully excluded from tls.js builds on systems with default openssl lib

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't want to go crazy with the preprocessing. we only remove debug() and assert() - a simple conditional isn't bad.

this.npnProtocol = this._ssl.getNegotiatedProtocol();
}

this._secureEstablished = true;
debug('secure established');
this.emit('secure');
Expand Down Expand Up @@ -733,13 +771,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 @@ -800,6 +840,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 @@ -842,7 +883,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 @@ -851,6 +894,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