From: Christos Tsantilas Date: Sun, 27 Mar 2016 17:36:18 +0000 (+0300) Subject: First implementation for parsing SSLv2/v3 handshake messages and extracing SSL features X-Git-Tag: SQUID_4_0_11~29^2~34 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f0f2a850d9d49c9ec4405a57b91653f13730bb16;p=thirdparty%2Fsquid.git First implementation for parsing SSLv2/v3 handshake messages and extracing SSL features --- diff --git a/src/security/Handshake.cc b/src/security/Handshake.cc index 08c4033a4d..f0e05ab271 100644 --- a/src/security/Handshake.cc +++ b/src/security/Handshake.cc @@ -21,13 +21,23 @@ Security::ProtocolVersion::ProtocolVersion(BinaryTokenizer &tk): { } +Security::ProtocolVersion::ProtocolVersion(uint8_t maj, uint8_t min): + vMajor(maj), + vMinor(min) +{ +} + Security::TLSPlaintext::TLSPlaintext(BinaryTokenizer &tk): FieldGroup(tk, "TLSPlaintext"), type(tk.uint8(".type")), - version(tk), - length(tk.uint16(".length")), - fragment(tk.area(length, ".fragment")) + version((type & 0x80) ? ProtocolVersion(2, 0): ProtocolVersion(tk)) { + if (type & 0x80){ // V2 compatible protocol + length = tk.uint8(".length"); + } else { //TLS protocol + length = tk.uint16(".length"); + } + fragment = tk.area(length, ".fragment"); commit(tk); } @@ -56,6 +66,60 @@ Security::P24String::P24String(BinaryTokenizer &tk, const char *description): commit(tk); } +Security::P16String::P16String(BinaryTokenizer &tk, const char *description): + FieldGroup(tk, description), + length(tk.uint16(".length")), + body(tk.area(length, ".body")) +{ + commit(tk); +} + +Security::P8String::P8String(BinaryTokenizer &tk, const char *description): + FieldGroup(tk, description), + length(tk.uint8(".length")), + body(tk.area(length, ".body")) +{ + commit(tk); +} + +Security::Extension::Extension(BinaryTokenizer &tk): + FieldGroup(tk, "Extension"), + type(tk.uint16(".type")), + length(tk.uint16(".length")), + body(tk.area(length, ".body")) +{ + commit(tk); +} + +//The SNI extension has the type 0 (extType == 0) +// RFC6066 sections 3, 10.2 +// The two first bytes indicates the length of the SNI data +// The next byte is the hostname type, it should be '0' for normal hostname +// The 3rd and 4th bytes are the length of the hostname +Security::SniExtension::SniExtension(BinaryTokenizer &tk): + FieldGroup(tk, "Sni"), + listLength(tk.uint16(".listLength")), + type(tk.uint8(".type")) +{ + if (type == 0) { + P16String aName(tk, "server name"); + serverName = aName.body; + } else + tk.skip(listLength - 1, "list without list type"); + commit(tk); +} + +Security::TlsDetails::TlsDetails(): + tlsVersion(-1), + tlsSupportedVersion(-1), + compressMethod(-1), + doHeartBeats(true), + tlsTicketsExtension(false), + hasTlsTicket(false), + tlsStatusRequest(false) +{ +} + /// debugging helper to print various parsed records and messages class DebugFrame { @@ -85,6 +149,11 @@ Security::HandshakeParser::parseRecord() // RFC 5246: MUST NOT send zero-length [non-application] fragments Must(record.length || record.type == ContentType::ctApplicationData); + if (details == NULL) { + details = new TlsDetails; + details->tlsVersion = (record.version.vMajor & 0xFF) << 8 | (record.version.vMinor & 0xFF); + } + if (currentContentType != record.type) { Must(tkMessages.atEnd()); // no currentContentType leftovers fragments = record.fragment; @@ -117,6 +186,11 @@ Security::HandshakeParser::parseMessages() case ContentType::ctApplicationData: parseApplicationDataMessage(); continue; + case ContentType::ctVersion2: { + SBuf raw; //Should fixed to body after record + parseVersion2HandshakeMessage(raw); + continue; + } } skipMessage("unknown ContentType msg"); } @@ -151,6 +225,13 @@ Security::HandshakeParser::parseHandshakeMessage() const Handshake message(tkMessages); switch (message.msg_type) { + case HandshakeType::hskClientHello: + Must(state < atHelloReceived); + // TODO: Parse ClientHello in message.body; extract version/session + Security::HandshakeParser::parseClientHelloHandshakeMessage(message.body); + state = atHelloReceived; + parseDone = true; + return; case HandshakeType::hskServerHello: Must(state < atHelloReceived); // TODO: Parse ServerHello in message.body; extract version/session @@ -181,6 +262,122 @@ Security::HandshakeParser::parseApplicationDataMessage() skipMessage("app data"); } +void +Security::HandshakeParser::parseVersion2HandshakeMessage(const SBuf &raw) +{ + BinaryTokenizer tkHsk(raw); + Must(details); + + details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion"); + tkHsk.commit(); + P16String ciphers(tkHsk, "Ciphers list"); + // TODO: retrieve ciphers list + P16String session(tkHsk, "Session ID"); + details->sessionId = session.body; + P16String challenge(tkHsk, "Challenge"); +} + +void +Security::HandshakeParser::parseClientHelloHandshakeMessage(const SBuf &raw) +{ + BinaryTokenizer tkHsk(raw); + Must(details); + + details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion"); + details->clientRandom = tkHsk.area(SQUID_TLS_RANDOM_SIZE, "Client Random"); + P8String session(tkHsk, "Session ID"); + details->sessionId = session.body; + P16String ciphers(tkHsk, "Ciphers list"); + // TODO: retrieve ciphers list + P8String compression(tkHsk, "Compression methods"); + details->compressMethod = compression.length > 0 ? 1 : 0; // Only deflate supported here. + P16String extensions(tkHsk, "Extensions List"); + parseExtensions(extensions.body); +} + +void +Security::HandshakeParser::parseExtensions(const SBuf &raw) +{ + Must(details); + BinaryTokenizer tk(raw); + while (!tk.atEnd()) { + Extension extension(tk); + details->extensions.push_back(extension.type); + + switch(extension.type) { + case 0: { //The SNI extension, RFC6066 sections 3, 10.2 + BinaryTokenizer tkSNI(extension.body); + SniExtension sni(tkSNI); + details->serverName = sni.serverName; + } + break; + case 5: // RFC6066 sections 8, 10.2 + details->tlsStatusRequest = true; + break; + case 15:// The heartBeats, RFC6520 + details->doHeartBeats = true; + break; + case 16: { // Application-Layer Protocol Negotiation Extension, RFC7301 + BinaryTokenizer tkAPN(extension.body); + P16String apn(tkAPN, "APN extension"); + details->tlsAppLayerProtoNeg = apn.body; + } + break; + case 35: //SessionTicket TLS Extension RFC5077 + details->tlsTicketsExtension = true; + if (extension.length) + details->hasTlsTicket = true; + case 13172: //Next Protocol Negotiation Extension, (expired draft?) + default: + break; + } + } +} + +void +Security::HandshakeParser::parseCiphers(const SBuf &raw) +{ + Must(details); + BinaryTokenizer tk(raw); + while (!tk.atEnd()) { + const uint16_t cipher = tk.uint16("cipher"); + details->ciphers.push_back(cipher); + } +} + +void +Security::HandshakeParser::parseV23Ciphers(const SBuf &raw) +{ + Must(details); + BinaryTokenizer tk(raw); + while (!tk.atEnd()) { + // The v2 hello messages cipher has 3 bytes. + // The v2 cipher has the first byte not null + // We are supporting only v3 message so we + // are ignoring v2 ciphers + const uint8_t prefix = tk.uint8("prefix"); + const uint16_t cipher = tk.uint16("cipher"); + if (prefix == 0) + details->ciphers.push_back(cipher); + } +} + +void +Security::HandshakeParser::parseServerHelloHandshakeMessage(const SBuf &raw) +{ + BinaryTokenizer tkHsk(raw); + Must(details); + + details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion"); + tkHsk.commit(); + details->clientRandom = tkHsk.area(SQUID_TLS_RANDOM_SIZE, "Client Random"); + tkHsk.commit(); + P8String session(tkHsk, "Session ID"); + details->sessionId = session.body; + P16String extensions(tkHsk, "Extensions List"); + parseExtensions(extensions.body); +} + void Security::HandshakeParser::skipMessage(const char *description) { @@ -214,6 +411,28 @@ Security::HandshakeParser::parseServerHello(const SBuf &data) return false; } +bool +Security::HandshakeParser::parseClientHello(const SBuf &data) +{ + try { + tkRecords.reinput(data); // data contains _everything_ read so far + tkRecords.rollback(); + while (!tkRecords.atEnd() && !parseDone) + parseRecord(); + debugs(83, 7, "success; done: " << parseDone); + return parseDone; + } + catch (const BinaryTokenizer::InsufficientInput &) { + debugs(83, 5, "need more data"); + Must(!parseError); + } + catch (const std::exception &ex) { + debugs(83, 2, "parsing error: " << ex.what()); + parseError = true; + } + return false; +} + #if USE_OPENSSL X509 * Security::HandshakeParser::ParseCertificate(const SBuf &raw) @@ -244,5 +463,10 @@ Security::HandshakeParser::parseServerCertificates(const SBuf &raw) debugs(83, 7, "parsed " << sk_X509_num(serverCertificates.get()) << " certificates so far"); } +} +#else +void +Security::HandshakeParser::parseServerCertificates(const SBuf &raw) +{ } #endif diff --git a/src/security/Handshake.h b/src/security/Handshake.h index 3ed2261c66..1f3a2339fa 100644 --- a/src/security/Handshake.h +++ b/src/security/Handshake.h @@ -9,6 +9,7 @@ #ifndef SQUID_SECURITY_HANDSHAKE_H #define SQUID_SECURITY_HANDSHAKE_H +#include "base/RefCount.h" #include "fd.h" #include "parser/BinaryTokenizer.h" #include "sbuf/SBuf.h" @@ -32,6 +33,7 @@ public: /// TLS Record Layer's content types from RFC 5246 Section 6.2.1 enum ContentType { + ctVersion2 = 128, ctChangeCipherSpec = 20, ctAlert = 21, ctHandshake = 22, @@ -42,6 +44,7 @@ enum ContentType { struct ProtocolVersion { explicit ProtocolVersion(BinaryTokenizer &tk); + ProtocolVersion(uint8_t, uint8_t); // the "v" prefix works around environments that #define major and minor uint8_t vMajor; @@ -61,6 +64,7 @@ struct TLSPlaintext: public FieldGroup /// TLS Handshake protocol's handshake types from RFC 5246 Section 7.4 enum HandshakeType { + hskClientHello = 1, hskServerHello = 2, hskCertificate = 11, hskServerHelloDone = 14 @@ -94,6 +98,66 @@ struct P24String: public FieldGroup SBuf body; ///< exactly length bytes }; +/// A "length-first" string but with a 1-byte length field. +/// Used for storing small strings/octets like (session keys) +struct P8String: public FieldGroup +{ + explicit P8String(BinaryTokenizer &tk, const char *description); + + uint8_t length; // bytes in body (stored using 1 byte) + SBuf body; ///< exactly length bytes +}; + +/// A "length-first" string but with a 2-byte length field. +/// Used for storing octets (documented in RRC 5246?) +struct P16String: public FieldGroup +{ + explicit P16String(BinaryTokenizer &tk, const char *description); + + uint16_t length; // bytes in body (stored using 2 bytes) + SBuf body; ///< exactly length bytes +}; + +struct Extension: public FieldGroup +{ + explicit Extension(BinaryTokenizer &tk); + uint16_t type; + uint16_t length; + SBuf body; +}; + +struct SniExtension: public FieldGroup +{ + explicit SniExtension(BinaryTokenizer &tk); + uint16_t listLength; + uint8_t type; + SBuf serverName; +}; + +#define SQUID_TLS_RANDOM_SIZE 32 + +class TlsDetails: public RefCountable +{ +public: + typedef RefCount Pointer; + + TlsDetails(); + int tlsVersion; ///< The TLS hello message version + int tlsSupportedVersion; ///< The requested/used TLS version + int compressMethod; ///< The requested/used compressed method + SBuf serverName; ///< The SNI hostname, if any + bool doHeartBeats; + bool tlsTicketsExtension; ///< whether TLS tickets extension is enabled + bool hasTlsTicket; ///< whether a TLS ticket is included + bool tlsStatusRequest; ///< whether the TLS status request extension is set + SBuf tlsAppLayerProtoNeg; ///< The value of the TLS application layer protocol extension if it is enabled + /// The client random number + SBuf clientRandom; + SBuf sessionId; + std::list ciphers; + std::list extensions; +}; + /// Incremental SSL Handshake parser. class HandshakeParser { public: @@ -107,6 +171,11 @@ public: /// Otherwise, returns false (and sets parseError to true on errors). bool parseServerHello(const SBuf &data); + /// Parses the initial sequence of raw bytes sent by the SSL client. + /// Returns true upon successful completion (HelloDone or Finished received). + /// Otherwise, returns false (and sets parseError to true on errors). + bool parseClientHello(const SBuf &data); + #if USE_OPENSSL Ssl::X509_STACK_Pointer serverCertificates; ///< parsed certificates chain #endif @@ -140,6 +209,14 @@ private: void parseApplicationDataMessage(); void skipMessage(const char *msgType); + void parseVersion2HandshakeMessage(const SBuf &raw); + void parseClientHelloHandshakeMessage(const SBuf &raw); + void parseServerHelloHandshakeMessage(const SBuf &raw); + + void parseExtensions(const SBuf &raw); + void parseCiphers(const SBuf &raw); + void parseV23Ciphers(const SBuf &raw); + void parseServerCertificates(const SBuf &raw); #if USE_OPENSSL static X509 *ParseCertificate(const SBuf &raw); @@ -150,6 +227,8 @@ private: BinaryTokenizer tkRecords; // TLS record layer (parsing uninterpreted data) BinaryTokenizer tkMessages; // TLS message layer (parsing fragments) + + TlsDetails::Pointer details; }; }