Skip to content

Commit 9010f5f

Browse files
indutnyry
authored andcommitted
Add support for TLS SNI
Fixes #1411
1 parent 9dd9792 commit 9010f5f

8 files changed

+315
-22
lines changed

lib/https.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ 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;
27-
2826
function Server(opts, requestListener) {
2927
if (!(this instanceof Server)) return new Server(opts, requestListener);
3028

31-
if (NPN_ENABLED && !opts.NPNProtocols) {
29+
if (process.features.tls_npn && !opts.NPNProtocols) {
3230
opts.NPNProtocols = ['http/1.1', 'http/1.0'];
3331
}
3432

@@ -64,7 +62,7 @@ Agent.prototype.defaultPort = 443;
6462

6563

6664
Agent.prototype._getConnection = function(options, cb) {
67-
if (NPN_ENABLED && !this.options.NPNProtocols) {
65+
if (process.features.tls_npn && !this.options.NPNProtocols) {
6866
this.options.NPNProtocols = ['http/1.1', 'http/1.0'];
6967
}
7068

lib/https2.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ 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;
27-
2826
function Server(opts, requestListener) {
2927
if (!(this instanceof Server)) return new Server(opts, requestListener);
3028

31-
if (NPN_ENABLED && !opts.NPNProtocols) {
29+
if (process.features.tls_npn && !opts.NPNProtocols) {
3230
opts.NPNProtocols = ['http/1.1', 'http/1.0'];
3331
}
3432

lib/tls.js

+63-11
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ 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-
3230
var debug;
3331
if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
3432
debug = function(a) { console.error('TLS:', a); };
@@ -40,7 +38,6 @@ if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
4038
var Connection = null;
4139
try {
4240
Connection = process.binding('crypto').Connection;
43-
exports.NPN_ENABLED = NPN_ENABLED;
4441
} catch (e) {
4542
throw new Error('node.js not compiled with openssl crypto support.');
4643
}
@@ -478,17 +475,19 @@ EncryptedStream.prototype._pusher = function(pool, offset, length) {
478475
*/
479476

480477
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
481-
NPNProtocols) {
478+
options) {
482479
if (!(this instanceof SecurePair)) {
483480
return new SecurePair(credentials,
484481
isServer,
485482
requestCert,
486483
rejectUnauthorized,
487-
NPNProtocols);
484+
options);
488485
}
489486

490487
var self = this;
491488

489+
options || (options = {});
490+
492491
events.EventEmitter.call(this);
493492

494493
this._secureEstablished = false;
@@ -514,11 +513,19 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
514513
this._requestCert = requestCert ? true : false;
515514

516515
this.ssl = new Connection(this.credentials.context,
517-
this._isServer ? true : false, this._requestCert,
516+
this._isServer ? true : false,
517+
this._isServer ? this._requestCert : options.servername,
518518
this._rejectUnauthorized);
519519

520-
if (NPN_ENABLED && NPNProtocols) {
521-
this.ssl.setNPNProtocols(NPNProtocols);
520+
if (process.features.tls_sni) {
521+
if (this._isServer && options.SNICallback) {
522+
this.ssl.setSNICallback(options.SNICallback);
523+
}
524+
this.servername = null;
525+
}
526+
527+
if (process.features.tls_npn && options.NPNProtocols) {
528+
this.ssl.setNPNProtocols(options.NPNProtocols);
522529
this.npnProtocol = null;
523530
}
524531

@@ -629,9 +636,14 @@ SecurePair.prototype.cycle = function(depth) {
629636

630637
SecurePair.prototype.maybeInitFinished = function() {
631638
if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) {
632-
if (NPN_ENABLED) {
639+
if (process.features.tls_npn) {
633640
this.npnProtocol = this.ssl.getNegotiatedProtocol();
634641
}
642+
643+
if (process.features.tls_sni) {
644+
this.servername = this.ssl.getServername();
645+
}
646+
635647
this._secureEstablished = true;
636648
debug('secure established');
637649
this.emit('secure');
@@ -789,14 +801,19 @@ function Server(/* [options], listener */) {
789801
true,
790802
self.requestCert,
791803
self.rejectUnauthorized,
792-
self.NPNProtocols);
804+
{
805+
NPNProtocols: self.NPNProtocols,
806+
SNICallback: self.SNICallback
807+
});
793808

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

797812
pair.on('secure', function() {
798813
pair.cleartext.authorized = false;
799814
pair.cleartext.npnProtocol = pair.npnProtocol;
815+
pair.cleartext.servername = pair.servername;
816+
800817
if (!self.requestCert) {
801818
cleartext._controlReleased = true;
802819
self.emit('secureConnection', pair.cleartext, pair.encrypted);
@@ -858,6 +875,38 @@ Server.prototype.setOptions = function(options) {
858875
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
859876
if (options.secureOptions) this.secureOptions = options.secureOptions;
860877
if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this);
878+
if (options.SNICallback) {
879+
this.SNICallback = options.SNICallback;
880+
} else {
881+
this.SNICallback = this.SNICallback.bind(this);
882+
}
883+
};
884+
885+
// SNI Contexts High-Level API
886+
Server.prototype._contexts = [];
887+
Server.prototype.addContext = function(servername, credentials) {
888+
if (!servername) {
889+
throw 'Servername is required parameter for Server.addContext';
890+
}
891+
892+
var re = new RegExp('^' +
893+
servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1')
894+
.replace(/\*/g, '.*') +
895+
'$');
896+
this._contexts.push([re, crypto.createCredentials(credentials).context]);
897+
};
898+
899+
Server.prototype.SNICallback = function(servername) {
900+
var ctx;
901+
902+
this._contexts.some(function(elem) {
903+
if (servername.match(elem[0]) !== null) {
904+
ctx = elem[1];
905+
return true;
906+
}
907+
});
908+
909+
return ctx;
861910
};
862911

863912

@@ -902,7 +951,10 @@ exports.connect = function(port /* host, options, cb */) {
902951

903952
convertNPNProtocols(options.NPNProtocols, this);
904953
var pair = new SecurePair(sslcontext, false, true, false,
905-
this.NPNProtocols);
954+
{
955+
NPNProtocols: this.NPNProtocols,
956+
servername: options.servername
957+
});
906958

907959
var cleartext = pipe(pair, socket);
908960

src/node.cc

+14
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ static bool use_uv = true;
142142
// disabled by default for now
143143
static bool use_http2 = false;
144144

145+
#ifdef OPENSSL_NPN_NEGOTIATED
146+
static bool use_npn = true;
147+
#else
148+
static bool use_npn = false;
149+
#endif
150+
151+
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
152+
static bool use_sni = true;
153+
#else
154+
static bool use_sni = false;
155+
#endif
156+
145157
// Buffer for getpwnam_r(), getgrpam_r() and other misc callers; keep this
146158
// scoped at file-level rather than method-level to avoid excess stack usage.
147159
static char getbuf[PATH_MAX + 1];
@@ -2031,6 +2043,8 @@ static Handle<Object> GetFeatures() {
20312043
obj->Set(String::NewSymbol("uv"), Boolean::New(use_uv));
20322044
obj->Set(String::NewSymbol("http2"), Boolean::New(use_http2));
20332045
obj->Set(String::NewSymbol("ipv6"), True()); // TODO ping libuv
2046+
obj->Set(String::NewSymbol("tls_npn"), Boolean::New(use_npn));
2047+
obj->Set(String::NewSymbol("tls_sni"), Boolean::New(use_sni));
20342048
obj->Set(String::NewSymbol("tls"),
20352049
Boolean::New(get_builtin_module("crypto") != NULL));
20362050

src/node_crypto.cc

+103-1
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,14 @@ static Persistent<String> name_symbol;
6161
static Persistent<String> version_symbol;
6262
static Persistent<String> ext_key_usage_symbol;
6363

64+
static Persistent<FunctionTemplate> secure_context_constructor;
6465

6566
void SecureContext::Initialize(Handle<Object> target) {
6667
HandleScope scope;
6768

6869
Local<FunctionTemplate> t = FunctionTemplate::New(SecureContext::New);
70+
secure_context_constructor = Persistent<FunctionTemplate>::New(t);
71+
6972
t->InstanceTemplate()->SetInternalFieldCount(1);
7073
t->SetClassName(String::NewSymbol("SecureContext"));
7174

@@ -585,6 +588,12 @@ void Connection::Initialize(Handle<Object> target) {
585588
NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", Connection::SetNPNProtocols);
586589
#endif
587590

591+
592+
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
593+
NODE_SET_PROTOTYPE_METHOD(t, "getServername", Connection::GetServername);
594+
NODE_SET_PROTOTYPE_METHOD(t, "setSNICallback", Connection::SetSNICallback);
595+
#endif
596+
588597
target->Set(String::NewSymbol("Connection"), t->GetFunction());
589598
}
590599

@@ -704,6 +713,56 @@ int Connection::SelectNextProtoCallback_(SSL *s,
704713
}
705714
#endif
706715

716+
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
717+
int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) {
718+
HandleScope scope;
719+
720+
Connection *p = static_cast<Connection*> SSL_get_app_data(s);
721+
722+
const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
723+
724+
if (servername) {
725+
if (!p->servername_.IsEmpty()) {
726+
p->servername_.Dispose();
727+
}
728+
p->servername_ = Persistent<String>::New(String::New(servername));
729+
730+
// Call sniCallback_ and use it's return value as context
731+
if (!p->sniCallback_.IsEmpty()) {
732+
if (!p->sniContext_.IsEmpty()) {
733+
p->sniContext_.Dispose();
734+
}
735+
736+
// Get callback init args
737+
Local<Value> argv[1] = {*p->servername_};
738+
Local<Function> callback = *p->sniCallback_;
739+
740+
TryCatch try_catch;
741+
742+
// Call it
743+
Local<Value> ret = callback->Call(Context::GetCurrent()->Global(),
744+
1,
745+
argv);
746+
747+
if (try_catch.HasCaught()) {
748+
FatalException(try_catch);
749+
}
750+
751+
// If ret is SecureContext
752+
if (secure_context_constructor->HasInstance(ret)) {
753+
p->sniContext_ = Persistent<Value>::New(ret);
754+
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(
755+
Local<Object>::Cast(ret));
756+
SSL_set_SSL_CTX(s, sc->ctx_);
757+
} else {
758+
return SSL_TLSEXT_ERR_NOACK;
759+
}
760+
}
761+
}
762+
763+
return SSL_TLSEXT_ERR_OK;
764+
}
765+
#endif
707766

708767
Handle<Value> Connection::New(const Arguments& args) {
709768
HandleScope scope;
@@ -724,8 +783,9 @@ Handle<Value> Connection::New(const Arguments& args) {
724783
p->bio_read_ = BIO_new(BIO_s_mem());
725784
p->bio_write_ = BIO_new(BIO_s_mem());
726785

727-
#ifdef OPENSSL_NPN_NEGOTIATED
728786
SSL_set_app_data(p->ssl_, p);
787+
788+
#ifdef OPENSSL_NPN_NEGOTIATED
729789
if (is_server) {
730790
// Server should advertise NPN protocols
731791
SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
@@ -740,6 +800,15 @@ Handle<Value> Connection::New(const Arguments& args) {
740800
}
741801
#endif
742802

803+
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
804+
if (is_server) {
805+
SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_);
806+
} else {
807+
String::Utf8Value servername(args[2]->ToString());
808+
SSL_set_tlsext_host_name(p->ssl_, *servername);
809+
}
810+
#endif
811+
743812
SSL_set_bio(p->ssl_, p->bio_read_, p->bio_write_);
744813

745814
#ifdef SSL_MODE_RELEASE_BUFFERS
@@ -1333,6 +1402,39 @@ Handle<Value> Connection::SetNPNProtocols(const Arguments& args) {
13331402
};
13341403
#endif
13351404

1405+
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
1406+
Handle<Value> Connection::GetServername(const Arguments& args) {
1407+
HandleScope scope;
1408+
1409+
Connection *ss = Connection::Unwrap(args);
1410+
1411+
if (ss->is_server_ && !ss->servername_.IsEmpty()) {
1412+
return ss->servername_;
1413+
} else {
1414+
return False();
1415+
}
1416+
}
1417+
1418+
Handle<Value> Connection::SetSNICallback(const Arguments& args) {
1419+
HandleScope scope;
1420+
1421+
Connection *ss = Connection::Unwrap(args);
1422+
1423+
if (args.Length() < 1 || !args[0]->IsFunction()) {
1424+
return ThrowException(Exception::Error(String::New(
1425+
"Must give a Function as first argument")));
1426+
}
1427+
1428+
// Release old handle
1429+
if (!ss->sniCallback_.IsEmpty()) {
1430+
ss->sniCallback_.Dispose();
1431+
}
1432+
ss->sniCallback_ = Persistent<Function>::New(
1433+
Local<Function>::Cast(args[0]));
1434+
1435+
return True();
1436+
}
1437+
#endif
13361438

13371439
static void HexEncode(unsigned char *md_value,
13381440
int md_len,

0 commit comments

Comments
 (0)