Skip to content

Commit 8e28193

Browse files
committed
tls_wrap: DRY ClientHelloParser
Share ClientHelloParser code between `tls_wrap.cc` and `node_crypto.cc`. fix #5959
1 parent 6942a95 commit 8e28193

8 files changed

+536
-414
lines changed

node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,10 @@
161161
'sources': [
162162
'src/node_crypto.cc',
163163
'src/node_crypto_bio.cc',
164+
'src/node_crypto_clienthello.cc',
164165
'src/node_crypto.h',
165166
'src/node_crypto_bio.h',
167+
'src/node_crypto_clienthello.h',
166168
'src/tls_wrap.cc',
167169
'src/tls_wrap.h'
168170
],

src/node_crypto.cc

+40-139
Original file line numberDiff line numberDiff line change
@@ -794,149 +794,38 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
794794
}
795795

796796

797-
size_t ClientHelloParser::Write(const uint8_t* data, size_t len) {
798-
HandleScope scope(node_isolate);
799-
800-
// Just accumulate data, everything will be pushed to BIO later
801-
if (state_ == kPaused) return 0;
802-
803-
// Copy incoming data to the internal buffer
804-
// (which has a size of the biggest possible TLS frame)
805-
size_t available = sizeof(data_) - offset_;
806-
size_t copied = len < available ? len : available;
807-
memcpy(data_ + offset_, data, copied);
808-
offset_ += copied;
809-
810-
// Vars for parsing hello
811-
bool is_clienthello = false;
812-
uint8_t session_size = -1;
813-
uint8_t* session_id = NULL;
814-
Local<Object> hello;
815-
Handle<Value> argv[1];
816-
817-
switch (state_) {
818-
case kWaiting:
819-
// >= 5 bytes for header parsing
820-
if (offset_ < 5)
821-
break;
822-
823-
if (data_[0] == kChangeCipherSpec ||
824-
data_[0] == kAlert ||
825-
data_[0] == kHandshake ||
826-
data_[0] == kApplicationData) {
827-
frame_len_ = (data_[3] << 8) + data_[4];
828-
state_ = kTLSHeader;
829-
body_offset_ = 5;
830-
} else {
831-
frame_len_ = (data_[0] << 8) + data_[1];
832-
state_ = kSSLHeader;
833-
if (*data_ & 0x40) {
834-
// header with padding
835-
body_offset_ = 3;
836-
} else {
837-
// without padding
838-
body_offset_ = 2;
839-
}
840-
}
841-
842-
// Sanity check (too big frame, or too small)
843-
if (frame_len_ >= sizeof(data_)) {
844-
// Let OpenSSL handle it
845-
Finish();
846-
return copied;
847-
}
848-
case kTLSHeader:
849-
case kSSLHeader:
850-
// >= 5 + frame size bytes for frame parsing
851-
if (offset_ < body_offset_ + frame_len_)
852-
break;
853-
854-
// Skip unsupported frames and gather some data from frame
855-
856-
if (data_[body_offset_] == kClientHello) {
857-
is_clienthello = true;
858-
uint8_t* body;
859-
size_t session_offset;
860-
861-
if (state_ == kTLSHeader) {
862-
// Skip frame header, hello header, protocol version and random data
863-
session_offset = body_offset_ + 4 + 2 + 32;
864-
865-
if (session_offset + 1 < offset_) {
866-
body = data_ + session_offset;
867-
session_size = *body;
868-
session_id = body + 1;
869-
}
870-
} else if (state_ == kSSLHeader) {
871-
// Skip header, version
872-
session_offset = body_offset_ + 3;
873-
874-
if (session_offset + 4 < offset_) {
875-
body = data_ + session_offset;
876-
877-
int ciphers_size = (body[0] << 8) + body[1];
878-
879-
if (body + 4 + ciphers_size < data_ + offset_) {
880-
session_size = (body[2] << 8) + body[3];
881-
session_id = body + 4 + ciphers_size;
882-
}
883-
}
884-
} else {
885-
// Whoa? How did we get here?
886-
abort();
887-
}
888-
889-
// Check if we overflowed (do not reply with any private data)
890-
if (session_id == NULL ||
891-
session_size > 32 ||
892-
session_id + session_size > data_ + offset_) {
893-
Finish();
894-
return copied;
895-
}
896-
}
797+
void Connection::OnClientHello(void* arg,
798+
const ClientHelloParser::ClientHello& hello) {
799+
HandleScope scope(node_isolate);
800+
Connection* c = static_cast<Connection*>(arg);
897801

898-
// Not client hello - let OpenSSL handle it
899-
if (!is_clienthello) {
900-
Finish();
901-
return copied;
902-
}
802+
if (onclienthello_sym.IsEmpty())
803+
onclienthello_sym = String::New("onclienthello");
804+
if (sessionid_sym.IsEmpty())
805+
sessionid_sym = String::New("sessionId");
903806

904-
// Parse frame, call javascript handler and
905-
// move parser into the paused state
906-
if (onclienthello_sym.IsEmpty())
907-
onclienthello_sym = String::New("onclienthello");
908-
if (sessionid_sym.IsEmpty())
909-
sessionid_sym = String::New("sessionId");
910-
911-
state_ = kPaused;
912-
hello = Object::New();
913-
hello->Set(sessionid_sym,
914-
Buffer::New(reinterpret_cast<char*>(session_id),
915-
session_size));
916-
917-
argv[0] = hello;
918-
MakeCallback(conn_->handle(node_isolate),
919-
onclienthello_sym,
920-
ARRAY_SIZE(argv),
921-
argv);
922-
break;
923-
case kEnded:
924-
default:
925-
break;
926-
}
807+
Local<Object> obj = Object::New();
808+
obj->Set(sessionid_sym,
809+
Buffer::New(reinterpret_cast<const char*>(hello.session_id()),
810+
hello.session_size()));
927811

928-
return copied;
812+
Handle<Value> argv[1] = { obj };
813+
MakeCallback(c->handle(node_isolate),
814+
onclienthello_sym,
815+
ARRAY_SIZE(argv),
816+
argv);
929817
}
930818

931819

932-
void ClientHelloParser::Finish() {
933-
assert(state_ != kEnded);
934-
state_ = kEnded;
820+
void Connection::OnClientHelloParseEnd(void* arg) {
821+
Connection* c = static_cast<Connection*>(arg);
935822

936823
// Write all accumulated data
937-
int r = BIO_write(conn_->bio_read_, reinterpret_cast<char*>(data_), offset_);
938-
conn_->HandleBIOError(conn_->bio_read_, "BIO_write", r);
939-
conn_->SetShutdownFlags();
824+
int r = BIO_write(c->bio_read_,
825+
reinterpret_cast<char*>(c->hello_data_),
826+
c->hello_offset_);
827+
c->HandleBIOError(c->bio_read_, "BIO_write", r);
828+
c->SetShutdownFlags();
940829
}
941830

942831

@@ -1398,9 +1287,21 @@ void Connection::EncIn(const FunctionCallbackInfo<Value>& args) {
13981287
int bytes_written;
13991288
char* data = buffer_data + off;
14001289

1401-
if (ss->is_server_ && !ss->hello_parser_.ended()) {
1402-
bytes_written = ss->hello_parser_.Write(reinterpret_cast<uint8_t*>(data),
1403-
len);
1290+
if (ss->is_server_ && !ss->hello_parser_.IsEnded()) {
1291+
// Just accumulate data, everything will be pushed to BIO later
1292+
if (ss->hello_parser_.IsPaused()) {
1293+
bytes_written = 0;
1294+
} else {
1295+
// Copy incoming data to the internal buffer
1296+
// (which has a size of the biggest possible TLS frame)
1297+
size_t available = sizeof(ss->hello_data_) - ss->hello_offset_;
1298+
size_t copied = len < available ? len : available;
1299+
memcpy(ss->hello_data_ + ss->hello_offset_, data, copied);
1300+
ss->hello_offset_ += copied;
1301+
1302+
ss->hello_parser_.Parse(ss->hello_data_, ss->hello_offset_);
1303+
bytes_written = copied;
1304+
}
14041305
} else {
14051306
bytes_written = BIO_write(ss->bio_read_, data, len);
14061307
ss->HandleBIOError(ss->bio_read_, "BIO_write", bytes_written);
@@ -1766,7 +1667,7 @@ void Connection::LoadSession(const FunctionCallbackInfo<Value>& args) {
17661667
ss->next_sess_ = sess;
17671668
}
17681669

1769-
ss->hello_parser_.Finish();
1670+
ss->hello_parser_.End();
17701671
}
17711672

17721673

src/node_crypto.h

+11-44
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#define SRC_NODE_CRYPTO_H_
2424

2525
#include "node.h"
26+
#include "node_crypto_clienthello.h" // ClientHelloParser
27+
#include "node_crypto_clienthello-inl.h"
2628
#include "node_object_wrap.h"
2729

2830
#ifdef OPENSSL_NPN_NEGOTIATED
@@ -117,49 +119,6 @@ class SecureContext : ObjectWrap {
117119
private:
118120
};
119121

120-
class ClientHelloParser {
121-
public:
122-
enum FrameType {
123-
kChangeCipherSpec = 20,
124-
kAlert = 21,
125-
kHandshake = 22,
126-
kApplicationData = 23,
127-
kOther = 255
128-
};
129-
130-
enum HandshakeType {
131-
kClientHello = 1
132-
};
133-
134-
enum ParseState {
135-
kWaiting,
136-
kTLSHeader,
137-
kSSLHeader,
138-
kPaused,
139-
kEnded
140-
};
141-
142-
explicit ClientHelloParser(Connection* c) : conn_(c),
143-
state_(kWaiting),
144-
offset_(0),
145-
body_offset_(0) {
146-
}
147-
148-
size_t Write(const uint8_t* data, size_t len);
149-
void Finish();
150-
151-
inline bool ended() { return state_ == kEnded; }
152-
153-
private:
154-
Connection* conn_;
155-
ParseState state_;
156-
size_t frame_len_;
157-
158-
uint8_t data_[18432];
159-
size_t offset_;
160-
size_t body_offset_;
161-
};
162-
163122
class Connection : ObjectWrap {
164123
public:
165124
static void Initialize(v8::Handle<v8::Object> target);
@@ -221,6 +180,10 @@ class Connection : ObjectWrap {
221180
static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg);
222181
#endif
223182

183+
static void OnClientHello(void* arg,
184+
const ClientHelloParser::ClientHello& hello);
185+
static void OnClientHelloParseEnd(void* arg);
186+
224187
int HandleBIOError(BIO* bio, const char* func, int rv);
225188

226189
enum ZeroStatus {
@@ -244,10 +207,11 @@ class Connection : ObjectWrap {
244207
return ss;
245208
}
246209

247-
Connection() : ObjectWrap(), hello_parser_(this) {
210+
Connection() : ObjectWrap(), hello_offset_(0) {
248211
bio_read_ = bio_write_ = NULL;
249212
ssl_ = NULL;
250213
next_sess_ = NULL;
214+
hello_parser_.Start(OnClientHello, OnClientHelloParseEnd, this);
251215
}
252216

253217
~Connection() {
@@ -285,6 +249,9 @@ class Connection : ObjectWrap {
285249
bool is_server_; /* coverity[member_decl] */
286250
SSL_SESSION* next_sess_;
287251

252+
uint8_t hello_data_[18432];
253+
size_t hello_offset_;
254+
288255
friend class ClientHelloParser;
289256
friend class SecureContext;
290257
};

src/node_crypto_clienthello-inl.h

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
#ifndef SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_
23+
#define SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_
24+
25+
#include <assert.h>
26+
27+
namespace node {
28+
29+
inline void ClientHelloParser::Reset() {
30+
frame_len_ = 0;
31+
body_offset_ = 0;
32+
extension_offset_ = 0;
33+
session_size_ = 0;
34+
session_id_ = NULL;
35+
tls_ticket_size_ = -1;
36+
tls_ticket_ = NULL;
37+
}
38+
39+
inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb,
40+
ClientHelloParser::OnEndCb onend_cb,
41+
void* onend_arg) {
42+
if (!IsEnded())
43+
return;
44+
Reset();
45+
46+
assert(onhello_cb != NULL);
47+
48+
state_ = kWaiting;
49+
onhello_cb_ = onhello_cb;
50+
onend_cb_ = onend_cb;
51+
cb_arg_ = onend_arg;
52+
}
53+
54+
inline void ClientHelloParser::End() {
55+
if (state_ == kEnded)
56+
return;
57+
state_ = kEnded;
58+
if (onend_cb_ != NULL) {
59+
onend_cb_(cb_arg_);
60+
onend_cb_ = NULL;
61+
}
62+
}
63+
64+
inline bool ClientHelloParser::IsEnded() const {
65+
return state_ == kEnded;
66+
}
67+
68+
inline bool ClientHelloParser::IsPaused() const {
69+
return state_ == kPaused;
70+
}
71+
72+
} // namespace node
73+
74+
#endif // SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_

0 commit comments

Comments
 (0)