#include "squid.h"
#include "BinaryTokenizer.h"
-BinaryTokenizer::BinaryTokenizer(): BinaryTokenizer(SBuf())
+Parser::BinaryTokenizer::BinaryTokenizer(): BinaryTokenizer(SBuf())
{
}
-BinaryTokenizer::BinaryTokenizer(const SBuf &data, const bool expectMore):
+Parser::BinaryTokenizer::BinaryTokenizer(const SBuf &data, const bool expectMore):
context(nullptr),
data_(data),
parsed_(0),
static inline
std::ostream &
-operator <<(std::ostream &os, const BinaryTokenizerContext *context)
+operator <<(std::ostream &os, const Parser::BinaryTokenizerContext *context)
{
if (context)
os << context->parent << context->name;
/// logs and throws if fewer than size octets remain; no other side effects
void
-BinaryTokenizer::want(uint64_t size, const char *description) const
+Parser::BinaryTokenizer::want(uint64_t size, const char *description) const
{
if (parsed_ + size > data_.length()) {
debugs(24, 5, (parsed_ + size - data_.length()) << " more bytes for " <<
}
void
-BinaryTokenizer::got(uint64_t size, const char *description) const
+Parser::BinaryTokenizer::got(uint64_t size, const char *description) const
{
debugs(24, 7, context << description <<
BinaryTokenizer_tail(size, parsed_ - size));
/// debugging helper for parsed number fields
void
-BinaryTokenizer::got(uint32_t value, uint64_t size, const char *description) const
+Parser::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
+Parser::BinaryTokenizer::got(const SBuf &value, uint64_t size, const char *description) const
{
debugs(24, 7, context << description << '=' <<
Raw(nullptr, value.rawContent(), value.length()).hex() <<
/// debugging helper for skipped fields
void
-BinaryTokenizer::skipped(uint64_t size, const char *description) const
+Parser::BinaryTokenizer::skipped(uint64_t size, const char *description) const
{
debugs(24, 7, context << description << BinaryTokenizer_tail(size, parsed_ - size));
/// 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()
+Parser::BinaryTokenizer::octet()
{
// While char may be signed, we view data characters as unsigned,
// which helps to arrive at the right 32-bit return value.
}
void
-BinaryTokenizer::reset(const SBuf &data, const bool expectMore)
+Parser::BinaryTokenizer::reset(const SBuf &data, const bool expectMore)
{
*this = BinaryTokenizer(data, expectMore);
}
void
-BinaryTokenizer::rollback()
+Parser::BinaryTokenizer::rollback()
{
parsed_ = syncPoint_;
}
void
-BinaryTokenizer::commit()
+Parser::BinaryTokenizer::commit()
{
syncPoint_ = parsed_;
}
bool
-BinaryTokenizer::atEnd() const
+Parser::BinaryTokenizer::atEnd() const
{
return parsed_ >= data_.length();
}
uint8_t
-BinaryTokenizer::uint8(const char *description)
+Parser::BinaryTokenizer::uint8(const char *description)
{
want(1, description);
const uint8_t result = octet();
}
uint16_t
-BinaryTokenizer::uint16(const char *description)
+Parser::BinaryTokenizer::uint16(const char *description)
{
want(2, description);
const uint16_t result = (octet() << 8) | octet();
}
uint32_t
-BinaryTokenizer::uint24(const char *description)
+Parser::BinaryTokenizer::uint24(const char *description)
{
want(3, description);
const uint32_t result = (octet() << 16) | (octet() << 8) | octet();
}
uint32_t
-BinaryTokenizer::uint32(const char *description)
+Parser::BinaryTokenizer::uint32(const char *description)
{
want(4, description);
const uint32_t result = (octet() << 24) | (octet() << 16) | (octet() << 8) | octet();
}
SBuf
-BinaryTokenizer::area(uint64_t size, const char *description)
+Parser::BinaryTokenizer::area(uint64_t size, const char *description)
{
want(size, description);
const SBuf result = data_.substr(parsed_, size);
}
void
-BinaryTokenizer::skip(uint64_t size, const char *description)
+Parser::BinaryTokenizer::skip(uint64_t size, const char *description)
{
want(size, description);
parsed_ += size;
*/
SBuf
-BinaryTokenizer::pstring8(const char *description)
+Parser::BinaryTokenizer::pstring8(const char *description)
{
BinaryTokenizerContext pstring(*this, description);
if (const uint8_t length = uint8(".length"))
}
SBuf
-BinaryTokenizer::pstring16(const char *description)
+Parser::BinaryTokenizer::pstring16(const char *description)
{
BinaryTokenizerContext pstring(*this, description);
if (const uint16_t length = uint16(".length"))
}
SBuf
-BinaryTokenizer::pstring24(const char *description)
+Parser::BinaryTokenizer::pstring24(const char *description)
{
BinaryTokenizerContext pstring(*this, description);
if (const uint32_t length = uint24(".length"))
#include <unordered_set>
namespace Security {
-
-// TODO: Replace with Anyp::ProtocolVersion and use for TlsDetails::tls*Version.
-/// TLS Record Layer's protocol version from RFC 5246 Section 6.2.1
-class ProtocolVersion
-{
-public:
- ProtocolVersion() {}
- explicit ProtocolVersion(BinaryTokenizer &tk);
-
- /// XXX: TlsDetails use "int" to manipulate version information.
- /// TODO: Use ProtocolVersion in TlsDetails and printTlsVersion().
- int toNumberXXX() const { return (vMajor << 8) | vMinor; }
-
- // the "v" prefix works around environments that #define major and minor
- uint8_t vMajor = 0;
- uint8_t vMinor = 0;
-};
-
/*
* The types below represent various SSL and TLS protocol elements. Most names
* are based on RFC 5264 and RFC 6066 terminology. Objects of these explicit
class TLSPlaintext
{
public:
- explicit TLSPlaintext(BinaryTokenizer &tk);
+ explicit TLSPlaintext(Parser::BinaryTokenizer &tk);
uint8_t type; ///< see ContentType
- int version; ///< Record Layer, not necessarily the negotiated TLS version; TODO: Replace with Anyp::ProtocolVersion
+ AnyP::ProtocolVersion version; ///< Record Layer, not necessarily the negotiated TLS version;
SBuf fragment; ///< possibly partial content
};
class Sslv2Record
{
public:
- explicit Sslv2Record(BinaryTokenizer &tk);
+ explicit Sslv2Record(Parser::BinaryTokenizer &tk);
SBuf fragment;
};
class Handshake
{
public:
- explicit Handshake(BinaryTokenizer &tk);
+ explicit Handshake(Parser::BinaryTokenizer &tk);
uint8_t msg_type; ///< see HandshakeType
SBuf msg_body; ///< Handshake Protocol message
class Alert
{
public:
- explicit Alert(BinaryTokenizer &tk);
+ explicit Alert(Parser::BinaryTokenizer &tk);
bool fatal() const { return level == 2; }
{
public:
typedef uint16_t Type;
- explicit Extension(BinaryTokenizer &tk);
+ explicit Extension(Parser::BinaryTokenizer &tk);
/// whether this extension is supported by Squid and, hence, may be bumped
/// after peeking or spliced after staring (subject to other restrictions)
} // namespace Security
/// Convenience helper: We parse ProtocolVersion but store "int".
-static int
-ParseProtocolVersion(BinaryTokenizer &tk)
+static AnyP::ProtocolVersion
+ParseProtocolVersion(Parser::BinaryTokenizer &tk)
{
- const Security::ProtocolVersion version(tk);
- return version.toNumberXXX();
-}
+ Parser::BinaryTokenizerContext context(tk, ".version");
+ uint8_t vMajor = tk.uint8(".major");
+ uint8_t vMinor = tk.uint8(".minor");
+ if (vMajor == 0 && vMinor == 2)
+ return AnyP::ProtocolVersion(AnyP::PROTO_SSL, 2, 0);
+ Must(vMajor == 3);
+ if (vMinor == 0)
+ return AnyP::ProtocolVersion(AnyP::PROTO_SSL, 3, 0);
-Security::ProtocolVersion::ProtocolVersion(BinaryTokenizer &tk)
-{
- BinaryTokenizerContext context(tk, ".version");
- vMajor = tk.uint8(".major");
- vMinor = tk.uint8(".minor");
- // do not summarize context.success() to reduce debugging noise
+ return AnyP::ProtocolVersion(AnyP::PROTO_TLS, 1, (vMinor - 1));
}
-Security::TLSPlaintext::TLSPlaintext(BinaryTokenizer &tk)
+Security::TLSPlaintext::TLSPlaintext(Parser::BinaryTokenizer &tk)
{
- BinaryTokenizerContext context(tk, "TLSPlaintext");
+ Parser::BinaryTokenizerContext context(tk, "TLSPlaintext");
type = tk.uint8(".type");
Must(type >= ctChangeCipherSpec && type <= ctApplicationData);
version = ParseProtocolVersion(tk);
context.success();
}
-Security::Handshake::Handshake(BinaryTokenizer &tk)
+Security::Handshake::Handshake(Parser::BinaryTokenizer &tk)
{
- BinaryTokenizerContext context(tk, "Handshake");
+ Parser::BinaryTokenizerContext context(tk, "Handshake");
msg_type = tk.uint8(".msg_type");
msg_body = tk.pstring24(".msg_body");
context.success();
}
-Security::Alert::Alert(BinaryTokenizer &tk)
+Security::Alert::Alert(Parser::BinaryTokenizer &tk)
{
- BinaryTokenizerContext context(tk, "Alert");
+ Parser::BinaryTokenizerContext context(tk, "Alert");
level = tk.uint8(".level");
description = tk.uint8(".description");
context.success();
}
-Security::Extension::Extension(BinaryTokenizer &tk)
+Security::Extension::Extension(Parser::BinaryTokenizer &tk)
{
- BinaryTokenizerContext context(tk, "Extension");
+ Parser::BinaryTokenizerContext context(tk, "Extension");
type = tk.uint16(".type");
data = tk.pstring16(".data");
context.success();
return supportedExtensions.find(type) != supportedExtensions.end();
}
-Security::Sslv2Record::Sslv2Record(BinaryTokenizer &tk)
+Security::Sslv2Record::Sslv2Record(Parser::BinaryTokenizer &tk)
{
- BinaryTokenizerContext context(tk, "Sslv2Record");
+ Parser::BinaryTokenizerContext context(tk, "Sslv2Record");
const uint16_t head = tk.uint16(".head");
const uint16_t length = head & 0x7FFF;
Must((head & 0x8000) && length); // SSLv2 message [without padding]
}
Security::TlsDetails::TlsDetails():
- tlsVersion(-1),
- tlsSupportedVersion(-1),
- compressMethod(-1),
+ compressionSupported(false),
doHeartBeats(false),
tlsTicketsExtension(false),
hasTlsTicket(false),
/* Security::HandshakeParser */
Security::HandshakeParser::HandshakeParser():
+ details(new TlsDetails),
state(atHelloNone),
ressumingSession(false),
currentContentType(0),
{
const Sslv2Record record(tkRecords);
tkRecords.commit();
- Must(details);
- details->tlsVersion = 0x002;
+ details->tlsVersion = AnyP::ProtocolVersion(AnyP::PROTO_SSL, 2, 0);
parseVersion2HandshakeMessage(record.fragment);
state = atHelloReceived;
done = "SSLv2";
bool
Security::HandshakeParser::isSslv2Record(const SBuf &raw) const
{
- BinaryTokenizer tk(raw, true);
+ Parser::BinaryTokenizer tk(raw, true);
const uint16_t head = tk.uint16("?v2Hello.msg_head");
const uint8_t type = tk.uint8("?v2Hello.msg_type");
const uint16_t length = head & 0x7FFF;
void
Security::HandshakeParser::parseRecord()
{
- Must(details);
if (expectingModernRecords)
parseModernRecord();
else
void
Security::HandshakeParser::parseVersion2HandshakeMessage(const SBuf &raw)
{
- BinaryTokenizer tk(raw);
- BinaryTokenizerContext hello(tk, "V2ClientHello");
+ Parser::BinaryTokenizer tk(raw);
+ Parser::BinaryTokenizerContext hello(tk, "V2ClientHello");
Must(tk.uint8(".type") == hskClientHello); // Only client hello supported.
details->tlsSupportedVersion = ParseProtocolVersion(tk);
const uint16_t ciphersLen = tk.uint16(".cipher_specs.length");
void
Security::HandshakeParser::parseClientHelloHandshakeMessage(const SBuf &raw)
{
- BinaryTokenizer tk(raw);
- BinaryTokenizerContext hello(tk, "ClientHello");
+ Parser::BinaryTokenizer tk(raw);
+ Parser::BinaryTokenizerContext hello(tk, "ClientHello");
details->tlsSupportedVersion = ParseProtocolVersion(tk);
details->clientRandom = tk.area(HelloRandomSize, ".random");
details->sessionId = tk.pstring8(".session_id");
parseCiphers(tk.pstring16(".cipher_suites"));
- details->compressMethod = tk.pstring8(".compression_methods").length() > 0 ? 1 : 0; // Only deflate supported here.
+ details->compressionSupported = parseCompressionMethods(tk.pstring8(".compression_methods"));
if (!tk.atEnd()) // extension-free message ends here
parseExtensions(tk.pstring16(".extensions"));
hello.success();
}
+bool
+Security::HandshakeParser::parseCompressionMethods(const SBuf &raw)
+{
+ if (raw.length() == 0)
+ return false;
+ Parser::BinaryTokenizer tk(raw);
+ while (!tk.atEnd()) {
+ // Probably here we should check for DEFLATE(1) compression method
+ // which is the only supported by openSSL subsystem.
+ if (tk.uint8("compression_method") != 0)
+ return true;
+ }
+ return false;
+}
+
void
Security::HandshakeParser::parseExtensions(const SBuf &raw)
{
- BinaryTokenizer tk(raw);
+ Parser::BinaryTokenizer tk(raw);
while (!tk.atEnd()) {
Extension extension(tk);
details->doHeartBeats = true;
break;
case 16: { // Application-Layer Protocol Negotiation Extension, RFC 7301
- BinaryTokenizer tkAPN(extension.data);
+ Parser::BinaryTokenizer tkAPN(extension.data);
details->tlsAppLayerProtoNeg = tkAPN.pstring16("APN");
break;
}
Security::HandshakeParser::parseCiphers(const SBuf &raw)
{
details->ciphers.reserve(raw.length() / sizeof(uint16_t));
- BinaryTokenizer tk(raw);
+ Parser::BinaryTokenizer tk(raw);
while (!tk.atEnd()) {
const uint16_t cipher = tk.uint16("cipher");
details->ciphers.insert(cipher);
void
Security::HandshakeParser::parseV23Ciphers(const SBuf &raw)
{
- BinaryTokenizer tk(raw);
+ Parser::BinaryTokenizer tk(raw);
while (!tk.atEnd()) {
- // The v2 hello messages cipher has 3 bytes.
- // The v2 cipher has the first byte not null.
- // We support v3 messages only so we are ignoring v2 ciphers.
- // XXX: The above line sounds wrong -- we support v2 hello messages.
+ // The v2 hello messages cipher has 3 bytes. The v2 cipher has the
+ // first byte not null. In an v23 SSL Hello message both v2 and
+ // v3/tls ciphers can coexist.
+ // The supported ciphers list needed for Peek and Stare bumping
+ // modes where only SSLv3 and TLS protocols are supported so
+ // we are ignoring the v2 ciphers.
const uint8_t prefix = tk.uint8("prefix");
const uint16_t cipher = tk.uint16("cipher");
- if (prefix == 0) // TODO: return immediately if prefix is positive?
+ if (prefix == 0)
details->ciphers.insert(cipher);
}
}
void
Security::HandshakeParser::parseServerHelloHandshakeMessage(const SBuf &raw)
{
- BinaryTokenizer tk(raw);
- BinaryTokenizerContext hello(tk, "ServerHello");
+ Parser::BinaryTokenizer tk(raw);
+ Parser::BinaryTokenizerContext hello(tk, "ServerHello");
details->tlsSupportedVersion = ParseProtocolVersion(tk);
tk.skip(HelloRandomSize, ".random");
details->sessionId = tk.pstring8(".session_id");
details->ciphers.insert(tk.uint16(".cipher_suite"));
- details->compressMethod = tk.uint8(".compression_method") != 0; // not null
+ details->compressionSupported = tk.uint8(".compression_method") != 0; // not null
if (!tk.atEnd()) // extensions present
parseExtensions(tk.pstring16(".extensions"));
hello.success();
// SNI MUST NOT contain more than one name of the same name_type but
// we ignore violations and simply return the first host name found.
- BinaryTokenizer tkList(extensionData);
- BinaryTokenizer tkNames(tkList.pstring16("ServerNameList"));
+ Parser::BinaryTokenizer tkList(extensionData);
+ Parser::BinaryTokenizer tkNames(tkList.pstring16("ServerNameList"));
while (!tkNames.atEnd()) {
- BinaryTokenizerContext serverName(tkNames, "ServerName");
+ Parser::BinaryTokenizerContext serverName(tkNames, "ServerName");
const uint8_t nameType = tkNames.uint8(".name_type");
const SBuf name = tkNames.pstring16(".name");
serverName.success();
Security::HandshakeParser::parseHello(const SBuf &data)
{
try {
- if (!details) {
- expectingModernRecords = !isSslv2Record(data);
- details = new TlsDetails; // after expectingModernRecords is known
- }
+ if (!expectingModernRecords.configured())
+ expectingModernRecords.configure(!isSslv2Record(data));
// data contains everything read so far, but we may read more later
tkRecords.reinput(data, true);
// we are done; tkRecords may have leftovers we are not interested in
return true;
}
- catch (const BinaryTokenizer::InsufficientInput &) {
+ catch (const Parser::BinaryTokenizer::InsufficientInput &) {
debugs(83, 5, "need more data");
return false;
}
void
Security::HandshakeParser::parseServerCertificates(const SBuf &raw)
{
- BinaryTokenizer tkList(raw);
+ Parser::BinaryTokenizer tkList(raw);
const SBuf clist = tkList.pstring24("CertificateList");
Must(tkList.atEnd()); // no leftovers after all certificates
- BinaryTokenizer tkItems(clist);
+ Parser::BinaryTokenizer tkItems(clist);
while (!tkItems.atEnd()) {
X509 *cert = ParseCertificate(tkItems.pstring24("Certificate"));
if (!serverCertificates.get())