]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
add forgotten Handshake.{cc,h} files
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Sat, 19 Mar 2016 19:07:09 +0000 (21:07 +0200)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Sat, 19 Mar 2016 19:07:09 +0000 (21:07 +0200)
src/security/Handshake.cc [new file with mode: 0644]
src/security/Handshake.h [new file with mode: 0644]

diff --git a/src/security/Handshake.cc b/src/security/Handshake.cc
new file mode 100644 (file)
index 0000000..08c4033
--- /dev/null
@@ -0,0 +1,248 @@
+#include "squid.h"
+#include "parser/BinaryTokenizer.h"
+#include "security/Handshake.h"
+#if USE_OPENSSL
+#include "ssl/support.h"
+#endif
+
+Security::FieldGroup::FieldGroup(BinaryTokenizer &tk, const char *description) {
+    tk.context = description;
+}
+
+void
+Security::FieldGroup::commit(BinaryTokenizer &tk) {
+    tk.commit();
+    tk.context = "";
+}
+
+Security::ProtocolVersion::ProtocolVersion(BinaryTokenizer &tk):
+    vMajor(tk.uint8(".vMajor")),
+    vMinor(tk.uint8(".vMinor"))
+{
+}
+
+Security::TLSPlaintext::TLSPlaintext(BinaryTokenizer &tk):
+    FieldGroup(tk, "TLSPlaintext"),
+    type(tk.uint8(".type")),
+    version(tk),
+    length(tk.uint16(".length")),
+    fragment(tk.area(length, ".fragment"))
+{
+    commit(tk);
+}
+
+Security::Handshake::Handshake(BinaryTokenizer &tk):
+    FieldGroup(tk, "Handshake"),
+    msg_type(tk.uint8(".msg_type")),
+    length(tk.uint24(".length")),
+    body(tk.area(length, ".body"))
+{
+    commit(tk);
+}
+
+Security::Alert::Alert(BinaryTokenizer &tk):
+    FieldGroup(tk, "Alert"),
+    level(tk.uint8(".level")),
+    description(tk.uint8(".description"))
+{
+    commit(tk);
+}
+
+Security::P24String::P24String(BinaryTokenizer &tk, const char *description):
+    FieldGroup(tk, description),
+    length(tk.uint24(".length")),
+    body(tk.area(length, ".body"))
+{
+    commit(tk);
+}
+
+/// debugging helper to print various parsed records and messages
+class DebugFrame
+{
+public:
+    DebugFrame(const char *aName, uint64_t aType, uint64_t aSize):
+        name(aName), type(aType), size(aSize) {}
+
+    const char *name;
+    uint64_t type;
+    uint64_t size;
+};
+
+inline std::ostream &
+operator <<(std::ostream &os, const DebugFrame &frame)
+{
+    return os << frame.size << "-byte type-" << frame.type << ' ' << frame.name;
+}
+
+/// parses a single TLS Record Layer frame
+void
+Security::HandshakeParser::parseRecord()
+{
+    const TLSPlaintext record(tkRecords);
+
+    Must(record.length <= (1 << 14)); // RFC 5246: length MUST NOT exceed 2^14
+
+    // RFC 5246: MUST NOT send zero-length [non-application] fragments
+    Must(record.length || record.type == ContentType::ctApplicationData);
+
+    if (currentContentType != record.type) {
+        Must(tkMessages.atEnd()); // no currentContentType leftovers
+        fragments = record.fragment;
+        tkMessages.reset(fragments);
+        currentContentType = record.type;
+    } else {
+        fragments.append(record.fragment);
+        tkMessages.reinput(fragments);
+        tkMessages.rollback();
+    }
+    parseMessages();
+}
+
+/// parses one or more "higher-level protocol" frames of currentContentType
+void
+Security::HandshakeParser::parseMessages()
+{
+    debugs(83, 7, DebugFrame("fragments", currentContentType, fragments.length()));
+    while (!tkMessages.atEnd()) {
+        switch (currentContentType) {
+        case ContentType::ctChangeCipherSpec:
+            parseChangeCipherCpecMessage();
+            continue;
+        case ContentType::ctAlert:
+            parseAlertMessage();
+            continue;
+        case ContentType::ctHandshake:
+            parseHandshakeMessage();
+            continue;
+        case ContentType::ctApplicationData:
+            parseApplicationDataMessage();
+            continue;
+        }
+        skipMessage("unknown ContentType msg");
+    }
+}
+
+void
+Security::HandshakeParser::parseChangeCipherCpecMessage()
+{
+    Must(currentContentType == ContentType::ctChangeCipherSpec);
+    // we are currently ignoring Change Cipher Spec Protocol messages
+    // Everything after this message may be is encrypted
+    // The continuing parsing is pointless, abort here and set parseDone
+    skipMessage("ChangeCipherCpec msg");
+    ressumingSession = true;
+    parseDone = true;
+}
+
+void
+Security::HandshakeParser::parseAlertMessage()
+{
+    Must(currentContentType == ContentType::ctAlert);
+    const Alert alert(tkMessages);
+    debugs(83, 3, "level " << alert.level << " description " << alert.description);
+    // we are currently ignoring Alert Protocol messages
+}
+
+void
+Security::HandshakeParser::parseHandshakeMessage()
+{
+    Must(currentContentType == ContentType::ctHandshake);
+
+    const Handshake message(tkMessages);
+
+    switch (message.msg_type) {
+        case HandshakeType::hskServerHello:
+            Must(state < atHelloReceived);
+            // TODO: Parse ServerHello in message.body; extract version/session
+            // If the server is resuming a session, stop parsing w/o certificates
+            // because all subsequent [Finished] messages will be encrypted, right?
+            state = atHelloReceived;
+            return;
+        case HandshakeType::hskCertificate:
+            Must(state < atCertificatesReceived);
+            parseServerCertificates(message.body);
+            state = atCertificatesReceived;
+            return;
+        case HandshakeType::hskServerHelloDone:
+            Must(state < atHelloDoneReceived);
+            // zero-length
+            state = atHelloDoneReceived;
+            parseDone = true;
+            return;
+    }
+    debugs(83, 5, "ignoring " <<
+           DebugFrame("handshake msg", message.msg_type, message.length));
+}
+
+void
+Security::HandshakeParser::parseApplicationDataMessage()
+{
+    Must(currentContentType == ContentType::ctApplicationData);
+    skipMessage("app data");
+}
+
+void
+Security::HandshakeParser::skipMessage(const char *description)
+{
+    // tkMessages/fragments can only contain messages of the same ContentType.
+    // To skip a message, we can and should skip everything we have [left]. If
+    // we have partial messages, debugging will mislead about their boundaries.
+    tkMessages.skip(tkMessages.leftovers().length(), description);
+    tkMessages.commit();
+}
+
+/// parseServerHelloTry() wrapper that maintains parseDone/parseError state
+bool
+Security::HandshakeParser::parseServerHello(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)
+{
+    typedef const unsigned char *x509Data;
+    const x509Data x509Start = reinterpret_cast<x509Data>(raw.rawContent());
+    x509Data x509Pos = x509Start;
+    X509 *x509 = d2i_X509(nullptr, &x509Pos, raw.length());
+    Must(x509); // successfully parsed
+    Must(x509Pos == x509Start + raw.length()); // no leftovers
+    return x509;
+}
+
+void
+Security::HandshakeParser::parseServerCertificates(const SBuf &raw)
+{
+    BinaryTokenizer tkList(raw);
+    const P24String list(tkList, "CertificateList");
+    Must(tkList.atEnd()); // no leftovers after all certificates
+
+    BinaryTokenizer tkItems(list.body);
+    while (!tkItems.atEnd()) {
+        const P24String item(tkItems, "Certificate");
+        X509 *cert = ParseCertificate(item.body);
+        if (!serverCertificates.get())
+            serverCertificates.reset(sk_X509_new_null());
+        sk_X509_push(serverCertificates.get(), cert);
+        debugs(83, 7, "parsed " << sk_X509_num(serverCertificates.get()) << " certificates so far");
+    }
+
+}
+#endif
diff --git a/src/security/Handshake.h b/src/security/Handshake.h
new file mode 100644 (file)
index 0000000..3ed2261
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID_SECURITY_HANDSHAKE_H
+#define SQUID_SECURITY_HANDSHAKE_H
+
+#include "fd.h"
+#include "parser/BinaryTokenizer.h"
+#include "sbuf/SBuf.h"
+#if USE_OPENSSL
+#include "ssl/gadgets.h"
+#endif
+
+namespace Security
+{
+
+// The Transport Layer Security (TLS) Protocol, Version 1.2
+
+/// Helper class to debug parsing of various TLS structures
+class FieldGroup
+{
+public:
+    FieldGroup(BinaryTokenizer &tk, const char *description); ///< starts parsing
+
+    void commit(BinaryTokenizer &tk); ///< commits successful parsing results
+};
+
+/// TLS Record Layer's content types from RFC 5246 Section 6.2.1
+enum ContentType {
+    ctChangeCipherSpec = 20,
+    ctAlert = 21,
+    ctHandshake = 22,
+    ctApplicationData = 23
+};
+
+/// TLS Record Layer's protocol version from RFC 5246 Section 6.2.1
+struct ProtocolVersion
+{
+    explicit ProtocolVersion(BinaryTokenizer &tk);
+
+    // the "v" prefix works around environments that #define major and minor
+    uint8_t vMajor;
+    uint8_t vMinor;
+};
+
+/// TLS Record Layer's frame from RFC 5246 Section 6.2.1.
+struct TLSPlaintext: public FieldGroup
+{
+    explicit TLSPlaintext(BinaryTokenizer &tk);
+
+    uint8_t type; ///< Rfc5246::ContentType
+    ProtocolVersion version;
+    uint16_t length;
+    SBuf fragment; ///< exactly length bytes
+};
+
+/// TLS Handshake protocol's handshake types from RFC 5246 Section 7.4
+enum HandshakeType {
+    hskServerHello = 2,
+    hskCertificate = 11,
+    hskServerHelloDone = 14
+};
+
+/// TLS Handshake Protocol frame from RFC 5246 Section 7.4.
+struct Handshake: public FieldGroup
+{
+    explicit Handshake(BinaryTokenizer &tk);
+
+    uint32_t msg_type: 8; ///< HandshakeType
+    uint32_t length: 24;
+    SBuf body; ///< Handshake Protocol message, exactly length bytes
+};
+
+/// TLS Alert protocol frame from RFC 5246 Section 7.2.
+struct Alert: public FieldGroup
+{
+    explicit Alert(BinaryTokenizer &tk);
+    uint8_t level; ///< warning or fatal
+    uint8_t description; ///< close_notify, unexpected_message, etc.
+};
+
+/// Like a Pascal "length-first" string but with a 3-byte length field.
+/// Used for (undocumented in RRC 5246?) Certificate and ASN1.Cert encodings.
+struct P24String: public FieldGroup
+{
+    explicit P24String(BinaryTokenizer &tk, const char *description);
+
+    uint32_t length;  // bytes in body (stored using 3 bytes, not 4!)
+    SBuf body; ///< exactly length bytes
+};
+
+/// Incremental SSL Handshake parser.
+class HandshakeParser {
+public:
+    /// The parsing states
+    typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived, atCertificatesReceived, atHelloDoneReceived, atNstReceived, atCcsReceived, atFinishReceived} ParserState;
+
+    HandshakeParser(): state(atHelloNone), ressumingSession(false), parseDone(false), parseError(false), currentContentType(0), unParsedContent(0), parsingPos(0), currentMsg(0), currentMsgSize(0), certificatesMsgPos(0), certificatesMsgSize(0) {}
+
+    /// Parses the initial sequence of raw bytes sent by the SSL server.
+    /// Returns true upon successful completion (HelloDone or Finished received).
+    /// Otherwise, returns false (and sets parseError to true on errors).
+    bool parseServerHello(const SBuf &data);
+
+#if USE_OPENSSL
+    Ssl::X509_STACK_Pointer serverCertificates; ///< parsed certificates chain
+#endif
+
+    ParserState state; ///< current parsing state.
+
+    bool ressumingSession; ///< True if this is a resumming session
+
+    bool parseDone; ///< The parser finishes its job
+    bool parseError; ///< Set to tru by parse on parse error.
+
+private:
+    unsigned int currentContentType; ///< The current SSL record content type
+    size_t unParsedContent; ///< The size of current SSL record, which is not parsed yet
+    size_t parsingPos; ///< The parsing position from the beginning of parsed data
+    size_t currentMsg; ///< The current handshake message possition from the beginning of parsed data
+    size_t currentMsgSize; ///< The current handshake message size.
+
+    size_t certificatesMsgPos; ///< The possition of certificates message from the beggining of parsed data
+    size_t certificatesMsgSize; ///< The size of certificates message
+
+private:
+    void parseServerHelloTry();
+
+    void parseRecord();
+    void parseMessages();
+
+    void parseChangeCipherCpecMessage();
+    void parseAlertMessage();
+    void parseHandshakeMessage();
+    void parseApplicationDataMessage();
+    void skipMessage(const char *msgType);
+
+    void parseServerCertificates(const SBuf &raw);
+#if USE_OPENSSL
+    static X509 *ParseCertificate(const SBuf &raw);
+#endif
+
+    /// concatenated TLSPlaintext.fragments of TLSPlaintext.type
+    SBuf fragments;
+
+    BinaryTokenizer tkRecords; // TLS record layer (parsing uninterpreted data)
+    BinaryTokenizer tkMessages; // TLS message layer (parsing fragments)
+};
+
+}
+
+#endif // SQUID_SECURITY_HANDSHAKE_H