From 51587ef41ab94dca2900267a5edcab4345b8f663 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Wed, 26 Jul 2017 23:06:58 +0200 Subject: Add SSL interception to Setup in GUI --- configure.ac | 1 + data/tp-monitor-gtk.gschema.xml | 24 ++++++++ src/gui_form.hh | 6 ++ src/gui_gtk.cc | 125 +++++++++++++++++++++++++++++++++++----- src/gui_qt.cc | 124 ++++++++++++++++++++++++++++++++++----- src/monitor-gui.cc | 111 ++++++++++++++++++++++++++++++++++- 6 files changed, 361 insertions(+), 30 deletions(-) diff --git a/configure.ac b/configure.ac index faa383c..95cb2ea 100644 --- a/configure.ac +++ b/configure.ac @@ -114,6 +114,7 @@ AS_IF([test x$have_ssl != x1], AC_SUBST([SSL_CFLAGS]) AC_SUBST([SSL_LIBS]) +AC_DEFINE_UNQUOTED([HAVE_SSL],[$have_ssl],[define to 1 if any SSL support is compiled in]) AM_CONDITIONAL([HAVE_SSL],[test "x$have_ssl" = "x1"]) AM_CONDITIONAL([HAVE_MBEDTLS],[test "x$ssl_mbedtls" = "x1"]) AM_CONDITIONAL([HAVE_OPENSSL],[test "x$ssl_openssl" = "x1"]) diff --git a/data/tp-monitor-gtk.gschema.xml b/data/tp-monitor-gtk.gschema.xml index 6fbb396..324ceb1 100644 --- a/data/tp-monitor-gtk.gschema.xml +++ b/data/tp-monitor-gtk.gschema.xml @@ -30,5 +30,29 @@ Last generate CA output filename + + "" + + Last file for SSL certficate bundle + + + + "" + + Last file for SSL CA selected + + + + false + + Last selection if SSL interception should be used + + + + false + + Last selection if SSL interception should allow unsecure connections + + diff --git a/src/gui_form.hh b/src/gui_form.hh index c91ebce..1c65ee6 100644 --- a/src/gui_form.hh +++ b/src/gui_form.hh @@ -40,6 +40,11 @@ public: std::string const& description, uint8_t flags, std::vector const& filter) = 0; + virtual void add_bool(std::string const& id, std::string const& label, + bool value, std::string const& description) = 0; + + virtual void enable(std::string const& id, bool enable) = 0; + virtual void add_listener(Listener* listener) = 0; virtual void remove_listener(Listener* listener) = 0; @@ -50,6 +55,7 @@ public: virtual std::string get_string(std::string const& id) const = 0; virtual uint64_t get_number(std::string const& id) const = 0; virtual std::string get_file(std::string const& id) const = 0; + virtual bool get_bool(std::string const& id) const = 0; protected: GuiForm() {} diff --git a/src/gui_gtk.cc b/src/gui_gtk.cc index c7031a8..301b92f 100644 --- a/src/gui_gtk.cc +++ b/src/gui_gtk.cc @@ -529,7 +529,6 @@ public: g_settings_sync(); } - using Config::get; std::string const& get(std::string const& key, std::string const& fallback) override { auto variant = g_settings_get_user_value(settings_.get(), key.c_str()); @@ -546,6 +545,13 @@ public: g_variant_unref(variant); return memory_[key].c_str(); } + bool get(std::string const& key, bool fallback) override { + auto variant = g_settings_get_user_value(settings_.get(), key.c_str()); + if (!variant) return fallback; + bool ret = g_variant_get_boolean(variant); + g_variant_unref(variant); + return ret; + } bool is_set(std::string const& key) override { auto variant = g_settings_get_user_value(settings_.get(), key.c_str()); if (!variant) return false; @@ -555,6 +561,9 @@ public: void set(std::string const& key, std::string const& value) override { g_settings_set_string(settings_.get(), key.c_str(), value.c_str()); } + void set(std::string const& key, bool value) override { + g_settings_set_boolean(settings_.get(), key.c_str(), value); + } void remove(std::string const& key) override { g_settings_reset(settings_.get(), key.c_str()); } @@ -931,6 +940,37 @@ public: flags, filter)); } + void add_bool(std::string const& id, std::string const& label, + bool value, std::string const& description) override { + values_.emplace_back(new BoolValue(this, id, label, description, value)); + } + + void enable(std::string const& id, bool enable) override { + for (auto const& value : values_) { + if (value->id_ == id) { + value->enable_ = enable; + if (value->entry_) { + gtk_widget_set_sensitive(value->entry_, enable); + } + switch (value->type_) { + case STRING: + case NUMBER: + case BOOLEAN: + break; + case FILE: { + auto v = static_cast(value.get()); + if (v->button_) { + gtk_widget_set_sensitive(GTK_WIDGET(v->button_), enable); + } else if (v->chooser_) { + gtk_widget_set_sensitive(GTK_WIDGET(v->chooser_), enable); + } + break; + } + } + } + } + } + std::string get_string(std::string const& id) const override { for (auto const& value : values_) { if (value->id_ == id && value->type_ == STRING) { @@ -980,6 +1020,19 @@ public: return ""; } + bool get_bool(std::string const& id) const override { + for (auto const& value : values_) { + if (value->id_ == id && value->type_ == BOOLEAN) { + if (value->entry_) { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(value->entry_)); + } + return static_cast(value.get())->value_; + } + } + assert(false); + return false; + } + void set_error(std::string const& error) override { if (!error_) { assert(false); @@ -1019,9 +1072,17 @@ public: gtk_grid_attach(GTK_GRID(grid), label, 0, row++, 3, 1); } for (auto& value : values_) { - auto label = gtk_label_new(value->label_.c_str()); - value->entry_ = gtk_entry_new(); - gtk_entry_set_activates_default(GTK_ENTRY(value->entry_), true); + GtkWidget* label; + if (value->type_ != BOOLEAN) { + label = gtk_label_new(value->label_.c_str()); + } else { + label = nullptr; + } + if (value->type_ != FILE + || (static_cast(value.get())->flags_ & FILE_SAVE)) { + value->entry_ = gtk_entry_new(); + gtk_entry_set_activates_default(GTK_ENTRY(value->entry_), true); + } GtkWidget* extra = nullptr; switch (value->type_) { case STRING: @@ -1044,11 +1105,10 @@ public: case FILE: { auto v = static_cast(value.get()); if ((v->flags_ & FILE_SAVE) == 0) { - gtk_widget_destroy(value->entry_); - auto button = gtk_file_chooser_button_new( + assert(!value->entry_); + value->entry_ = gtk_file_chooser_button_new( v->label_.c_str(), GTK_FILE_CHOOSER_ACTION_OPEN); - v->chooser_ = GTK_FILE_CHOOSER(button); - value->entry_ = button; + v->chooser_ = GTK_FILE_CHOOSER(value->entry_); g_signal_connect(G_OBJECT(v->chooser_), "file-set", G_CALLBACK(file_set), value.get()); } else { @@ -1061,6 +1121,7 @@ public: g_signal_connect(G_OBJECT(extra), "clicked", G_CALLBACK(show_filechooser), v); v->chooser_ = GTK_FILE_CHOOSER(chooser); + v->button_ = extra; gtk_editable_set_editable(GTK_EDITABLE(value->entry_), false); gtk_file_chooser_set_do_overwrite_confirmation(v->chooser_, true); g_signal_connect(G_OBJECT(value->entry_), "changed", @@ -1079,13 +1140,28 @@ public: } break; } + case BOOLEAN: + assert(!label); + gtk_widget_destroy(value->entry_); + value->entry_ = gtk_check_button_new_with_label(value->label_.c_str()); + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(value->entry_), + static_cast(value.get())->value_); + g_signal_connect(G_OBJECT(value->entry_), "toggled", + G_CALLBACK(toggled), value.get()); + break; + } + gint col = 0; + if (label) { + gtk_grid_attach(GTK_GRID(grid), label, col++, row, 1, 1); } - gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1); + gtk_widget_set_sensitive(value->entry_, value->enable_); if (extra) { - gtk_grid_attach(GTK_GRID(grid), value->entry_, 1, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), extra, 2, row, 1, 1); + gtk_widget_set_sensitive(extra, value->enable_); + gtk_grid_attach(GTK_GRID(grid), value->entry_, col, row, 2 - col, 1); + gtk_grid_attach(GTK_GRID(grid), extra, ++col, row, 1, 1); } else { - gtk_grid_attach(GTK_GRID(grid), value->entry_, 1, row, 2, 1); + gtk_grid_attach(GTK_GRID(grid), value->entry_, col, row, 3 - col, 1); } row++; if (!value->description_.empty()) { @@ -1138,6 +1214,10 @@ public: v->chooser_ = nullptr; break; } + case BOOLEAN: + static_cast(value.get())->value_ = + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(value->entry_)); + break; } value->entry_ = nullptr; } @@ -1153,6 +1233,7 @@ protected: STRING, NUMBER, FILE, + BOOLEAN, }; struct Value { @@ -1161,12 +1242,13 @@ protected: std::string const id_; std::string const label_; std::string const description_; + bool enable_; GtkWidget* entry_; Value(GtkGuiForm* me, Type type, std::string const& id, std::string const& label, std::string const& description) : me_(me), type_(type), id_(id), label_(label), description_(description), - entry_(nullptr) { + enable_(true), entry_(nullptr) { } }; @@ -1193,12 +1275,22 @@ protected: uint8_t flags_; std::vector filter_; GtkFileChooser* chooser_; + GtkWidget* button_; FileValue(GtkGuiForm* me, std::string const& id, std::string const& label, std::string const& description, std::string const& value, uint8_t flags, std::vector const& filter) : Value(me, FILE, id, label, description), value_(value), - flags_(flags), filter_(filter) { + flags_(flags), filter_(filter), chooser_(nullptr), button_(nullptr) { + } + }; + + struct BoolValue : public Value { + bool value_; + + BoolValue(GtkGuiForm* me, std::string const& id, std::string const& label, + std::string const& description, bool value) + : Value(me, BOOLEAN, id, label, description), value_(value) { } }; @@ -1264,6 +1356,11 @@ protected: value->me_->notify_changed(value->id_); } + static void toggled(GtkToggleButton* UNUSED(button), gpointer user_data) { + auto value = reinterpret_cast(user_data); + value->me_->notify_changed(value->id_); + } + virtual gint add_extra_widgets(GtkGrid* UNUSED(grid), gint row, gint UNUSED(colspan)) { return row; diff --git a/src/gui_qt.cc b/src/gui_qt.cc index cad859e..77e292e 100644 --- a/src/gui_qt.cc +++ b/src/gui_qt.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -863,6 +864,37 @@ public: flags, filter)); } + void add_bool(std::string const& id, std::string const& label, + bool value, std::string const& description) override { + values_.emplace_back(new BoolValue(id, label, description, value)); + } + + void enable(std::string const& id, bool enable) override { + for (auto const& value : values_) { + if (value->id_ == id) { + value->enable_ = enable; + if (value->edit_) { + value->edit_->setEnabled(enable); + } + switch (value->type_) { + case STRING: + case NUMBER: + break; + case FILE: { + auto v = static_cast(value.get()); + if (v->button_) v->button_->setEnabled(enable); + break; + } + case BOOLEAN: { + auto v = static_cast(value.get()); + if (v->check_) v->check_->setEnabled(enable); + break; + } + } + } + } + } + std::string get_string(std::string const& id) const override { for (auto const& value : values_) { if (value->id_ == id && value->type_ == STRING) { @@ -905,6 +937,20 @@ public: return ""; } + bool get_bool(std::string const& id) const override { + for (auto const& value : values_) { + if (value->id_ == id && value->type_ == BOOLEAN) { + auto v = static_cast(value.get()); + if (v->check_) { + return v->check_->isChecked(); + } + return v->value_; + } + } + assert(false); + return ""; + } + void set_error(std::string const& error) override { if (!error_) { assert(false); @@ -927,6 +973,7 @@ protected: STRING, NUMBER, FILE, + BOOLEAN, }; struct Value { @@ -934,12 +981,13 @@ protected: std::string const id_; std::string const label_; std::string const description_; + bool enable_; QLineEdit* edit_; Value(Type type, std::string const& id, std::string const& label, std::string const& description) : type_(type), id_(id), label_(label), description_(description), - edit_(nullptr) { + enable_(true), edit_(nullptr) { } }; @@ -967,13 +1015,25 @@ protected: std::string value_; uint8_t flags_; std::vector filter_; + QPushButton* button_; FileValue(std::string const& id, std::string const& label, std::string const& description, std::string const& value, uint8_t flags, std::vector const& filter) : Value(FILE, id, label, description), value_(value), - flags_(flags), filter_(filter) { + flags_(flags), filter_(filter), button_(nullptr) { + } + }; + + struct BoolValue : public Value { + bool value_; + QCheckBox* check_; + + BoolValue(std::string const& id, std::string const& label, + std::string const& description, bool value) + : Value(BOOLEAN, id, label, description), value_(value), + check_(nullptr) { } }; @@ -1000,9 +1060,16 @@ protected: layout->addWidget(text, row++, 0, 1, 3); } for (auto& value : values_) { - auto label = new QLabel(QString::fromStdString(value->label_)); - auto edit = new QLineEdit(); - value->edit_ = edit; + QWidget* label; + QLineEdit* edit; + if (value->type_ != BOOLEAN) { + label = new QLabel(QString::fromStdString(value->label_)); + edit = new QLineEdit(); + value->edit_ = edit; + } else { + label = nullptr; + edit = nullptr; + } switch (value->type_) { case STRING: edit->setText(QString::fromStdString( @@ -1020,24 +1087,45 @@ protected: edit->setText(QString::fromStdString( static_cast(value.get())->value_)); break; + case BOOLEAN: { + auto v = static_cast(value.get()); + assert(!label); + v->check_ = new QCheckBox(QString::fromStdString(v->label_)); + label = v->check_; + v->check_->setChecked(v->value_); + v->check_->setEnabled(v->enable_); + v->check_->connect(v->check_, &QCheckBox::stateChanged, + [=](int UNUSED(state)) { + notify_changed(v->id_); + }); + break; + } } auto vp = value.get(); - edit->connect(edit, &QLineEdit::textChanged, - [=](QString const& UNUSED(text)) { - notify_changed(vp->id_); - }); - layout->addWidget(label, row, 0); + if (edit) { + edit->connect(edit, &QLineEdit::textChanged, + [=](QString const& UNUSED(text)) { + notify_changed(vp->id_); + }); + edit->setEnabled(value->enable_); + } if (value->type_ == FILE) { - auto button = new QPushButton("..."); + auto v = static_cast(vp); + v->button_ = new QPushButton("..."); + v->button_->setEnabled(v->enable_); edit->setReadOnly(true); - dialog_->connect(button, &QPushButton::clicked, + dialog_->connect(v->button_, &QPushButton::clicked, [=](bool UNUSED(checked)) { - showFileDialog(static_cast(vp)); + showFileDialog(v); }); + layout->addWidget(label, row, 0); layout->addWidget(edit, row, 1); - layout->addWidget(button, row, 2); - } else { + layout->addWidget(v->button_, row, 2); + } else if (edit) { + layout->addWidget(label, row, 0); layout->addWidget(edit, row, 1, 1, 2); + } else { + layout->addWidget(label, row, 0, 1, 3); } ++row; if (!value->description_.empty()) { @@ -1074,6 +1162,12 @@ protected: static_cast(value.get())->value_ = value->edit_->text().toStdString(); break; + case BOOLEAN: { + auto v = static_cast(value.get()); + v->value_ = v->check_->isChecked(); + v->check_ = nullptr; + break; + } } value->edit_ = nullptr; } diff --git a/src/monitor-gui.cc b/src/monitor-gui.cc index d293935..037f2ff 100644 --- a/src/monitor-gui.cc +++ b/src/monitor-gui.cc @@ -67,6 +67,25 @@ bool parse_address(std::string const& addr, std::string* host, uint16_t* port) { return true; } +#if HAVE_SSL +const char* CERT_BUNDLE[] = { + "/etc/ssl/certs/ca-certificates.crt", + NULL +}; +std::string default_cert_bundle() { + static std::string cache; + if (cache.empty()) { + for (auto bundle = CERT_BUNDLE; *bundle; ++bundle) { + if (access(*bundle, R_OK) == 0) { + cache.assign(*bundle); + break; + } + } + } + return cache; +} +#endif // HAVE_SSL + class PackageList : public GuiListModel { public: struct Package { @@ -374,7 +393,13 @@ private: class SetupFormListener : public GuiFormApply::Listener { public: - void changed(GuiForm* UNUSED(form), std::string const& UNUSED(id)) override { + void changed(GuiForm* form, std::string const& id) override { + if (id.compare("mitm") == 0) { + auto enable = form->get_bool(id); + form->enable("ssl-certs", enable); + form->enable("ssl-ca", enable); + form->enable("unsecure", enable); + } } bool about_to_close(GuiForm* form) override { @@ -383,6 +408,19 @@ private: form->set_error("Empty proxy port"); return false; } + auto const& mitm = form->get_bool("mitm"); + if (mitm) { + auto const& certs = form->get_file("ssl-certs"); + if (certs.empty()) { + form->set_error("No SSL certificates file set"); + return false; + } + auto const& ca = form->get_file("ssl-ca"); + if (ca.empty()) { + form->set_error("No SSL CA file set"); + return false; + } + } return true; } }; @@ -411,8 +449,49 @@ private: } config_->set("bind", bind); config_->set("port", port); +#if HAVE_SSL + auto const& mitm = form->get_bool("mitm"); + config_->set("mitm", mitm); proxy_config_->set("proxy_bind", bind); proxy_config_->set("proxy_port", port); + if (mitm) { + auto const& certs = form->get_file("ssl-certs"); + auto const& ca = form->get_file("ssl-ca"); + auto const& unsecure = form->get_bool("unsecure"); + if (certs.empty()) { + form->set_error("No SSL certificates file set"); + form->applied(false); + return; + } + if (access(certs.c_str(), R_OK)) { + form->set_error("SSL certificates file not readable"); + form->applied(false); + return; + } + if (ca.empty()) { + form->set_error("No SSL CA file set"); + form->applied(false); + return; + } + if (access(ca.c_str(), R_OK)) { + form->set_error("SSL CA file not readable"); + form->applied(false); + return; + } + config_->set("ssl-certs", certs); + config_->set("ssl-ca", ca); + config_->set("unsecure", unsecure); + proxy_config_->set("ssl_cert_bundle", certs); + proxy_config_->set("ssl_ca_cert", ca); + proxy_config_->set("ssl_ca_key", ca); + proxy_config_->set("ssl_unsecure", unsecure); + } else { + proxy_config_->remove("ssl_cert_bundle"); + proxy_config_->remove("ssl_ca_cert"); + proxy_config_->remove("ssl_ca_key"); + proxy_config_->remove("ssl_unsecure"); + } +#endif // HAVE_SSL proxy_config_->set("__one_single_monitor", "true"); io::auto_fd proxy_fd( Proxy::setup_accept_socket(proxy_config_, proxy_logger_)); @@ -622,6 +701,36 @@ public: connect_->add_string("port", "Port", main_->config()->get("port", "8080"), "Port to listen for proxy connections on."); +#if HAVE_SSL + bool mitm = main_->config()->get("mitm", false); + connect_->add_bool("mitm", "Intercept SSL traffic", + mitm, + "If enabled SSL connections will be intercepted" + " by the proxy to log unencrypted traffic."); + std::vector filter; + filter.emplace_back(); + filter.back().name = "PEM"; + filter.back().masks.emplace_back("*.pem"); + connect_->add_file("ssl-ca", "Certificate Authority", + main_->config()->get("ssl-ca", + main_->config()->get("genca-output", "")), + "CA and key to sign all fake server certificates with", + GuiForm::FILE_OPEN, filter); + connect_->enable("ssl-ca", mitm); + filter.back().name = "CRT"; + filter.back().masks.emplace_back("*.crt"); + connect_->add_file("ssl-certs", "Certificate bundle", + main_->config()->get("ssl-certs", + default_cert_bundle()), + "Certificate bundle to verify remote SSL connections", + GuiForm::FILE_OPEN, filter); + connect_->enable("ssl-certs", mitm); + connect_->add_bool("unsecure", "Allow unsecure remote connections", + main_->config()->get("unsecure", false), + "Allow deprecated protocols such as SSLv3 and " + " self-signed or missmatched certificates"); + connect_->enable("unsecure", mitm); +#endif // HAVE_SSL connect_->add_listener(lst.get()); if (connect_->show(main_.get())) { monitor_->attach(); -- cgit v1.2.3-70-g09d2