#include "ssl/support.h"
#endif
+#include <unordered_set>
+
namespace Security {
// TODO: Replace with Anyp::ProtocolVersion and use for TlsDetails::tls*Version.
class Extension
{
public:
+ typedef uint16_t Type;
explicit Extension(BinaryTokenizer &tk);
- uint16_t type;
+ /// whether this extension is supported by Squid and, hence, may be bumped
+ /// after peeking or spliced after staring (subject to other restrictions)
+ bool supported() const;
+
+ Type type;
SBuf data;
};
+/// Extension types optimized for fast lookups.
+typedef std::unordered_set<Extension::Type> Extensions;
+static Extensions SupportedExtensions();
+
} // namespace Security
/// Convenience helper: We parse ProtocolVersion but store "int".
context.success();
}
+bool
+Security::Extension::supported() const
+{
+ static const Extensions supportedExtensions = SupportedExtensions();
+ return supportedExtensions.find(type) != supportedExtensions.end();
+}
+
Security::Sslv2Record::Sslv2Record(BinaryTokenizer &tk)
{
BinaryTokenizerContext context(tk, "Sslv2Record");
doHeartBeats(true),
tlsTicketsExtension(false),
hasTlsTicket(false),
- tlsStatusRequest(false)
-{
-}
-
-/// 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)
+ tlsStatusRequest(false),
+ unsupportedExtensions(false)
{
- return os << frame.size << "-byte type-" << frame.type << ' ' << frame.name;
}
/* Security::HandshakeParser */
done = "ServerHelloDone";
return;
}
- debugs(83, 5, "ignoring " <<
- DebugFrame("handshake msg", message.msg_type, message.msg_body.length()));
+ debugs(83, 5, "ignoring " << message.msg_body.length() << "-byte type-" <<
+ message.msg_type << " handshake message");
}
void
BinaryTokenizer tk(raw);
while (!tk.atEnd()) {
Extension extension(tk);
- details->extensions.push_back(extension.type);
+
+ if (!details->unsupportedExtensions && !extension.supported()) {
+ debugs(83, 5, "first unsupported extension: " << extension.type);
+ details->unsupportedExtensions = true;
+ }
switch(extension.type) {
case 0: // The SNI extension; RFC 6066, Section 3
void
Security::HandshakeParser::parseCiphers(const SBuf &raw)
{
+ details->ciphers.reserve(raw.length() / sizeof(uint16_t));
BinaryTokenizer tk(raw);
while (!tk.atEnd()) {
const uint16_t cipher = tk.uint16("cipher");
- details->ciphers.push_back(cipher);
+ details->ciphers.insert(cipher);
}
}
const uint8_t prefix = tk.uint8("prefix");
const uint16_t cipher = tk.uint16("cipher");
if (prefix == 0) // TODO: return immediately if prefix is positive?
- details->ciphers.push_back(cipher);
+ details->ciphers.insert(cipher);
}
}
details->tlsSupportedVersion = ParseProtocolVersion(tk);
tk.skip(HelloRandomSize, ".random");
details->sessionId = tk.pstring8(".session_id");
- details->ciphers.push_back(tk.uint16(".cipher_suite"));
+ details->ciphers.insert(tk.uint16(".cipher_suite"));
details->compressMethod = tk.uint8(".compression_method") != 0; // not null
if (!tk.atEnd()) // extensions present
parseExtensions(tk.pstring16(".extensions"));
}
}
+
+/// A helper function to create a set of all supported TLS extensions
+static
+Security::Extensions
+Security::SupportedExtensions()
+{
+ // optimize lookup speed by reserving the number of values x3, approximately
+ Security::Extensions extensions(64);
+
+ // Keep this list ordered and up to date by running something like
+ // egrep '# *define TLSEXT_TYPE_' /usr/include/openssl/tls1.h
+ // TODO: Teach OpenSSL to return the list of extensions it supports.
+#if defined(TLSEXT_TYPE_server_name) // 0
+ extensions.insert(TLSEXT_TYPE_server_name);
+#endif
+#if defined(TLSEXT_TYPE_max_fragment_length) // 1
+ extensions.insert(TLSEXT_TYPE_max_fragment_length);
+#endif
+#if defined(TLSEXT_TYPE_client_certificate_url) // 2
+ extensions.insert(TLSEXT_TYPE_client_certificate_url);
+#endif
+#if defined(TLSEXT_TYPE_trusted_ca_keys) // 3
+ extensions.insert(TLSEXT_TYPE_trusted_ca_keys);
+#endif
+#if defined(TLSEXT_TYPE_truncated_hmac) // 4
+ extensions.insert(TLSEXT_TYPE_truncated_hmac);
+#endif
+#if defined(TLSEXT_TYPE_status_request) // 5
+ extensions.insert(TLSEXT_TYPE_status_request);
+#endif
+#if defined(TLSEXT_TYPE_user_mapping) // 6
+ extensions.insert(TLSEXT_TYPE_user_mapping);
+#endif
+#if defined(TLSEXT_TYPE_client_authz) // 7
+ extensions.insert(TLSEXT_TYPE_client_authz);
+#endif
+#if defined(TLSEXT_TYPE_server_authz) // 8
+ extensions.insert(TLSEXT_TYPE_server_authz);
+#endif
+#if defined(TLSEXT_TYPE_cert_type) // 9
+ extensions.insert(TLSEXT_TYPE_cert_type);
+#endif
+#if defined(TLSEXT_TYPE_elliptic_curves) // 10
+ extensions.insert(TLSEXT_TYPE_elliptic_curves);
+#endif
+#if defined(TLSEXT_TYPE_ec_point_formats) // 11
+ extensions.insert(TLSEXT_TYPE_ec_point_formats);
+#endif
+#if defined(TLSEXT_TYPE_srp) // 12
+ extensions.insert(TLSEXT_TYPE_srp);
+#endif
+#if defined(TLSEXT_TYPE_signature_algorithms) // 13
+ extensions.insert(TLSEXT_TYPE_signature_algorithms);
+#endif
+#if defined(TLSEXT_TYPE_use_srtp) // 14
+ extensions.insert(TLSEXT_TYPE_use_srtp);
+#endif
+#if defined(TLSEXT_TYPE_heartbeat) // 15
+ extensions.insert(TLSEXT_TYPE_heartbeat);
+#endif
+#if defined(TLSEXT_TYPE_session_ticket) // 35
+ extensions.insert(TLSEXT_TYPE_session_ticket);
+#endif
+#if defined(TLSEXT_TYPE_renegotiate) // 0xff01
+ extensions.insert(TLSEXT_TYPE_renegotiate);
+#endif
+#if defined(TLSEXT_TYPE_next_proto_neg) // 13172
+ extensions.insert(TLSEXT_TYPE_next_proto_neg);
+#endif
+
+ /*
+ * OpenSSL does not support these last extensions by default, but those
+ * building the OpenSSL libraries and/or Squid might define them.
+ */
+
+ // OpenSSL may be built to support draft-rescorla-tls-opaque-prf-input-00,
+ // with the extension type value configured at build time. OpenSSL, Squid,
+ // and TLS agents must all be built with the same extension type value.
+#if defined(TLSEXT_TYPE_opaque_prf_input)
+ extensions.insert(TLSEXT_TYPE_opaque_prf_input);
+#endif
+
+ // Define this to add extensions supported by your OpenSSL but unknown to
+ // your Squid version. Use {list-initialization} to add multiple extensions.
+#if defined(TLSEXT_TYPE_SUPPORTED_BY_MY_SQUID)
+ extensions.insert(TLSEXT_TYPE_SUPPORTED_BY_MY_SQUID);
+#endif
+
+ return extensions; // might be empty
+}
+
#else
+
void
Security::HandshakeParser::parseServerCertificates(const SBuf &raw)
{
}
+
+static
+Security::Extensions
+Security::SupportedExtensions()
+{
+ return Extensions(); // no extensions are supported without OpenSSL
+}
#endif
return false;
}
- // Check ciphers list
- for (auto cipherId: details->ciphers) {
- bool found = false;
- STACK_OF(SSL_CIPHER) *cipher_stack = SSL_get_ciphers(ssl);
- for (int i = 0; i < sk_SSL_CIPHER_num(cipher_stack); i++) {
- SSL_CIPHER *c = sk_SSL_CIPHER_value(cipher_stack, i);
- const long cid = SSL_CIPHER_get_id(c);
- if (cipherId == (0xFFFF & cid)) {
- found = true;
- break;
- }
- }
- if (!found) {
- debugs(83, 5, "Client Hello Data supports cipher '"<< cipherId <<"' but we do not support it!");
- return false;
- }
- }
-
#if !defined(SSL_TLSEXT_HB_ENABLED)
if (details->doHeartBeats) {
debugs(83, 5, "Client Hello Data supports HeartBeats but we do not support!");
}
#endif
- for (std::list<uint16_t>::iterator it = details->extensions.begin(); it != details->extensions.end(); ++it) {
- static int supportedExtensions[] = {
-#if defined(TLSEXT_TYPE_server_name)
- TLSEXT_TYPE_server_name,
-#endif
-#if defined(TLSEXT_TYPE_opaque_prf_input)
- TLSEXT_TYPE_opaque_prf_input,
-#endif
-#if defined(TLSEXT_TYPE_heartbeat)
- TLSEXT_TYPE_heartbeat,
-#endif
+ if (details->unsupportedExtensions) {
+ debugs(83, 5, "Client Hello contains extensions that we do not support!");
+ return false;
+ }
+
+ SSL3_BUFFER *wb=&(ssl->s3->wbuf);
+ if (wb->len < (size_t)helloMessage.length()) {
+ debugs(83, 5, "Client Hello exceeds OpenSSL buffer: " << helloMessage.length() << " >= " << wb->len);
+ return false;
+ }
+
+ /* Check whether all on-the-wire ciphers are supported by OpenSSL. */
+
+ const auto &wireCiphers = details->ciphers;
+ Security::TlsDetails::Ciphers::size_type ciphersToFind = wireCiphers.size();
+
+ // RFC 5746: "TLS_EMPTY_RENEGOTIATION_INFO_SCSV is not a true cipher suite".
+ // It is commonly seen on the wire, including in from-OpenSSL traffic, but
+ // SSL_get_ciphers() does not return this _pseudo_ cipher suite in my tests.
+ // If OpenSSL supports scsvCipher, we count it (at most once) further below.
#if defined(TLSEXT_TYPE_renegotiate)
- TLSEXT_TYPE_renegotiate,
-#endif
-#if defined(TLSEXT_TYPE_ec_point_formats)
- TLSEXT_TYPE_ec_point_formats,
-#endif
-#if defined(TLSEXT_TYPE_elliptic_curves)
- TLSEXT_TYPE_elliptic_curves,
-#endif
-#if defined(TLSEXT_TYPE_session_ticket)
- TLSEXT_TYPE_session_ticket,
-#endif
-#if defined(TLSEXT_TYPE_status_request)
- TLSEXT_TYPE_status_request,
-#endif
-#if defined(TLSEXT_TYPE_use_srtp)
- TLSEXT_TYPE_use_srtp,
-#endif
-#if 0 //Allow 13172 Firefox supported extension for testing purposes
- 13172,
+ // the 0x00FFFF mask converts 3-byte OpenSSL cipher to our 2-byte cipher
+ const uint16_t scsvCipher = SSL3_CK_SCSV & 0x00FFFF;
+#else
+ const uint16_t scsvCipher = 0;
#endif
- -1
- };
- bool found = false;
- for (int i = 0; supportedExtensions[i] != -1; i++) {
- if (*it == supportedExtensions[i]) {
- found = true;
- break;
- }
- }
- if (!found) {
- debugs(83, 5, "Extension " << *it << " does not supported!");
- return false;
- }
+
+ STACK_OF(SSL_CIPHER) *cipher_stack = SSL_get_ciphers(ssl);
+ const int supportedCipherCount = sk_SSL_CIPHER_num(cipher_stack);
+ for (int idx = 0; idx < supportedCipherCount && ciphersToFind > 0; ++idx) {
+ const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(cipher_stack, idx);
+ const auto id = SSL_CIPHER_get_id(cipher) & 0x00FFFF;
+ if (wireCiphers.find(id) != wireCiphers.end() && (!scsvCipher || id != scsvCipher))
+ --ciphersToFind;
}
- SSL3_BUFFER *wb=&(ssl->s3->wbuf);
- if (wb->len < (size_t)helloMessage.length())
+ if (ciphersToFind > 0 && scsvCipher && wireCiphers.find(scsvCipher) != wireCiphers.end())
+ --ciphersToFind;
+
+ if (ciphersToFind > 0) {
+ // TODO: Add slowlyReportUnsupportedCiphers() to slowly find and report each of them
+ debugs(83, 5, "Client Hello Data has " << ciphersToFind << " ciphers that we do not support!");
return false;
+ }
debugs(83, 5, "OpenSSL SSL struct will be adjusted to mimic client hello data!");