From d01e13c9dee53c3ab4faf70a215f4d1dcfed9e87 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 28 Mar 2017 22:36:44 +0200 Subject: MITM SSL Interception support using mbedtls --- src/mitm.cc | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 src/mitm.cc (limited to 'src/mitm.cc') diff --git a/src/mitm.cc b/src/mitm.cc new file mode 100644 index 0000000..7e0fd19 --- /dev/null +++ b/src/mitm.cc @@ -0,0 +1,253 @@ +// -*- mode: c++; c-basic-offset: 2; -*- + +#include "common.hh" + +#include +#include + +#include "config.hh" +#include "buffer.hh" +#include "paths.hh" +#include "logger.hh" +#include "lru.hh" +#include "mitm.hh" +#include "ssl.hh" + +namespace { + +class ConnectionImpl : public Mitm::Connection { +public: + ConnectionImpl(Logger* logger) + : logger_(logger){ + } + + bool connect(SSLEntropy* entropy, SSLCertStore* store, + std::string const& cert, SSLKey* key, + bool unsecure, std::string const& host) { + uint16_t flags = unsecure ? SSL::UNSECURE : 0; + cert_.reset(SSLCert::load(logger_, cert)); + if (!cert_) return false; + local_.reset(SSL::server(logger_, entropy, cert_.get(), key, flags)); + if (!local_) return false; + remote_.reset(SSL::client(logger_, entropy, store, host, flags)); + if (!remote_) return false; + + in_.reset(Buffer::create(8192, 1024)); + out_.reset(Buffer::create(8192, 1024)); + + return true; + } + + bool transfer(Buffer* local_in, Buffer* local_out, + Buffer* remote_in, Buffer* remote_out, + Mitm::Monitor* monitor) override { + bool local_active = true, remote_active = true; + while (local_active || remote_active) { + local_active = false; + if (local_) { + size_t avail; + out_->read_ptr(&avail); + switch (local_->transfer(local_in, local_out, in_.get(), out_.get())) { + case SSL::NO_ERR: { + size_t avail2; + auto ptr = out_->read_ptr(&avail2); + if (avail < avail2) { + if (monitor) { + monitor->local2remote( + static_cast(ptr) + avail, avail2 - avail); + } + local_active = true; + } + break; + } + case SSL::ERR: + return false; + case SSL::CLOSED: + local_.reset(); + break; + } + } + remote_active = false; + if (remote_) { + size_t avail; + in_->read_ptr(&avail); + switch (remote_->transfer(remote_in, remote_out, + out_.get(), in_.get())) { + case SSL::NO_ERR: { + size_t avail2; + auto ptr = in_->read_ptr(&avail2); + if (avail < avail2) { + if (monitor) { + monitor->remote2local( + static_cast(ptr) + avail, avail2 - avail); + } + remote_active = true; + } + break; + } + case SSL::ERR: + return false; + case SSL::CLOSED: + remote_.reset(); + break; + } + } + } + return true; + } + + bool local_eof() const override { + return !local_; + } + + bool remote_eof() const override { + return !remote_; + } + + void close_local() override { + if (local_) { + local_->close(); + } + } + void close_remote() override { + if (remote_) { + remote_->close(); + } + } + +private: + Logger* const logger_; + std::unique_ptr cert_; + std::unique_ptr local_; + std::unique_ptr remote_; + std::unique_ptr in_; + std::unique_ptr out_; +}; + +class MitmImpl : public Mitm { +public: + MitmImpl(Logger* logger) + : logger_(logger), unsecure_(false), cache_(42) { + } + + ~MitmImpl() override { + } + + bool load(Config* config, std::string const& cwd) { + entropy_.reset(SSLEntropy::create(logger_)); + if (!entropy_) return false; + store_.reset(SSLCertStore::create( + logger_, config->get("ssl_cert_bundle", ""))); + if (!store_) return false; + std::string ca_cert, ca_key; + if (!load_file(config->get("ssl_ca_cert", ""), cwd, &ca_cert)) return false; + if (!load_file(config->get("ssl_ca_key", ""), cwd, &ca_key)) return false; + unsecure_ = config->get("ssl_unsecure", false); + issuer_cert_.reset(SSLCert::load(logger_, ca_cert)); + if (!issuer_cert_) return false; + issuer_key_.reset(SSLKey::load(logger_, ca_key)); + if (!issuer_key_) return false; + return true; + } + + bool reload(Config* config, std::string const& cwd) override { + std::unique_ptr store( + SSLCertStore::create(logger_, config->get("ssl_cert_bundle", ""))); + if (!store) return false; + std::string ca_cert, ca_key; + if (!load_file(config->get("ssl_ca_cert", ""), cwd, &ca_cert)) return false; + if (!load_file(config->get("ssl_ca_key", ""), cwd, &ca_key)) return false; + unsecure_ = config->get("ssl_unsecure", false); + std::unique_ptr issuer_cert(SSLCert::load(logger_, ca_cert)); + if (!issuer_cert) return false; + std::unique_ptr issuer_key(SSLKey::load(logger_, ca_key)); + if (!issuer_key) return false; + store_.swap(store); + issuer_cert_.swap(issuer_cert); + issuer_key_.swap(issuer_key); + return true; + } + + DetectResult detect(void const* data, size_t avail) override { + if (avail == 0) return NEED_MORE; + auto d = static_cast(data); + // SSL 3.0 + if (d[0] == 0x16) { + if (avail < 2) return NEED_MORE; + if (d[1] != 0x03) return OTHER; // Need fixing when SSL 4.0 shows up + if (avail < 5) return NEED_MORE; + if (((d[3] << 8) | d[4]) < 9) // Min size of client hello + return OTHER; + if (avail < 6) return NEED_MORE; + return d[5] == 0x01 ? SSL : OTHER; + } + // SSL 2.0 + if (!(d[0] & 0x80)) return OTHER; + if (avail < 2) return NEED_MORE; + if (((d[0] & 0x7f) << 8 | d[1]) < 9) // Min size of client hello + return OTHER; + if (avail < 3) return NEED_MORE; + return d[2] == 0x01 ? SSL : OTHER; + } + + Connection* open(std::string const& host) override { + CacheEntry* entry; + CacheEntry _entry; + entry = cache_.get(host); + if (!entry) { + entry = &_entry; + if (!SSLCert::generate(logger_, entropy_.get(), + issuer_cert_.get(), issuer_key_.get(), host, + issuer_key_.get(), &entry->cert)) { + return nullptr; + } + cache_.insert(host, _entry); + } + std::unique_ptr conn(new ConnectionImpl(logger_)); + if (!conn->connect(entropy_.get(), store_.get(), entry->cert, + issuer_key_.get(), unsecure_, host)) return nullptr; + return conn.release(); + } + +private: + struct CacheEntry { + std::string cert; + }; + + bool load_file(std::string const& path, std::string const& cwd, + std::string* out) const { + std::ifstream in(Paths::join(cwd, path)); + if (!in.good()) { + logger_->out(Logger::ERR, "Unable to open: %s", path.c_str()); + return false; + } + out->clear(); + while (in.good()) { + char buffer[8192]; + in.read(buffer, sizeof(buffer)); + out->append(buffer, in.gcount()); + } + if (!in.eof()) { + logger_->out(Logger::ERR, "Unable to open: %s", path.c_str()); + return false; + } + return true; + } + + Logger* const logger_; + std::unique_ptr entropy_; + std::unique_ptr store_; + std::unique_ptr issuer_cert_; + std::unique_ptr issuer_key_; + bool unsecure_; + lru cache_; +}; + +} // namespace + +// static +Mitm* Mitm::create(Logger* logger, Config* config, std::string const& cwd) { + std::unique_ptr mitm(new MitmImpl(logger)); + if (!mitm->load(config, cwd)) return nullptr; + return mitm.release(); +} -- cgit v1.2.3-70-g09d2