]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
First implementation for parsing SSLv2/v3 handshake messages and extracing SSL features
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Sun, 27 Mar 2016 17:36:18 +0000 (20:36 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Sun, 27 Mar 2016 17:36:18 +0000 (20:36 +0300)
src/security/Handshake.cc
src/security/Handshake.h

index 08c4033a4dbf8084cee8e69de6cc692480b54ed0..f0e05ab27139d6f9ff7abeb2dead42824d760c6a 100644 (file)
@@ -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
index 3ed2261c665004f4f0f05c75c8f79359a0c8cb31..1f3a2339fad4b3b27dd4fbb60944f93636ca705f 100644 (file)
@@ -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<TlsDetails> 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<uint16_t> ciphers;
+    std::list<uint16_t> 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;
 };
 
 }