mirror of https://github.com/lukechilds/node.git
Browse Source
Share ClientHelloParser code between `tls_wrap.cc` and `node_crypto.cc`. fix #5959v0.11.5-release
Fedor Indutny
12 years ago
8 changed files with 536 additions and 414 deletions
@ -0,0 +1,74 @@ |
|||
// 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.
|
|||
|
|||
#ifndef SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_ |
|||
#define SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_ |
|||
|
|||
#include <assert.h> |
|||
|
|||
namespace node { |
|||
|
|||
inline void ClientHelloParser::Reset() { |
|||
frame_len_ = 0; |
|||
body_offset_ = 0; |
|||
extension_offset_ = 0; |
|||
session_size_ = 0; |
|||
session_id_ = NULL; |
|||
tls_ticket_size_ = -1; |
|||
tls_ticket_ = NULL; |
|||
} |
|||
|
|||
inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb, |
|||
ClientHelloParser::OnEndCb onend_cb, |
|||
void* onend_arg) { |
|||
if (!IsEnded()) |
|||
return; |
|||
Reset(); |
|||
|
|||
assert(onhello_cb != NULL); |
|||
|
|||
state_ = kWaiting; |
|||
onhello_cb_ = onhello_cb; |
|||
onend_cb_ = onend_cb; |
|||
cb_arg_ = onend_arg; |
|||
} |
|||
|
|||
inline void ClientHelloParser::End() { |
|||
if (state_ == kEnded) |
|||
return; |
|||
state_ = kEnded; |
|||
if (onend_cb_ != NULL) { |
|||
onend_cb_(cb_arg_); |
|||
onend_cb_ = NULL; |
|||
} |
|||
} |
|||
|
|||
inline bool ClientHelloParser::IsEnded() const { |
|||
return state_ == kEnded; |
|||
} |
|||
|
|||
inline bool ClientHelloParser::IsPaused() const { |
|||
return state_ == kPaused; |
|||
} |
|||
|
|||
} // namespace node
|
|||
|
|||
#endif // SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_
|
@ -0,0 +1,240 @@ |
|||
// 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; |
|||
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 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
|
@ -0,0 +1,124 @@ |
|||
// 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.
|
|||
|
|||
#ifndef SRC_NODE_CRYPTO_CLIENTHELLO_H_ |
|||
#define SRC_NODE_CRYPTO_CLIENTHELLO_H_ |
|||
|
|||
#include "node.h" |
|||
|
|||
#include <stddef.h> // size_t |
|||
#include <stdlib.h> // NULL |
|||
|
|||
namespace node { |
|||
|
|||
class ClientHelloParser { |
|||
public: |
|||
ClientHelloParser() : state_(kEnded), |
|||
onhello_cb_(NULL), |
|||
onend_cb_(NULL), |
|||
cb_arg_(NULL) { |
|||
Reset(); |
|||
} |
|||
|
|||
class ClientHello { |
|||
public: |
|||
ClientHello() { |
|||
} |
|||
|
|||
inline uint8_t session_size() const { return session_size_; } |
|||
inline const uint8_t* session_id() const { return session_id_; } |
|||
inline bool has_ticket() const { return has_ticket_; } |
|||
|
|||
private: |
|||
uint8_t session_size_; |
|||
const uint8_t* session_id_; |
|||
bool has_ticket_; |
|||
|
|||
friend class ClientHelloParser; |
|||
}; |
|||
|
|||
typedef void (*OnHelloCb)(void* arg, const ClientHello& hello); |
|||
typedef void (*OnEndCb)(void* arg); |
|||
|
|||
void Parse(const uint8_t* data, size_t avail); |
|||
|
|||
inline void Reset(); |
|||
inline void Start(OnHelloCb onhello_cb, OnEndCb onend_cb, void* onend_arg); |
|||
inline void End(); |
|||
inline bool IsPaused() const; |
|||
inline bool IsEnded() const; |
|||
|
|||
private: |
|||
static const uint8_t kSSL2TwoByteHeaderBit = 0x80; |
|||
static const uint8_t kSSL2HeaderMask = 0x3f; |
|||
static const size_t kMaxTLSFrameLen = 16 * 1024 + 5; |
|||
static const size_t kMaxSSLExFrameLen = 32 * 1024; |
|||
|
|||
enum ParseState { |
|||
kWaiting, |
|||
kTLSHeader, |
|||
kSSL2Header, |
|||
kPaused, |
|||
kEnded |
|||
}; |
|||
|
|||
enum FrameType { |
|||
kChangeCipherSpec = 20, |
|||
kAlert = 21, |
|||
kHandshake = 22, |
|||
kApplicationData = 23, |
|||
kOther = 255 |
|||
}; |
|||
|
|||
enum HandshakeType { |
|||
kClientHello = 1 |
|||
}; |
|||
|
|||
enum ExtensionType { |
|||
kTLSSessionTicket = 35 |
|||
}; |
|||
|
|||
bool ParseRecordHeader(const uint8_t* data, size_t avail); |
|||
void ParseHeader(const uint8_t* data, size_t avail); |
|||
void ParseExtension(ExtensionType type, |
|||
const uint8_t* data, |
|||
size_t len); |
|||
bool ParseTLSClientHello(const uint8_t* data, size_t avail); |
|||
#ifdef OPENSSL_NO_SSL2 |
|||
bool ParseSSL2ClientHello(const uint8_t* data, size_t avail); |
|||
#endif // OPENSSL_NO_SSL2
|
|||
|
|||
ParseState state_; |
|||
OnHelloCb onhello_cb_; |
|||
OnEndCb onend_cb_; |
|||
void* cb_arg_; |
|||
size_t frame_len_; |
|||
size_t body_offset_; |
|||
size_t extension_offset_; |
|||
uint8_t session_size_; |
|||
const uint8_t* session_id_; |
|||
uint16_t tls_ticket_size_; |
|||
const uint8_t* tls_ticket_; |
|||
}; |
|||
|
|||
} // namespace node
|
|||
|
|||
#endif // SRC_NODE_CRYPTO_CLIENTHELLO_H_
|
Loading…
Reference in new issue