| // 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" // NOLINT(build/include_inline) |
| #include "node_crypto_clienthello-inl.h" |
| |
| namespace node { |
| namespace crypto { |
| |
| void ClientHelloParser::Parse(const uint8_t* data, size_t avail) { |
| switch (state_) { |
| case kWaiting: |
| if (!ParseRecordHeader(data, avail)) |
| break; |
| // Fall through |
| case kTLSHeader: |
| 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 { |
| End(); |
| return false; |
| } |
| |
| // 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) { |
| ClientHello hello; |
| |
| // >= 5 + frame size bytes for frame parsing |
| if (body_offset_ + frame_len_ > avail) |
| return; |
| |
| // Check hello protocol version. Protocol tuples that we know about: |
| // |
| // (3,1) TLS v1.0 |
| // (3,2) TLS v1.1 |
| // (3,3) TLS v1.2 |
| // |
| // Note that TLS v1.3 uses a TLS v1.2 handshake so requires no specific |
| // support here. |
| if (data[body_offset_ + 4] != 0x03 || |
| data[body_offset_ + 5] < 0x01 || |
| data[body_offset_ + 5] > 0x03) { |
| return End(); |
| } |
| |
| if (data[body_offset_] == kClientHello) { |
| if (state_ == kTLSHeader) { |
| if (!ParseTLSClientHello(data, avail)) |
| return End(); |
| } else { |
| // We couldn't get here, but whatever |
| return End(); |
| } |
| |
| // Check if we overflowed (do not reply with any private data) |
| if (session_id_ == nullptr || |
| session_size_ > 32 || |
| session_id_ + session_size_ > data + avail) { |
| return End(); |
| } |
| } |
| |
| state_ = kPaused; |
| hello.session_id_ = session_id_; |
| hello.session_size_ = session_size_; |
| hello.has_ticket_ = tls_ticket_ != nullptr && tls_ticket_size_ != 0; |
| hello.servername_ = servername_; |
| hello.servername_size_ = static_cast<uint8_t>(servername_size_); |
| onhello_cb_(cb_arg_, hello); |
| } |
| |
| |
| void ClientHelloParser::ParseExtension(const uint16_t 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(ext_type, |
| data + ext_off, |
| ext_len); |
| |
| ext_off += ext_len; |
| } |
| |
| // Extensions OOB failure |
| if (ext_off > avail) |
| return false; |
| |
| return true; |
| } |
| |
| } // namespace crypto |
| } // namespace node |