Skip to content

Commit 048e0e7

Browse files
committed
tls: asynchronous SNICallback
Make ClientHelloParser handle SNI extension, and extend `_tls_wrap.js` to support loading SNI Context from both hello, and resumed session. fix #5967
1 parent 8e28193 commit 048e0e7

8 files changed

+162
-36
lines changed

doc/api/tls.markdown

+4-3
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,10 @@ automatically set as a listener for the [secureConnection][] event. The
156156
- `NPNProtocols`: An array or `Buffer` of possible NPN protocols. (Protocols
157157
should be ordered by their priority).
158158

159-
- `SNICallback`: A function that will be called if client supports SNI TLS
160-
extension. Only one argument will be passed to it: `servername`. And
161-
`SNICallback` should return SecureContext instance.
159+
- `SNICallback(servername, cb)`: A function that will be called if client
160+
supports SNI TLS extension. Two argument will be passed to it: `servername`,
161+
and `cb`. `SNICallback` should invoke `cb(null, ctx)`, where `ctx` is a
162+
SecureContext instance.
162163
(You can use `crypto.createCredentials(...).context` to get proper
163164
SecureContext). If `SNICallback` wasn't provided - default callback with
164165
high-level API will be used (see below).

lib/_tls_wrap.js

+54-9
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,67 @@ function onhandshakedone() {
4949

5050
function onclienthello(hello) {
5151
var self = this,
52-
once = false;
52+
onceSession = false,
53+
onceSNI = false;
5354

5455
function callback(err, session) {
55-
if (once)
56-
return self.destroy(new Error('TLS session callback was called twice'));
57-
once = true;
56+
if (onceSession)
57+
return self.destroy(new Error('TLS session callback was called 2 times'));
58+
onceSession = true;
5859

5960
if (err)
6061
return self.destroy(err);
6162

62-
self.ssl.loadSession(session);
63+
// NOTE: That we have disabled OpenSSL's internal session storage in
64+
// `node_crypto.cc` and hence its safe to rely on getting servername only
65+
// from clienthello or this place.
66+
var ret = self.ssl.loadSession(session);
67+
68+
// Servername came from SSL session
69+
// NOTE: TLS Session ticket doesn't include servername information
70+
//
71+
// Another note, From RFC3546:
72+
//
73+
// If, on the other hand, the older
74+
// session is resumed, then the server MUST ignore extensions appearing
75+
// in the client hello, and send a server hello containing no
76+
// extensions; in this case the extension functionality negotiated
77+
// during the original session initiation is applied to the resumed
78+
// session.
79+
//
80+
// Therefore we should account session loading when dealing with servername
81+
if (ret && ret.servername) {
82+
self._SNICallback(ret.servername, onSNIResult);
83+
} else if (hello.servername && self._SNICallback) {
84+
self._SNICallback(hello.servername, onSNIResult);
85+
} else {
86+
self.ssl.endParser();
87+
}
88+
}
89+
90+
function onSNIResult(err, context) {
91+
if (onceSNI)
92+
return self.destroy(new Error('TLS SNI callback was called 2 times'));
93+
onceSNI = true;
94+
95+
if (err)
96+
return self.destroy(err);
97+
98+
if (context)
99+
self.ssl.sni_context = context;
100+
101+
self.ssl.endParser();
63102
}
64103

65104
if (hello.sessionId.length <= 0 ||
66105
hello.tlsTicket ||
67106
this.server &&
68107
!this.server.emit('resumeSession', hello.sessionId, callback)) {
69-
callback(null, null);
108+
// Invoke SNI callback, since we've no session to resume
109+
if (hello.servername && this._SNICallback)
110+
this._SNICallback(hello.servername, onSNIResult);
111+
else
112+
this.ssl.endParser();
70113
}
71114
}
72115

@@ -94,6 +137,7 @@ function TLSSocket(socket, options) {
94137
this._tlsOptions = options;
95138
this._secureEstablished = false;
96139
this._controlReleased = false;
140+
this._SNICallback = null;
97141
this.ssl = null;
98142
this.servername = null;
99143
this.npnProtocol = null;
@@ -176,7 +220,8 @@ TLSSocket.prototype._init = function() {
176220
(options.SNICallback !== SNICallback ||
177221
options.server._contexts.length)) {
178222
assert(typeof options.SNICallback === 'function');
179-
this.ssl.onsniselect = options.SNICallback;
223+
this._SNICallback = options.SNICallback;
224+
this.ssl.enableHelloParser();
180225
}
181226

182227
if (process.features.tls_npn && options.NPNProtocols)
@@ -499,7 +544,7 @@ Server.prototype.addContext = function(servername, credentials) {
499544
this._contexts.push([re, crypto.createCredentials(credentials).context]);
500545
};
501546

502-
function SNICallback(servername) {
547+
function SNICallback(servername, callback) {
503548
var ctx;
504549

505550
this._contexts.some(function(elem) {
@@ -509,7 +554,7 @@ function SNICallback(servername) {
509554
}
510555
});
511556

512-
return ctx;
557+
callback(null, ctx);
513558
}
514559

515560
Server.prototype.SNICallback = SNICallback;

src/node_crypto_clienthello-inl.h

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ inline void ClientHelloParser::Reset() {
3434
session_id_ = NULL;
3535
tls_ticket_size_ = -1;
3636
tls_ticket_ = NULL;
37+
servername_size_ = 0;
38+
servername_ = NULL;
3739
}
3840

3941
inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb,

src/node_crypto_clienthello.cc

+25
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) {
123123
hello.session_id_ = session_id_;
124124
hello.session_size_ = session_size_;
125125
hello.has_ticket_ = tls_ticket_ != NULL && tls_ticket_size_ != 0;
126+
hello.servername_ = servername_;
127+
hello.servername_size_ = servername_size_;
126128
onhello_cb_(cb_arg_, hello);
127129
}
128130

@@ -134,6 +136,29 @@ void ClientHelloParser::ParseExtension(ClientHelloParser::ExtensionType type,
134136
// That's because we're heavily relying on OpenSSL to solve any problem with
135137
// incoming data.
136138
switch (type) {
139+
case kServerName:
140+
{
141+
if (len < 2)
142+
return;
143+
uint16_t server_names_len = (data[0] << 8) + data[1];
144+
if (server_names_len + 2 > len)
145+
return;
146+
for (size_t offset = 2; offset < 2 + server_names_len; ) {
147+
if (offset + 3 > len)
148+
return;
149+
uint8_t name_type = data[offset];
150+
if (name_type != kServernameHostname)
151+
return;
152+
uint16_t name_len = (data[offset + 1] << 8) + data[offset + 2];
153+
offset += 3;
154+
if (offset + name_len > len)
155+
return;
156+
servername_ = data + offset;
157+
servername_size_ = name_len;
158+
offset += name_len;
159+
}
160+
}
161+
break;
137162
case kTLSSessionTicket:
138163
tls_ticket_size_ = len;
139164
tls_ticket_ = data + len;

src/node_crypto_clienthello.h

+8
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,15 @@ class ClientHelloParser {
4646
inline uint8_t session_size() const { return session_size_; }
4747
inline const uint8_t* session_id() const { return session_id_; }
4848
inline bool has_ticket() const { return has_ticket_; }
49+
inline uint8_t servername_size() const { return servername_size_; }
50+
inline const uint8_t* servername() const { return servername_; }
4951

5052
private:
5153
uint8_t session_size_;
5254
const uint8_t* session_id_;
5355
bool has_ticket_;
56+
uint8_t servername_size_;
57+
const uint8_t* servername_;
5458

5559
friend class ClientHelloParser;
5660
};
@@ -71,6 +75,7 @@ class ClientHelloParser {
7175
static const uint8_t kSSL2HeaderMask = 0x3f;
7276
static const size_t kMaxTLSFrameLen = 16 * 1024 + 5;
7377
static const size_t kMaxSSLExFrameLen = 32 * 1024;
78+
static const uint8_t kServernameHostname = 0;
7479

7580
enum ParseState {
7681
kWaiting,
@@ -93,6 +98,7 @@ class ClientHelloParser {
9398
};
9499

95100
enum ExtensionType {
101+
kServerName = 0,
96102
kTLSSessionTicket = 35
97103
};
98104

@@ -115,6 +121,8 @@ class ClientHelloParser {
115121
size_t extension_offset_;
116122
uint8_t session_size_;
117123
const uint8_t* session_id_;
124+
uint16_t servername_size_;
125+
const uint8_t* servername_;
118126
uint16_t tls_ticket_size_;
119127
const uint8_t* tls_ticket_;
120128
};

src/tls_wrap.cc

+57-20
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ using v8::Value;
4949

5050
static Cached<String> onread_sym;
5151
static Cached<String> onerror_sym;
52-
static Cached<String> onsniselect_sym;
5352
static Cached<String> onhandshakestart_sym;
5453
static Cached<String> onhandshakedone_sym;
5554
static Cached<String> onclienthello_sym;
@@ -67,6 +66,8 @@ static Cached<String> version_sym;
6766
static Cached<String> ext_key_usage_sym;
6867
static Cached<String> sessionid_sym;
6968
static Cached<String> tls_ticket_sym;
69+
static Cached<String> servername_sym;
70+
static Cached<String> sni_context_sym;
7071

7172
static Persistent<Function> tlsWrap;
7273

@@ -174,7 +175,6 @@ TLSCallbacks::~TLSCallbacks() {
174175
#endif // OPENSSL_NPN_NEGOTIATED
175176

176177
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
177-
servername_.Dispose();
178178
sni_context_.Dispose();
179179
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
180180
}
@@ -640,7 +640,6 @@ void TLSCallbacks::DoRead(uv_stream_t* handle,
640640

641641
// Parse ClientHello first
642642
if (!hello_.IsEnded()) {
643-
assert(session_callbacks_);
644643
size_t avail = 0;
645644
uint8_t* data = reinterpret_cast<uint8_t*>(enc_in->Peek(&avail));
646645
assert(avail == 0 || data != NULL);
@@ -770,6 +769,16 @@ void TLSCallbacks::EnableSessionCallbacks(
770769
UNWRAP(TLSCallbacks);
771770

772771
wrap->session_callbacks_ = true;
772+
EnableHelloParser(args);
773+
}
774+
775+
776+
void TLSCallbacks::EnableHelloParser(
777+
const FunctionCallbackInfo<Value>& args) {
778+
HandleScope scope(node_isolate);
779+
780+
UNWRAP(TLSCallbacks);
781+
773782
wrap->hello_.Start(OnClientHello, OnClientHelloParseEnd, wrap);
774783
}
775784

@@ -785,6 +794,14 @@ void TLSCallbacks::OnClientHello(void* arg,
785794
reinterpret_cast<const char*>(hello.session_id()),
786795
hello.session_size());
787796
hello_obj->Set(sessionid_sym, buff);
797+
if (hello.servername() == NULL) {
798+
hello_obj->Set(servername_sym, String::Empty(node_isolate));
799+
} else {
800+
Local<String> servername = String::New(
801+
reinterpret_cast<const char*>(hello.servername()),
802+
hello.servername_size());
803+
hello_obj->Set(servername_sym, servername);
804+
}
788805
hello_obj->Set(tls_ticket_sym, Boolean::New(hello.has_ticket()));
789806

790807
Handle<Value> argv[1] = { hello_obj };
@@ -999,7 +1016,23 @@ void TLSCallbacks::LoadSession(const FunctionCallbackInfo<Value>& args) {
9991016
if (wrap->next_sess_ != NULL)
10001017
SSL_SESSION_free(wrap->next_sess_);
10011018
wrap->next_sess_ = sess;
1019+
1020+
Local<Object> info = Object::New();
1021+
#ifndef OPENSSL_NO_TLSEXT
1022+
if (sess->tlsext_hostname == NULL) {
1023+
info->Set(servername_sym, False(node_isolate));
1024+
} else {
1025+
info->Set(servername_sym, String::New(sess->tlsext_hostname));
1026+
}
1027+
#endif
1028+
args.GetReturnValue().Set(info);
10021029
}
1030+
}
1031+
1032+
void TLSCallbacks::EndParser(const FunctionCallbackInfo<Value>& args) {
1033+
HandleScope scope(node_isolate);
1034+
1035+
UNWRAP(TLSCallbacks);
10031036

10041037
wrap->hello_.End();
10051038
}
@@ -1143,8 +1176,10 @@ void TLSCallbacks::GetServername(const FunctionCallbackInfo<Value>& args) {
11431176

11441177
UNWRAP(TLSCallbacks);
11451178

1146-
if (wrap->kind_ == kTLSServer && !wrap->servername_.IsEmpty()) {
1147-
args.GetReturnValue().Set(wrap->servername_);
1179+
const char* servername = SSL_get_servername(wrap->ssl_,
1180+
TLSEXT_NAMETYPE_host_name);
1181+
if (servername != NULL) {
1182+
args.GetReturnValue().Set(String::New(servername));
11481183
} else {
11491184
args.GetReturnValue().Set(false);
11501185
}
@@ -1179,25 +1214,22 @@ int TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) {
11791214

11801215
const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
11811216

1182-
if (servername) {
1183-
p->servername_.Reset(node_isolate, String::New(servername));
1184-
1217+
if (servername != NULL) {
11851218
// Call the SNI callback and use its return value as context
11861219
Local<Object> object = p->object();
1187-
if (object->Has(onsniselect_sym)) {
1188-
p->sni_context_.Dispose();
1220+
Local<Value> ctx;
1221+
if (object->Has(sni_context_sym)) {
1222+
ctx = object->Get(sni_context_sym);
1223+
}
11891224

1190-
Local<Value> arg = PersistentToLocal(node_isolate, p->servername_);
1191-
Handle<Value> ret = MakeCallback(object, onsniselect_sym, 1, &arg);
1225+
if (ctx.IsEmpty() || ctx->IsUndefined())
1226+
return SSL_TLSEXT_ERR_NOACK;
11921227

1193-
// If ret is SecureContext
1194-
if (ret->IsUndefined())
1195-
return SSL_TLSEXT_ERR_NOACK;
1228+
p->sni_context_.Dispose();
1229+
p->sni_context_.Reset(node_isolate, ctx);
11961230

1197-
p->sni_context_.Reset(node_isolate, ret);
1198-
SecureContext* sc = ObjectWrap::Unwrap<SecureContext>(ret.As<Object>());
1199-
SSL_set_SSL_CTX(s, sc->ctx_);
1200-
}
1231+
SecureContext* sc = ObjectWrap::Unwrap<SecureContext>(ctx.As<Object>());
1232+
SSL_set_SSL_CTX(s, sc->ctx_);
12011233
}
12021234

12031235
return SSL_TLSEXT_ERR_OK;
@@ -1219,13 +1251,17 @@ void TLSCallbacks::Initialize(Handle<Object> target) {
12191251
NODE_SET_PROTOTYPE_METHOD(t, "getSession", GetSession);
12201252
NODE_SET_PROTOTYPE_METHOD(t, "setSession", SetSession);
12211253
NODE_SET_PROTOTYPE_METHOD(t, "loadSession", LoadSession);
1254+
NODE_SET_PROTOTYPE_METHOD(t, "endParser", EndParser);
12221255
NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher);
12231256
NODE_SET_PROTOTYPE_METHOD(t, "verifyError", VerifyError);
12241257
NODE_SET_PROTOTYPE_METHOD(t, "setVerifyMode", SetVerifyMode);
12251258
NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", IsSessionReused);
12261259
NODE_SET_PROTOTYPE_METHOD(t,
12271260
"enableSessionCallbacks",
12281261
EnableSessionCallbacks);
1262+
NODE_SET_PROTOTYPE_METHOD(t,
1263+
"enableHelloParser",
1264+
EnableHelloParser);
12291265

12301266
#ifdef OPENSSL_NPN_NEGOTIATED
12311267
NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto);
@@ -1240,7 +1276,6 @@ void TLSCallbacks::Initialize(Handle<Object> target) {
12401276
tlsWrap.Reset(node_isolate, t->GetFunction());
12411277

12421278
onread_sym = String::New("onread");
1243-
onsniselect_sym = String::New("onsniselect");
12441279
onerror_sym = String::New("onerror");
12451280
onhandshakestart_sym = String::New("onhandshakestart");
12461281
onhandshakedone_sym = String::New("onhandshakedone");
@@ -1260,6 +1295,8 @@ void TLSCallbacks::Initialize(Handle<Object> target) {
12601295
ext_key_usage_sym = String::New("ext_key_usage");
12611296
sessionid_sym = String::New("sessionId");
12621297
tls_ticket_sym = String::New("tlsTicket");
1298+
servername_sym = String::New("servername");
1299+
sni_context_sym = String::New("sni_context");
12631300
}
12641301

12651302
} // namespace node

0 commit comments

Comments
 (0)