From: Christos Tsantilas Date: Sat, 19 Mar 2016 18:24:53 +0000 (+0200) Subject: Move BinaryTokenizer, Ssl::HandshakeParser and Ssl::Rfc5246::* classes to X-Git-Tag: SQUID_4_0_13~5^2~12 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7c8ee6885f9702ff30dfdfe3edce25c7dd3b3fd6;p=thirdparty%2Fsquid.git Move BinaryTokenizer, Ssl::HandshakeParser and Ssl::Rfc5246::* classes to their own *.cc,*.h files -Move the BinaryTokenizer from ssl/bio.* files to parser/BinaryTokenizer.{cc,h} -Move the Ssl::Handshake and related structures and declarations from ssl/bio.* to security/Handshake.{c,h}, and under the Security namespace (Security::Handshake) -Move the Ssl::Rfc5246::* classes from ssl/bio.* to security/Handshake.{c,h}, under the Security namespace. --- diff --git a/src/Makefile.am b/src/Makefile.am index 448a68e815..c51b6e9aa9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -541,7 +541,6 @@ squid_LDADD = \ ftp/libftp.la \ helper/libhelper.la \ http/libhttp.la \ - parser/libparser.la \ dns/libdns.la \ base/libbase.la \ libsquid.la \ @@ -554,6 +553,7 @@ squid_LDADD = \ anyp/libanyp.la \ comm/libcomm.la \ security/libsecurity.la \ + parser/libparser.la \ eui/libeui.la \ icmp/libicmp.la \ log/liblog.la \ diff --git a/src/parser/BinaryTokenizer.cc b/src/parser/BinaryTokenizer.cc new file mode 100644 index 0000000000..8ee081657d --- /dev/null +++ b/src/parser/BinaryTokenizer.cc @@ -0,0 +1,157 @@ +/* + * Copyright (C) 1996-2015 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. + */ + +/* DEBUG: section 24 SBuf */ + +#include "squid.h" +#include "BinaryTokenizer.h" + +BinaryTokenizer::BinaryTokenizer(): BinaryTokenizer(SBuf()) +{ +} + +BinaryTokenizer::BinaryTokenizer(const SBuf &data): + context(""), + data_(data), + parsed_(0), + syncPoint_(0) +{ +} + +/// debugging helper that prints a "standard" debugs() trailer +#define BinaryTokenizer_tail(size, start) \ + " occupying " << (size) << " bytes @" << (start) << " in " << this; + +/// logs and throws if fewer than size octets remain; no other side effects +void +BinaryTokenizer::want(uint64_t size, const char *description) const +{ + if (parsed_ + size > data_.length()) { + debugs(24, 5, (parsed_ + size - data_.length()) << " more bytes for " << + context << description << BinaryTokenizer_tail(size, parsed_)); + throw InsufficientInput(); + } +} + +/// debugging helper for parsed number fields +void +BinaryTokenizer::got(uint32_t value, uint64_t size, const char *description) const +{ + debugs(24, 7, context << description << '=' << value << + BinaryTokenizer_tail(size, parsed_ - size)); +} + +/// debugging helper for parsed areas/blobs +void +BinaryTokenizer::got(const SBuf &value, uint64_t size, const char *description) const +{ + debugs(24, 7, context << description << '=' << + Raw(nullptr, value.rawContent(), value.length()).hex() << + BinaryTokenizer_tail(size, parsed_ - size)); + +} + +/// debugging helper for skipped fields +void +BinaryTokenizer::skipped(uint64_t size, const char *description) const +{ + debugs(24, 7, context << description << BinaryTokenizer_tail(size, parsed_ - size)); + +} + +/// Returns the next ready-for-shift byte, adjusting the number of parsed bytes. +/// The larger 32-bit return type helps callers shift/merge octets into numbers. +/// This internal method does not perform out-of-bounds checks. +uint32_t +BinaryTokenizer::octet() +{ + // While char may be signed, we view data characters as unsigned, + // which helps to arrive at the right 32-bit return value. + return static_cast(data_[parsed_++]); +} + +void +BinaryTokenizer::reset(const SBuf &data) +{ + *this = BinaryTokenizer(data); +} + +void +BinaryTokenizer::rollback() +{ + parsed_ = syncPoint_; +} + +void +BinaryTokenizer::commit() +{ + if (context && *context) + debugs(24, 6, context << BinaryTokenizer_tail(parsed_ - syncPoint_, syncPoint_)); + syncPoint_ = parsed_; +} + +bool +BinaryTokenizer::atEnd() const +{ + return parsed_ >= data_.length(); +} + +uint8_t +BinaryTokenizer::uint8(const char *description) +{ + want(1, description); + const uint8_t result = octet(); + got(result, 1, description); + return result; +} + +uint16_t +BinaryTokenizer::uint16(const char *description) +{ + want(2, description); + const uint16_t result = (octet() << 8) | octet(); + got(result, 2, description); + return result; +} + +uint32_t +BinaryTokenizer::uint24(const char *description) +{ + want(3, description); + const uint32_t result = (octet() << 16) | (octet() << 8) | octet(); + got(result, 3, description); + return result; +} + +uint32_t +BinaryTokenizer::uint32(const char *description) +{ + want(4, description); + const uint32_t result = (octet() << 24) | (octet() << 16) | (octet() << 8) | octet(); + got(result, 4, description); + return result; +} + +SBuf +BinaryTokenizer::area(uint64_t size, const char *description) +{ + want(size, description); + const SBuf result = data_.substr(parsed_, size); + parsed_ += size; + got(result, size, description); + return result; +} + +void +BinaryTokenizer::skip(uint64_t size, const char *description) +{ + want(size, description); + parsed_ += size; + skipped(size, description); +} + diff --git a/src/parser/BinaryTokenizer.h b/src/parser/BinaryTokenizer.h new file mode 100644 index 0000000000..08af24607a --- /dev/null +++ b/src/parser/BinaryTokenizer.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 1996-2015 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_BINARY_TOKENIZER_H +#define SQUID_BINARY_TOKENIZER_H + +#include "sbuf/SBuf.h" + +/// Safely extracts byte-oriented (i.e., non-textual) fields from raw input. +/// Supports commit points for atomic incremental parsing of multi-part fields. +/// Throws InsufficientInput when more input is needed to parse the next field. +/// Throws on errors. +class BinaryTokenizer +{ +public: + class InsufficientInput {}; // thrown when a method runs out of data + typedef uint64_t size_type; // enough for the largest supported offset + + BinaryTokenizer(); + explicit BinaryTokenizer(const SBuf &data); + + /// restart parsing from the very beginning + /// this method is for using one BinaryTokenizer to parse independent inputs + void reset(const SBuf &data); + + /// change input without changing parsing state + /// this method avoids append overheads during incremental parsing + void reinput(const SBuf &data) { data_ = data; } + + /// make progress: future parsing failures will not rollback beyond this point + void commit(); + + /// resume [incremental] parsing from the last commit point + void rollback(); + + /// no more bytes to parse or skip + bool atEnd() const; + + /// parse a single-byte unsigned integer + uint8_t uint8(const char *description); + + // parse a two-byte unsigned integer + uint16_t uint16(const char *description); + + // parse a three-byte unsigned integer (returned as uint32_t) + uint32_t uint24(const char *description); + + // parse a four-byte unsigned integer + uint32_t uint32(const char *description); + + /// parse size consecutive bytes as an opaque blob + SBuf area(uint64_t size, const char *description); + + /// ignore the next size bytes + void skip(uint64_t size, const char *description); + + /// yet unparsed bytes + SBuf leftovers() const { return data_.substr(parsed_); } + + const char *context; ///< simplifies debugging + +protected: + uint32_t octet(); + void want(uint64_t size, const char *description) const; + void got(uint32_t value, uint64_t size, const char *description) const; + void got(const SBuf &value, uint64_t size, const char *description) const; + void skipped(uint64_t size, const char *description) const; + +private: + SBuf data_; + uint64_t parsed_; ///< number of data bytes parsed or skipped + uint64_t syncPoint_; ///< where to re-start the next parsing attempt +}; + +#endif // SQUID_BINARY_TOKENIZER_H diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am index 869f71be1f..bcf4332293 100644 --- a/src/parser/Makefile.am +++ b/src/parser/Makefile.am @@ -11,6 +11,8 @@ include $(top_srcdir)/src/TestHeaders.am noinst_LTLIBRARIES = libparser.la libparser_la_SOURCES = \ + BinaryTokenizer.h \ + BinaryTokenizer.cc \ Tokenizer.h \ Tokenizer.cc diff --git a/src/security/Makefile.am b/src/security/Makefile.am index 7e8dc50d4c..debbb0740f 100644 --- a/src/security/Makefile.am +++ b/src/security/Makefile.am @@ -16,6 +16,8 @@ libsecurity_la_SOURCES= \ Context.h \ EncryptorAnswer.cc \ EncryptorAnswer.h \ + Handshake.cc \ + Handshake.h \ forward.h \ KeyData.h \ LockingPointer.h \ diff --git a/src/ssl/PeerConnector.cc b/src/ssl/PeerConnector.cc index 5d633f921e..a77fbfe21e 100644 --- a/src/ssl/PeerConnector.cc +++ b/src/ssl/PeerConnector.cc @@ -543,7 +543,7 @@ Ssl::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus) if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) { char buffer[1024]; debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024)); - const Ssl::X509_STACK_Pointer &certsList = srvBio->serverCertificates(); + const Ssl::X509_STACK_Pointer &certsList = srvBio->serverCertificatesIfAny(); if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList)) { urlsOfMissingCerts.push(SBuf(issuerUri)); } diff --git a/src/ssl/bio.cc b/src/ssl/bio.cc index 91d5afd5da..9c2b22ad79 100644 --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@ -19,6 +19,7 @@ #include "fde.h" #include "globals.h" #include "ip/Address.h" +#include "parser/BinaryTokenizer.h" #include "ssl/bio.h" #if HAVE_OPENSSL_SSL_H @@ -56,229 +57,8 @@ static BIO_METHOD SquidMethods = { NULL // squid_callback_ctrl not supported }; - -/* BinaryTokenizer */ - -BinaryTokenizer::BinaryTokenizer(): BinaryTokenizer(SBuf()) -{ -} - -BinaryTokenizer::BinaryTokenizer(const SBuf &data): - context(""), - data_(data), - parsed_(0), - syncPoint_(0) -{ -} - -/// debugging helper that prints a "standard" debugs() trailer -#define BinaryTokenizer_tail(size, start) \ - " occupying " << (size) << " bytes @" << (start) << " in " << this; - -/// logs and throws if fewer than size octets remain; no other side effects -void -BinaryTokenizer::want(uint64_t size, const char *description) const -{ - if (parsed_ + size > data_.length()) { - debugs(83, 5, (parsed_ + size - data_.length()) << " more bytes for " << - context << description << BinaryTokenizer_tail(size, parsed_)); - throw InsufficientInput(); - } -} - -/// debugging helper for parsed number fields -void -BinaryTokenizer::got(uint32_t value, uint64_t size, const char *description) const -{ - debugs(83, 7, context << description << '=' << value << - BinaryTokenizer_tail(size, parsed_ - size)); -} - -/// debugging helper for parsed areas/blobs -void -BinaryTokenizer::got(const SBuf &value, uint64_t size, const char *description) const -{ - debugs(83, 7, context << description << '=' << - Raw(nullptr, value.rawContent(), value.length()).hex() << - BinaryTokenizer_tail(size, parsed_ - size)); - -} - -/// debugging helper for skipped fields -void -BinaryTokenizer::skipped(uint64_t size, const char *description) const -{ - debugs(83, 7, context << description << BinaryTokenizer_tail(size, parsed_ - size)); - -} - -/// Returns the next ready-for-shift byte, adjusting the number of parsed bytes. -/// The larger 32-bit return type helps callers shift/merge octets into numbers. -/// This internal method does not perform out-of-bounds checks. -uint32_t -BinaryTokenizer::octet() -{ - // While char may be signed, we view data characters as unsigned, - // which helps to arrive at the right 32-bit return value. - return static_cast(data_[parsed_++]); -} - -void -BinaryTokenizer::reset(const SBuf &data) -{ - *this = BinaryTokenizer(data); -} - -void -BinaryTokenizer::rollback() -{ - parsed_ = syncPoint_; -} - -void -BinaryTokenizer::commit() -{ - if (context && *context) - debugs(83, 6, context << BinaryTokenizer_tail(parsed_ - syncPoint_, syncPoint_)); - syncPoint_ = parsed_; -} - -bool -BinaryTokenizer::atEnd() const -{ - return parsed_ >= data_.length(); -} - -uint8_t -BinaryTokenizer::uint8(const char *description) -{ - want(1, description); - const uint8_t result = octet(); - got(result, 1, description); - return result; -} - -uint16_t -BinaryTokenizer::uint16(const char *description) -{ - want(2, description); - const uint16_t result = (octet() << 8) | octet(); - got(result, 2, description); - return result; -} - -uint32_t -BinaryTokenizer::uint24(const char *description) -{ - want(3, description); - const uint32_t result = (octet() << 16) | (octet() << 8) | octet(); - got(result, 3, description); - return result; -} - -uint32_t -BinaryTokenizer::uint32(const char *description) -{ - want(4, description); - const uint32_t result = (octet() << 24) | (octet() << 16) | (octet() << 8) | octet(); - got(result, 4, description); - return result; -} - -SBuf -BinaryTokenizer::area(uint64_t size, const char *description) -{ - want(size, description); - const SBuf result = data_.substr(parsed_, size); - parsed_ += size; - got(result, size, description); - return result; -} - -void -BinaryTokenizer::skip(uint64_t size, const char *description) -{ - want(size, description); - parsed_ += size; - skipped(size, description); -} - - -/* Ssl::Rfc5246 */ - -Ssl::Rfc5246::FieldGroup::FieldGroup(BinaryTokenizer &tk, const char *description) { - tk.context = description; -} - -void -Ssl::Rfc5246::FieldGroup::commit(BinaryTokenizer &tk) { - tk.commit(); - tk.context = ""; -} - - -Ssl::Rfc5246::ProtocolVersion::ProtocolVersion(BinaryTokenizer &tk): - vMajor(tk.uint8(".vMajor")), - vMinor(tk.uint8(".vMinor")) -{ -} - -Ssl::Rfc5246::TLSPlaintext::TLSPlaintext(BinaryTokenizer &tk): - FieldGroup(tk, "TLSPlaintext"), - type(tk.uint8(".type")), - version(tk), - length(tk.uint16(".length")), - fragment(tk.area(length, ".fragment")) -{ - commit(tk); -} - -Ssl::Rfc5246::Handshake::Handshake(BinaryTokenizer &tk): - FieldGroup(tk, "Handshake"), - msg_type(tk.uint8(".msg_type")), - length(tk.uint24(".length")), - body(tk.area(length, ".body")) -{ - commit(tk); -} - -Ssl::Rfc5246::Alert::Alert(BinaryTokenizer &tk): - FieldGroup(tk, "Alert"), - level(tk.uint8(".level")), - description(tk.uint8(".description")) -{ - commit(tk); -} - -Ssl::Rfc5246::P24String::P24String(BinaryTokenizer &tk, const char *description): - FieldGroup(tk, description), - length(tk.uint24(".length")), - body(tk.area(length, ".body")) -{ - commit(tk); -} - - /* Ssl:Bio */ -/// 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; -} - BIO * Ssl::Bio::Create(const int fd, Ssl::Bio::Type type) { @@ -1356,176 +1136,5 @@ Ssl::Bio::sslFeatures::print(std::ostream &os) const " Random:" << Raw(nullptr, (char *)client_random, SSL3_RANDOM_SIZE).hex(); } -/// parses a single TLS Record Layer frame -void -Ssl::HandshakeParser::parseRecord() -{ - const Rfc5246::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 == Rfc5246::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 -Ssl::HandshakeParser::parseMessages() -{ - debugs(83, 7, DebugFrame("fragments", currentContentType, fragments.length())); - while (!tkMessages.atEnd()) { - switch (currentContentType) { - case Rfc5246::ContentType::ctChangeCipherSpec: - parseChangeCipherCpecMessage(); - continue; - case Rfc5246::ContentType::ctAlert: - parseAlertMessage(); - continue; - case Rfc5246::ContentType::ctHandshake: - parseHandshakeMessage(); - continue; - case Rfc5246::ContentType::ctApplicationData: - parseApplicationDataMessage(); - continue; - } - skipMessage("unknown ContentType msg"); - } -} - -void -Ssl::HandshakeParser::parseChangeCipherCpecMessage() -{ - Must(currentContentType == Rfc5246::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 -Ssl::HandshakeParser::parseAlertMessage() -{ - Must(currentContentType == Rfc5246::ContentType::ctAlert); - const Rfc5246::Alert alert(tkMessages); - debugs(83, 3, "level " << alert.level << " description " << alert.description); - // we are currently ignoring Alert Protocol messages -} - -void -Ssl::HandshakeParser::parseHandshakeMessage() -{ - Must(currentContentType == Rfc5246::ContentType::ctHandshake); - - const Rfc5246::Handshake message(tkMessages); - - switch (message.msg_type) { - case Rfc5246::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 Rfc5246::HandshakeType::hskCertificate: - Must(state < atCertificatesReceived); - parseServerCertificates(message.body); - state = atCertificatesReceived; - return; - case Rfc5246::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 -Ssl::HandshakeParser::parseApplicationDataMessage() -{ - Must(currentContentType == Rfc5246::ContentType::ctApplicationData); - skipMessage("app data"); -} - -void -Ssl::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 -Ssl::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; -} - -X509 * -Ssl::HandshakeParser::ParseCertificate(const SBuf &raw) -{ - typedef const unsigned char *x509Data; - const x509Data x509Start = reinterpret_cast(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 -Ssl::HandshakeParser::parseServerCertificates(const SBuf &raw) -{ - BinaryTokenizer tkList(raw); - const Rfc5246::P24String list(tkList, "CertificateList"); - Must(tkList.atEnd()); // no leftovers after all certificates - - BinaryTokenizer tkItems(list.body); - while (!tkItems.atEnd()) { - const Rfc5246::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 /* USE_SSL */ diff --git a/src/ssl/bio.h b/src/ssl/bio.h index 0268874a2e..1399f0908d 100644 --- a/src/ssl/bio.h +++ b/src/ssl/bio.h @@ -11,6 +11,7 @@ #include "fd.h" #include "sbuf/SBuf.h" +#include "security/Handshake.h" #include #include @@ -20,218 +21,9 @@ #include #include -// TODO: Move BinaryTokenizer to its own set of files outside of Ssl namespace. - -/// Safely extracts byte-oriented (i.e., non-textual) fields from raw input. -/// Supports commit points for atomic incremental parsing of multi-part fields. -/// Throws InsufficientInput when more input is needed to parse the next field. -/// Throws on errors. -class BinaryTokenizer -{ -public: - class InsufficientInput {}; // thrown when a method runs out of data - typedef uint64_t size_type; // enough for the largest supported offset - - BinaryTokenizer(); - explicit BinaryTokenizer(const SBuf &data); - - /// restart parsing from the very beginning - /// this method is for using one BinaryTokenizer to parse independent inputs - void reset(const SBuf &data); - - /// change input without changing parsing state - /// this method avoids append overheads during incremental parsing - void reinput(const SBuf &data) { data_ = data; } - - /// make progress: future parsing failures will not rollback beyond this point - void commit(); - - /// resume [incremental] parsing from the last commit point - void rollback(); - - /// no more bytes to parse or skip - bool atEnd() const; - - /// parse a single-byte unsigned integer - uint8_t uint8(const char *description); - - // parse a two-byte unsigned integer - uint16_t uint16(const char *description); - - // parse a three-byte unsigned integer (returned as uint32_t) - uint32_t uint24(const char *description); - - // parse a four-byte unsigned integer - uint32_t uint32(const char *description); - - /// parse size consecutive bytes as an opaque blob - SBuf area(uint64_t size, const char *description); - - /// ignore the next size bytes - void skip(uint64_t size, const char *description); - - /// yet unparsed bytes - SBuf leftovers() const { return data_.substr(parsed_); } - - const char *context; ///< simplifies debugging - -protected: - uint32_t octet(); - void want(uint64_t size, const char *description) const; - void got(uint32_t value, uint64_t size, const char *description) const; - void got(const SBuf &value, uint64_t size, const char *description) const; - void skipped(uint64_t size, const char *description) const; - -private: - SBuf data_; - uint64_t parsed_; ///< number of data bytes parsed or skipped - uint64_t syncPoint_; ///< where to re-start the next parsing attempt -}; - - namespace Ssl { -// The Transport Layer Security (TLS) Protocol, Version 1.2 - -// TODO: Consider removing this namespace. The idea was to encapsulate various -// RFC 5246 types defined using the naming scheme from the RFC rather than -// following Squid naming conventions. However, using these names in other code -// may make that code inconsistent. Besides, we are running into some C++ naming -// limits. -namespace Rfc5246 -{ - -/// 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 -}; - -} // namespace Rfc5246 - - -/// 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); - - Ssl::X509_STACK_Pointer serverCertificates; ///< parsed certificates chain - - 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); - static X509 *ParseCertificate(const SBuf &raw); - - /// concatenated TLSPlaintext.fragments of TLSPlaintext.type - SBuf fragments; - - BinaryTokenizer tkRecords; // TLS record layer (parsing uninterpreted data) - BinaryTokenizer tkMessages; // TLS message layer (parsing fragments) -}; - /// BIO source and sink node, handling socket I/O and monitoring SSL state class Bio { @@ -431,7 +223,7 @@ public: /// Return true if the Server Hello parsing failed bool gotHelloFailed() const { return (parser_.parseDone && parser_.parseError); } - const Ssl::X509_STACK_Pointer &serverCertificates() { return parser_.serverCertificates; } /* XXX: may be nil */ + const Ssl::X509_STACK_Pointer &serverCertificatesIfAny() { return parser_.serverCertificates; } /* XXX: may be nil */ private: sslFeatures clientFeatures; ///< SSL client features extracted from ClientHello message or SSL object @@ -447,7 +239,7 @@ private: ///< The size of data stored in rbuf which passed to the openSSL size_t rbufConsumePos; - HandshakeParser parser_; ///< The SSL messages parser. + Security::HandshakeParser parser_; ///< The SSL messages parser. }; inline