Skip to content

Commit c9b40da

Browse files
indutnyry
authored andcommitted
OpenSSL NPN in node.js
closes nodejs#926.
1 parent 9e6498d commit c9b40da

File tree

6 files changed

+324
-4
lines changed

6 files changed

+324
-4
lines changed

lib/https.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@ var tls = require('tls');
2323
var http = require('http');
2424
var inherits = require('util').inherits;
2525

26+
var NPN_ENABLED = process.binding('constants').NPN_ENABLED;
2627

2728
function Server(opts, requestListener) {
2829
if (!(this instanceof Server)) return new Server(opts, requestListener);
30+
31+
if (NPN_ENABLED && !opts.NPNProtocols) {
32+
opts.NPNProtocols = ['http/1.1', 'http/1.0'];
33+
}
34+
2935
tls.Server.call(this, opts, http._connectionListener);
3036

3137
this.httpAllowHalfOpen = false;
@@ -58,6 +64,10 @@ Agent.prototype.defaultPort = 443;
5864

5965

6066
Agent.prototype._getConnection = function(host, port, cb) {
67+
if (NPN_ENABLED && !this.options.NPNProtocols) {
68+
this.options.NPNProtocols = ['http/1.1', 'http/1.0'];
69+
}
70+
6171
var s = tls.connect(port, host, this.options, function() {
6272
// do other checks here?
6373
if (cb) cb();

lib/tls.js

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var stream = require('stream');
2727
var END_OF_FILE = 42;
2828
var assert = require('assert').ok;
2929

30+
var NPN_ENABLED = process.binding('constants').NPN_ENABLED;
31+
3032
var debug;
3133
if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
3234
debug = function(a) { console.error('TLS:', a); };
@@ -38,10 +40,36 @@ if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
3840
var Connection = null;
3941
try {
4042
Connection = process.binding('crypto').Connection;
43+
exports.NPN_ENABLED = NPN_ENABLED;
4144
} catch (e) {
4245
throw new Error('node.js not compiled with openssl crypto support.');
4346
}
4447

48+
// Convert protocols array into valid OpenSSL protocols list
49+
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
50+
function convertNPNProtocols(NPNProtocols, out) {
51+
// If NPNProtocols is Array - translate it into buffer
52+
if (Array.isArray(NPNProtocols)) {
53+
var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
54+
return p + 1 + Buffer.byteLength(c);
55+
}, 0));
56+
57+
NPNProtocols.reduce(function(offset, c) {
58+
var clen = Buffer.byteLength(c);
59+
buff[offset] = clen;
60+
buff.write(c, offset + 1);
61+
62+
return offset + 1 + clen;
63+
}, 0);
64+
65+
NPNProtocols = buff;
66+
}
67+
68+
// If it's already a Buffer - store it
69+
if (Buffer.isBuffer(NPNProtocols)) {
70+
out.NPNProtocols = NPNProtocols;
71+
}
72+
};
4573

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

440-
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) {
468+
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
469+
NPNProtocols) {
441470
if (!(this instanceof SecurePair)) {
442471
return new SecurePair(credentials,
443472
isServer,
444473
requestCert,
445-
rejectUnauthorized);
474+
rejectUnauthorized,
475+
NPNProtocols);
446476
}
447477

448478
var self = this;
@@ -478,6 +508,10 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) {
478508
this._requestCert,
479509
this._rejectUnauthorized);
480510

511+
if (NPN_ENABLED && NPNProtocols) {
512+
this._ssl.setNPNProtocols(NPNProtocols);
513+
this.npnProtocol = null;
514+
}
481515

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

589623
SecurePair.prototype._maybeInitFinished = function() {
590624
if (this._ssl && !this._secureEstablished && this._ssl.isInitFinished()) {
625+
if (NPN_ENABLED) {
626+
this.npnProtocol = this._ssl.getNegotiatedProtocol();
627+
}
628+
591629
this._secureEstablished = true;
592630
debug('secure established');
593631
this.emit('secure');
@@ -745,13 +783,15 @@ function Server(/* [options], listener */) {
745783
var pair = new SecurePair(creds,
746784
true,
747785
self.requestCert,
748-
self.rejectUnauthorized);
786+
self.rejectUnauthorized,
787+
self.NPNProtocols);
749788

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

753792
pair.on('secure', function() {
754793
pair.cleartext.authorized = false;
794+
pair.cleartext.npnProtocol = pair.npnProtocol;
755795
if (!self.requestCert) {
756796
cleartext._controlReleased = true;
757797
self.emit('secureConnection', pair.cleartext, pair.encrypted);
@@ -812,6 +852,7 @@ Server.prototype.setOptions = function(options) {
812852
if (options.ciphers) this.ciphers = options.ciphers;
813853
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
814854
if (options.secureOptions) this.secureOptions = options.secureOptions;
855+
if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this);
815856
};
816857

817858

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

857-
var pair = new SecurePair(sslcontext, false);
898+
convertNPNProtocols(options.NPNProtocols, this);
899+
var pair = new SecurePair(sslcontext, false, true, false,
900+
this.NPNProtocols);
858901

859902
var cleartext = pipe(pair, socket);
860903

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

909+
cleartext.npnProtocol = pair.npnProtocol;
910+
866911
if (verifyError) {
867912
cleartext.authorized = false;
868913
cleartext.authorizationError = verifyError;

src/node_constants.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,11 @@ void DefineConstants(Handle<Object> target) {
912912
#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
913913
NODE_DEFINE_CONSTANT(target, SSL_OP_CRYPTOPRO_TLSEXT_BUG);
914914
#endif
915+
916+
#ifdef OPENSSL_NPN_NEGOTIATED
917+
#define NPN_ENABLED 1
918+
NODE_DEFINE_CONSTANT(target, NPN_ENABLED);
919+
#endif
915920
}
916921

917922
} // namespace node

src/node_crypto.cc

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,11 @@ void Connection::Initialize(Handle<Object> target) {
565565
NODE_SET_PROTOTYPE_METHOD(t, "receivedShutdown", Connection::ReceivedShutdown);
566566
NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close);
567567

568+
#ifdef OPENSSL_NPN_NEGOTIATED
569+
NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", Connection::GetNegotiatedProto);
570+
NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", Connection::SetNPNProtocols);
571+
#endif
572+
568573
target->Set(String::NewSymbol("Connection"), t->GetFunction());
569574
}
570575

@@ -614,6 +619,76 @@ static int VerifyCallback(int preverify_ok, X509_STORE_CTX *ctx) {
614619
return 1;
615620
}
616621

622+
#ifdef OPENSSL_NPN_NEGOTIATED
623+
624+
int Connection::AdvertiseNextProtoCallback_(SSL *s,
625+
const unsigned char **data,
626+
unsigned int *len,
627+
void *arg) {
628+
629+
Connection *p = static_cast<Connection*>(SSL_get_app_data(s));
630+
631+
if (p->npnProtos_.IsEmpty()) {
632+
// No initialization - no NPN protocols
633+
*data = reinterpret_cast<const unsigned char*>("");
634+
*len = 0;
635+
} else {
636+
*data = reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_));
637+
*len = Buffer::Length(p->npnProtos_);
638+
}
639+
640+
return SSL_TLSEXT_ERR_OK;
641+
}
642+
643+
int Connection::SelectNextProtoCallback_(SSL *s,
644+
unsigned char **out, unsigned char *outlen,
645+
const unsigned char* in,
646+
unsigned int inlen, void *arg) {
647+
Connection *p = static_cast<Connection*> SSL_get_app_data(s);
648+
649+
// Release old protocol handler if present
650+
if (!p->selectedNPNProto_.IsEmpty()) {
651+
p->selectedNPNProto_.Dispose();
652+
}
653+
654+
if (p->npnProtos_.IsEmpty()) {
655+
// We should at least select one protocol
656+
// If server is using NPN
657+
*out = reinterpret_cast<unsigned char*>(const_cast<char*>("http/1.1"));
658+
*outlen = 8;
659+
660+
// set status unsupported
661+
p->selectedNPNProto_ = Persistent<Value>::New(False());
662+
663+
return SSL_TLSEXT_ERR_OK;
664+
}
665+
666+
const unsigned char* npnProtos =
667+
reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_));
668+
669+
int status = SSL_select_next_proto(out, outlen, in, inlen, npnProtos,
670+
Buffer::Length(p->npnProtos_));
671+
672+
switch (status) {
673+
case OPENSSL_NPN_UNSUPPORTED:
674+
p->selectedNPNProto_ = Persistent<Value>::New(Null());
675+
break;
676+
case OPENSSL_NPN_NEGOTIATED:
677+
p->selectedNPNProto_ = Persistent<Value>::New(String::New(
678+
reinterpret_cast<const char*>(*out), *outlen
679+
));
680+
break;
681+
case OPENSSL_NPN_NO_OVERLAP:
682+
p->selectedNPNProto_ = Persistent<Value>::New(False());
683+
break;
684+
default:
685+
break;
686+
}
687+
688+
return SSL_TLSEXT_ERR_OK;
689+
}
690+
#endif
691+
617692

618693
Handle<Value> Connection::New(const Arguments& args) {
619694
HandleScope scope;
@@ -633,6 +708,23 @@ Handle<Value> Connection::New(const Arguments& args) {
633708
p->ssl_ = SSL_new(sc->ctx_);
634709
p->bio_read_ = BIO_new(BIO_s_mem());
635710
p->bio_write_ = BIO_new(BIO_s_mem());
711+
712+
#ifdef OPENSSL_NPN_NEGOTIATED
713+
SSL_set_app_data(p->ssl_, p);
714+
if (is_server) {
715+
// Server should advertise NPN protocols
716+
SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
717+
AdvertiseNextProtoCallback_,
718+
NULL);
719+
} else {
720+
// Client should select protocol from advertised
721+
// If server supports NPN
722+
SSL_CTX_set_next_proto_select_cb(sc->ctx_,
723+
SelectNextProtoCallback_,
724+
NULL);
725+
}
726+
#endif
727+
636728
SSL_set_bio(p->ssl_, p->bio_read_, p->bio_write_);
637729

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

1279+
#ifdef OPENSSL_NPN_NEGOTIATED
1280+
Handle<Value> Connection::GetNegotiatedProto(const Arguments& args) {
1281+
HandleScope scope;
1282+
1283+
Connection *ss = Connection::Unwrap(args);
1284+
1285+
if (ss->is_server_) {
1286+
const unsigned char *npn_proto;
1287+
unsigned int npn_proto_len;
1288+
1289+
SSL_get0_next_proto_negotiated(ss->ssl_, &npn_proto, &npn_proto_len);
1290+
1291+
if (!npn_proto) {
1292+
return False();
1293+
}
1294+
1295+
return String::New((const char*) npn_proto, npn_proto_len);
1296+
} else {
1297+
return ss->selectedNPNProto_;
1298+
}
1299+
}
1300+
1301+
Handle<Value> Connection::SetNPNProtocols(const Arguments& args) {
1302+
HandleScope scope;
1303+
1304+
Connection *ss = Connection::Unwrap(args);
1305+
1306+
if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
1307+
return ThrowException(Exception::Error(String::New(
1308+
"Must give a Buffer as first argument")));
1309+
}
1310+
1311+
// Release old handle
1312+
if (!ss->npnProtos_.IsEmpty()) {
1313+
ss->npnProtos_.Dispose();
1314+
}
1315+
ss->npnProtos_ = Persistent<Object>::New(args[0]->ToObject());
1316+
1317+
return True();
1318+
};
1319+
#endif
1320+
11871321

11881322
static void HexEncode(unsigned char *md_value,
11891323
int md_len,

src/node_crypto.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#define SRC_NODE_CRYPTO_H_
2424

2525
#include <node.h>
26+
2627
#include <node_object_wrap.h>
2728
#include <v8.h>
2829

@@ -33,6 +34,10 @@
3334
#include <openssl/x509.h>
3435
#include <openssl/hmac.h>
3536

37+
#ifdef OPENSSL_NPN_NEGOTIATED
38+
#include <node_buffer.h>
39+
#endif
40+
3641
#define EVP_F_EVP_DECRYPTFINAL 101
3742

3843

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

102+
#ifdef OPENSSL_NPN_NEGOTIATED
103+
v8::Persistent<v8::Object> npnProtos_;
104+
v8::Persistent<v8::Value> selectedNPNProto_;
105+
#endif
106+
97107
protected:
98108
static v8::Handle<v8::Value> New(const v8::Arguments& args);
99109
static v8::Handle<v8::Value> EncIn(const v8::Arguments& args);
@@ -111,6 +121,20 @@ class Connection : ObjectWrap {
111121
static v8::Handle<v8::Value> Start(const v8::Arguments& args);
112122
static v8::Handle<v8::Value> Close(const v8::Arguments& args);
113123

124+
#ifdef OPENSSL_NPN_NEGOTIATED
125+
// NPN
126+
static v8::Handle<v8::Value> GetNegotiatedProto(const v8::Arguments& args);
127+
static v8::Handle<v8::Value> SetNPNProtocols(const v8::Arguments& args);
128+
static int AdvertiseNextProtoCallback_(SSL *s,
129+
const unsigned char **data,
130+
unsigned int *len,
131+
void *arg);
132+
static int SelectNextProtoCallback_(SSL *s,
133+
unsigned char **out, unsigned char *outlen,
134+
const unsigned char* in,
135+
unsigned int inlen, void *arg);
136+
#endif
137+
114138
int HandleBIOError(BIO *bio, const char* func, int rv);
115139
int HandleSSLError(const char* func, int rv);
116140

@@ -139,6 +163,7 @@ class Connection : ObjectWrap {
139163
BIO *bio_read_;
140164
BIO *bio_write_;
141165
SSL *ssl_;
166+
142167
bool is_server_; /* coverity[member_decl] */
143168
};
144169

0 commit comments

Comments
 (0)