mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
7.1 KiB
265 lines
7.1 KiB
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
#include "node_crypto_clienthello.h"
|
|
#include "node_crypto_clienthello-inl.h"
|
|
#include "node_buffer.h" // Buffer
|
|
|
|
namespace node {
|
|
|
|
void ClientHelloParser::Parse(const uint8_t* data, size_t avail) {
|
|
switch (state_) {
|
|
case kWaiting:
|
|
if (!ParseRecordHeader(data, avail))
|
|
break;
|
|
// Fall through
|
|
case kTLSHeader:
|
|
case kSSL2Header:
|
|
ParseHeader(data, avail);
|
|
break;
|
|
case kPaused:
|
|
// Just nop
|
|
case kEnded:
|
|
// Already ended, just ignore it
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
bool ClientHelloParser::ParseRecordHeader(const uint8_t* data, size_t avail) {
|
|
// >= 5 bytes for header parsing
|
|
if (avail < 5)
|
|
return false;
|
|
|
|
if (data[0] == kChangeCipherSpec ||
|
|
data[0] == kAlert ||
|
|
data[0] == kHandshake ||
|
|
data[0] == kApplicationData) {
|
|
frame_len_ = (data[3] << 8) + data[4];
|
|
state_ = kTLSHeader;
|
|
body_offset_ = 5;
|
|
} else {
|
|
#ifdef OPENSSL_NO_SSL2
|
|
frame_len_ = ((data[0] << 8) & kSSL2HeaderMask) + data[1];
|
|
state_ = kSSL2Header;
|
|
if (data[0] & kSSL2TwoByteHeaderBit) {
|
|
// header without padding
|
|
body_offset_ = 2;
|
|
} else {
|
|
// header with padding
|
|
body_offset_ = 3;
|
|
}
|
|
#else
|
|
End();
|
|
return false;
|
|
#endif // OPENSSL_NO_SSL2
|
|
}
|
|
|
|
// Sanity check (too big frame, or too small)
|
|
// Let OpenSSL handle it
|
|
if (frame_len_ >= kMaxTLSFrameLen) {
|
|
End();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) {
|
|
// >= 5 + frame size bytes for frame parsing
|
|
if (body_offset_ + frame_len_ > avail)
|
|
return;
|
|
|
|
// Skip unsupported frames and gather some data from frame
|
|
|
|
// TODO(indutny): Check hello protocol version
|
|
if (data[body_offset_] == kClientHello) {
|
|
if (state_ == kTLSHeader) {
|
|
if (!ParseTLSClientHello(data, avail))
|
|
return End();
|
|
} else if (state_ == kSSL2Header) {
|
|
#ifdef OPENSSL_NO_SSL2
|
|
if (!ParseSSL2ClientHello(data, avail))
|
|
return End();
|
|
#else
|
|
abort(); // Unreachable
|
|
#endif // OPENSSL_NO_SSL2
|
|
} else {
|
|
// We couldn't get here, but whatever
|
|
return End();
|
|
}
|
|
|
|
// Check if we overflowed (do not reply with any private data)
|
|
if (session_id_ == NULL ||
|
|
session_size_ > 32 ||
|
|
session_id_ + session_size_ > data + avail) {
|
|
return End();
|
|
}
|
|
}
|
|
|
|
state_ = kPaused;
|
|
ClientHello hello;
|
|
hello.session_id_ = session_id_;
|
|
hello.session_size_ = session_size_;
|
|
hello.has_ticket_ = tls_ticket_ != NULL && tls_ticket_size_ != 0;
|
|
hello.servername_ = servername_;
|
|
hello.servername_size_ = servername_size_;
|
|
onhello_cb_(cb_arg_, hello);
|
|
}
|
|
|
|
|
|
void ClientHelloParser::ParseExtension(ClientHelloParser::ExtensionType type,
|
|
const uint8_t* data,
|
|
size_t len) {
|
|
// NOTE: In case of anything we're just returning back, ignoring the problem.
|
|
// That's because we're heavily relying on OpenSSL to solve any problem with
|
|
// incoming data.
|
|
switch (type) {
|
|
case kServerName:
|
|
{
|
|
if (len < 2)
|
|
return;
|
|
uint32_t server_names_len = (data[0] << 8) + data[1];
|
|
if (server_names_len + 2 > len)
|
|
return;
|
|
for (size_t offset = 2; offset < 2 + server_names_len; ) {
|
|
if (offset + 3 > len)
|
|
return;
|
|
uint8_t name_type = data[offset];
|
|
if (name_type != kServernameHostname)
|
|
return;
|
|
uint16_t name_len = (data[offset + 1] << 8) + data[offset + 2];
|
|
offset += 3;
|
|
if (offset + name_len > len)
|
|
return;
|
|
servername_ = data + offset;
|
|
servername_size_ = name_len;
|
|
offset += name_len;
|
|
}
|
|
}
|
|
break;
|
|
case kTLSSessionTicket:
|
|
tls_ticket_size_ = len;
|
|
tls_ticket_ = data + len;
|
|
break;
|
|
default:
|
|
// Ignore
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
bool ClientHelloParser::ParseTLSClientHello(const uint8_t* data, size_t avail) {
|
|
const uint8_t* body;
|
|
|
|
// Skip frame header, hello header, protocol version and random data
|
|
size_t session_offset = body_offset_ + 4 + 2 + 32;
|
|
|
|
if (session_offset + 1 >= avail)
|
|
return false;
|
|
|
|
body = data + session_offset;
|
|
session_size_ = *body;
|
|
session_id_ = body + 1;
|
|
|
|
size_t cipher_offset = session_offset + 1 + session_size_;
|
|
|
|
// Session OOB failure
|
|
if (cipher_offset + 1 >= avail)
|
|
return false;
|
|
|
|
uint16_t cipher_len =
|
|
(data[cipher_offset] << 8) + data[cipher_offset + 1];
|
|
size_t comp_offset = cipher_offset + 2 + cipher_len;
|
|
|
|
// Cipher OOB failure
|
|
if (comp_offset >= avail)
|
|
return false;
|
|
|
|
uint8_t comp_len = data[comp_offset];
|
|
size_t extension_offset = comp_offset + 1 + comp_len;
|
|
|
|
// Compression OOB failure
|
|
if (extension_offset > avail)
|
|
return false;
|
|
|
|
// No extensions present
|
|
if (extension_offset == avail)
|
|
return true;
|
|
|
|
size_t ext_off = extension_offset + 2;
|
|
|
|
// Parse known extensions
|
|
while (ext_off < avail) {
|
|
// Extension OOB
|
|
if (ext_off + 4 > avail)
|
|
return false;
|
|
|
|
uint16_t ext_type = (data[ext_off] << 8) + data[ext_off + 1];
|
|
uint16_t ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3];
|
|
ext_off += 4;
|
|
|
|
// Extension OOB
|
|
if (ext_off + ext_len > avail)
|
|
return false;
|
|
|
|
ParseExtension(static_cast<ExtensionType>(ext_type),
|
|
data + ext_off,
|
|
ext_len);
|
|
|
|
ext_off += ext_len;
|
|
}
|
|
|
|
// Extensions OOB failure
|
|
if (ext_off > avail)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#ifdef OPENSSL_NO_SSL2
|
|
bool ClientHelloParser::ParseSSL2ClientHello(const uint8_t* data,
|
|
size_t avail) {
|
|
const uint8_t* body;
|
|
|
|
// Skip header, version
|
|
size_t session_offset = body_offset_ + 3;
|
|
|
|
if (session_offset + 4 < avail) {
|
|
body = data + session_offset;
|
|
|
|
uint16_t ciphers_size = (body[0] << 8) + body[1];
|
|
|
|
if (body + 4 + ciphers_size < data + avail) {
|
|
session_size_ = (body[2] << 8) + body[3];
|
|
session_id_ = body + 4 + ciphers_size;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif // OPENSSL_NO_SSL2
|
|
|
|
} // namespace node
|
|
|