From c5576c85c92c464ff3aa53f680ce18d8b51f60ab Mon Sep 17 00:00:00 2001 From: Aqua-sama <aqua@iserlohn-fortress.net> Date: Fri, 19 Apr 2019 17:27:39 +0300 Subject: Add plugin signature verification policies --- .gitignore | 2 +- Kconfig | 2 +- lib/plugin/Kconfig | 29 ------------ lib/pluginloader/Kconfig | 26 +++++++++++ lib/pluginloader/meson.build | 13 ++++++ lib/pluginloader/pluginloader.cpp | 93 +++++++++++++++++++++++++++++++++++++++ lib/pluginloader/pluginloader.h | 55 +++++++++++++++++++++++ linux/.config | 1 - meson.build | 3 +- src/browser.cpp | 13 ++++-- src/main.cpp | 3 +- src/meson.build | 7 +-- src/plugin/pluginloader.cpp | 77 -------------------------------- src/plugin/pluginloader.h | 13 ------ 14 files changed, 202 insertions(+), 135 deletions(-) delete mode 100644 lib/plugin/Kconfig create mode 100644 lib/pluginloader/Kconfig create mode 100644 lib/pluginloader/meson.build create mode 100644 lib/pluginloader/pluginloader.cpp create mode 100644 lib/pluginloader/pluginloader.h delete mode 100644 src/plugin/pluginloader.cpp delete mode 100644 src/plugin/pluginloader.h diff --git a/.gitignore b/.gitignore index 367c3c9..3af30f4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ build* subprojects/ lang/*.qm test/plugins.d - +publicKey.h .config .config.old diff --git a/Kconfig b/Kconfig index 0fbafaa..c7c0a1e 100644 --- a/Kconfig +++ b/Kconfig @@ -10,7 +10,7 @@ endmenu source 'lib/configuration/Kconfig' # Plugin loading -source 'lib/plugin/Kconfig' +source 'lib/pluginloader/Kconfig' config USEPLASMA bool "Enable KDE Frameworks integration" diff --git a/lib/plugin/Kconfig b/lib/plugin/Kconfig deleted file mode 100644 index 1de9403..0000000 --- a/lib/plugin/Kconfig +++ /dev/null @@ -1,29 +0,0 @@ -config USEPLUGINS - bool "Enable plugins" - default y - -menu "Plugin Settings" - depends on USEPLUGINS - - choice PLUGIN_SIGNATURE_CHECK - bool "Plugin Signature enforcement" - default PLUGIN_SIGNATURE_CHECKED - - config PLUGIN_SIGNATURE_IGNORED - bool "Don't check plugin signatures" - - config PLUGIN_SIGNATURE_NONFATAL - bool "Check signature validity, but always load plugins" - - config PLUGIN_SIGNATURE_CHECKED - bool "Don't load plugins with invalid signatures" - - config PLUGIN_SIGNATURE_ENFORCED - bool "Only load plugins with valid signatures" - - endchoice - - config PLUGIN_SIGNATURE_HASH - string "Hashing algorithm used by the signature" - default "SHA256" -endmenu diff --git a/lib/pluginloader/Kconfig b/lib/pluginloader/Kconfig new file mode 100644 index 0000000..28a3b73 --- /dev/null +++ b/lib/pluginloader/Kconfig @@ -0,0 +1,26 @@ +config USEPLUGINS + bool "Enable plugins" + default y + +menu "Plugin Settings" + depends on USEPLUGINS + + choice PLUGIN_SIGNATURE_CHECK + bool "Plugin Signature enforcement" + default PLUGIN_SIGNATURE_CHECKED + + config PLUGIN_SIGNATURE_IGNORED + bool "Don't check plugin signatures" + + config PLUGIN_SIGNATURE_CHECKED + bool "Don't load plugins with invalid signatures" + + config PLUGIN_SIGNATURE_ENFORCED + bool "Only load plugins with valid signatures" + + endchoice + + config PLUGIN_SIGNATURE_HASH + string "Hashing algorithm used by the signature" + default "SHA256" +endmenu diff --git a/lib/pluginloader/meson.build b/lib/pluginloader/meson.build new file mode 100644 index 0000000..4786156 --- /dev/null +++ b/lib/pluginloader/meson.build @@ -0,0 +1,13 @@ +dep_openssl = dependency('openssl') + +pluginloader_moc = mod_qt5.preprocess( + moc_headers: ['pluginloader.h'], + dependencies: dep_qt5 +) + +dep_pluginloader = declare_dependency( + include_directories: include_directories('.'), + link_with: static_library('plugin', + ['pluginloader.cpp', pluginloader_moc], + dependencies: [dep_qt5, dep_openssl, dep_genheaders]) +) diff --git a/lib/pluginloader/pluginloader.cpp b/lib/pluginloader/pluginloader.cpp new file mode 100644 index 0000000..f47c39e --- /dev/null +++ b/lib/pluginloader/pluginloader.cpp @@ -0,0 +1,93 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "pluginloader.h" +#include <QFile> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include "publicKey.h" + +PluginLoader::PluginLoader(const QString &fileName, const VerifyState sigLevel, QObject *parent) + : QPluginLoader(fileName, parent) + , requiredSignatureLevel(sigLevel) +{ +} + +PluginLoader::VerifyState PluginLoader::verify(const char *hashName) const +{ + const QString sigName = this->fileName() + ".sig"; + if(!QFile::exists(sigName)) { + return SignatureMissing; + } + + auto *bio = BIO_new_mem_buf(publicKey_pem, publicKey_pem_len); + Q_CHECK_PTR(bio); + + auto *key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + Q_CHECK_PTR(key); + + auto *ctx = EVP_MD_CTX_new(); + Q_CHECK_PTR(ctx); + + const auto *md = EVP_get_digestbyname(hashName); + Q_CHECK_PTR(md); + + int rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, key); + if(rc != 1) { + return SignatureMismatched; + } + + // read plugin into DigestVerifyUpdate + QFile plugin(this->fileName()); + plugin.open(QIODevice::ReadOnly); + int len = plugin.size(); + int read = 0; + auto *buf = new unsigned char[1024]; + while(len > 0) { + read = plugin.read((char*) buf, 1024); + len -= read; + + rc = EVP_DigestVerifyUpdate(ctx, buf, read); + if(rc != 1) + return SignatureComputeFailed; + } + delete buf; + plugin.close(); + + // read signature into DigestVerifyFinal + QFile sigFile(sigName); + sigFile.open(QIODevice::ReadOnly); + const int sig_len = sigFile.size(); + const auto* sig = [&sigFile, sig_len]() { + auto* buf = new unsigned char[sig_len]; + sigFile.read((char*) buf, sig_len); + return buf; + }(); + sigFile.close(); + + rc = EVP_DigestVerifyFinal(ctx, sig, sig_len); + delete sig; + + if(rc == 1) + return SignatureMatched; + else { + return SignatureMismatched; + } +} +/* +bool PluginLoader::load() +{ + if(signature == SignatureUnverified) + signature = this->verify(); + + if(signature > requiredSignatureLevel) + return QPluginLoader::load(); + else + return false; +} +*/ diff --git a/lib/pluginloader/pluginloader.h b/lib/pluginloader/pluginloader.h new file mode 100644 index 0000000..0c8bcd3 --- /dev/null +++ b/lib/pluginloader/pluginloader.h @@ -0,0 +1,55 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include <QPluginLoader> + +class PluginLoader : public QPluginLoader +{ + Q_OBJECT + +public: + enum VerifyState { + // uninitialized state + SignatureUnverified = -1, + + // signature is optional, match is optional + SignatureCheckIfAvailable = 1, + + SignatureComputeFailed = 2, // error computing signature + SignatureMismatched = 3, // signature does not match + + // signature is optional, match is required + SignatureMatchIfAvailable = 4, + + SignatureMissing = 5, // signature is not available + + // signature required, match is required + SignatureMatchRequired = 10, + + SignatureMatched = 20 // signature is matched + }; + + PluginLoader(const QString &fileName, const VerifyState sigLevel = SignatureMissing, QObject *parent = nullptr); + ~PluginLoader() = default; + + QString errorString() const + { + if(signature < requiredSignatureLevel) + return QString("Required signature level: %2; Signature level: %3").arg(QString::number((int) requiredSignatureLevel), QString::number((int) signature)); + else + return QPluginLoader::errorString(); + } + + VerifyState verify(const char *hashName = "SHA256") const; + //bool load(); + +private: + const VerifyState requiredSignatureLevel; + VerifyState signature = SignatureUnverified; +}; + diff --git a/linux/.config b/linux/.config index 6ba6018..3e8d40c 100644 --- a/linux/.config +++ b/linux/.config @@ -76,7 +76,6 @@ CONFIG_USEPLUGINS=y # Plugin Settings # # CONFIG_PLUGIN_SIGNATURE_IGNORED is not set -# CONFIG_PLUGIN_SIGNATURE_NONFATAL is not set CONFIG_PLUGIN_SIGNATURE_CHECKED=y # CONFIG_PLUGIN_SIGNATURE_ENFORCED is not set CONFIG_PLUGIN_SIGNATURE_HASH="SHA256" diff --git a/meson.build b/meson.build index 121b472..a2b5a17 100644 --- a/meson.build +++ b/meson.build @@ -26,8 +26,6 @@ dep_boost = dependency('boost', modules: ['program_options']) dep_spdlog = dependency('spdlog', fallback: ['spdlog', 'spdlog_dep'], version: '>=1.3.1') -dep_openssl = dependency('openssl') - optional_deps = [] if get_option('Breakpad').enabled() @@ -83,6 +81,7 @@ subdir('lib/addressbar') subdir('lib/bookmarks') subdir('lib/configuration') subdir('lib/downloads') +subdir('lib/pluginloader') subdir('lib/urlfilter') subdir('lib/webprofile') diff --git a/src/browser.cpp b/src/browser.cpp index db75fa1..e11a1da 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -38,7 +38,7 @@ #include "adblock/adblocklist.h" #include "hostlist/hostlist.h" #include <spdlog/spdlog.h> -#include "plugin/pluginloader.h" +#include <pluginloader.h> Browser::Browser(int &argc, char *argv[], bool allowSecondary) : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion) @@ -151,9 +151,14 @@ QPluginLoader *Browser::addPlugin(const QString &path) if(path.isEmpty()) return nullptr; - auto *loader = new PluginLoader(path, this); - spdlog::info("Verifying plugin: {}", loader->verify() ? "passed" : "failed"); - loader->load(); + auto *loader = new PluginLoader(path, PluginLoader::SignatureMatchIfAvailable, this); + const bool loaded = loader->load(); + spdlog::info("Loading plugin [{}]: {}", qUtf8Printable(path), loaded ? "passed" : "failed"); + + if(!loaded) { + delete loader; + return nullptr; + } auto *info = new PluginInfo(loader); m_plugins.append(info); diff --git a/src/main.cpp b/src/main.cpp index 0d6f466..8063b86 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,7 @@ #include <QFile> #include <QLibraryInfo> #include <QPluginLoader> -#include "plugin/pluginloader.h" +#include <pluginloader.h> #include <QTranslator> #include <memory> #include <plugininterface.h> @@ -72,7 +72,6 @@ int main(int argc, char **argv) // Load plugins for(const QString &path : Util::files(config->value<QString>("plugins.path").value(), {"*.so", "*.dll"})) { auto *loader = new PluginLoader(path); - spdlog::info("Verifying plugin: {}", loader->verify() ? "passed" : "failed"); const bool loaded = loader->load(); spdlog::info("{} plugin {}", loaded ? "Loaded" : "Failed to load", qUtf8Printable(path)); diff --git a/src/meson.build b/src/meson.build index 960cc69..8660534 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,7 +1,6 @@ # poi poi_moc = mod_qt5.preprocess( moc_headers: ['browser.h', - 'plugin/pluginloader.h', 'mainwindow/mainwindow.h', 'mainwindow/menubar.h', 'mainwindow/widgets/dockwidget.h', 'mainwindow/widgets/menusearch.h', 'mainwindow/widgets/navigationbar.h', 'mainwindow/widgets/searchform.h', 'session/savesessiondialog.h', 'session/sessiondialog.h', 'session/sessionform.h', 'subwindow/subwindow.h', 'subwindow/tabwidget.h', @@ -14,15 +13,13 @@ poi_moc = mod_qt5.preprocess( poi = executable(get_option('poiName'), install: true, cpp_args: ['-DQAPPLICATION_CLASS=QApplication'], - dependencies: [dep_qt5, dep_boost, dep_spdlog, dep_openssl, dep_SingleApplication, dep_genheaders, optional_deps, - dep_about, dep_addressbar, dep_bookmarks, dep_configuration, dep_downloads, dep_urlfilter, dep_webprofile], + dependencies: [dep_qt5, dep_boost, dep_spdlog, dep_SingleApplication, dep_genheaders, optional_deps, + dep_about, dep_addressbar, dep_bookmarks, dep_configuration, dep_downloads, dep_pluginloader, dep_urlfilter, dep_webprofile], include_directories: [include], sources: ['main.cpp', 'builtins.cpp', 'crashhandler.cpp', poi_moc, 'browser.cpp', 'util.cpp', 'util.h', - 'plugin/pluginloader.cpp', - 'mainwindow/mainwindow.cpp', 'mainwindow/menubar.cpp', 'mainwindow/widgets/dockwidget.cpp', diff --git a/src/plugin/pluginloader.cpp b/src/plugin/pluginloader.cpp deleted file mode 100644 index d1626f2..0000000 --- a/src/plugin/pluginloader.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "pluginloader.h" -#include <QFile> -#include <openssl/evp.h> -#include <openssl/pem.h> -#include "publicKey.h" -#include <spdlog/spdlog.h> - -PluginLoader::PluginLoader(const QString &fileName, QObject *parent) - : QPluginLoader(fileName, parent) -{ -} - -bool PluginLoader::verify(const char *hashName) const -{ - const QString sigName = this->fileName() + ".sig"; - if(!QFile::exists(sigName)) { - spdlog::error("Signature does not exist: {}", qUtf8Printable(sigName)); - return false; - } - - auto *bio = BIO_new_mem_buf(publicKey_pem, publicKey_pem_len); - Q_CHECK_PTR(bio); - - auto *key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); - Q_CHECK_PTR(key); - - auto *ctx = EVP_MD_CTX_new(); - Q_CHECK_PTR(ctx); - - const auto *md = EVP_get_digestbyname(hashName); - Q_CHECK_PTR(md); - - int rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, key); - if(rc != 1) { - spdlog::error("DigestVerifyInit failed: %i", rc); - return false; - } - - // read plugin into DigestVerifyUpdate - QFile plugin(this->fileName()); - plugin.open(QIODevice::ReadOnly); - int len = plugin.size(); - int read = 0; - auto *buf = new unsigned char[1024]; - while(len > 0) { - read = plugin.read((char*) buf, 1024); - len -= read; - - rc = EVP_DigestVerifyUpdate(ctx, buf, read); - if(rc != 1) - spdlog::error("DigestVerifyUpdate failed: %i", rc); - } - delete buf; - plugin.close(); - - // read signature into DigestVerifyFinal - QFile sigFile(sigName); - sigFile.open(QIODevice::ReadOnly); - const int sig_len = sigFile.size(); - const auto* sig = [&sigFile, sig_len]() { - auto* buf = new unsigned char[sig_len]; - sigFile.read((char*) buf, sig_len); - return buf; - }(); - sigFile.close(); - - rc = EVP_DigestVerifyFinal(ctx, sig, sig_len); - delete sig; - - if(rc == 1) - return true; - else { - spdlog::error("DigestVerifyFinal failed: %i", rc); - return false; - } -} - diff --git a/src/plugin/pluginloader.h b/src/plugin/pluginloader.h deleted file mode 100644 index 8d186aa..0000000 --- a/src/plugin/pluginloader.h +++ /dev/null @@ -1,13 +0,0 @@ -#include <QPluginLoader> - -class PluginLoader : public QPluginLoader -{ - Q_OBJECT - -public: - PluginLoader(const QString &fileName, QObject *parent = nullptr); - ~PluginLoader() = default; - - bool verify(const char *hashName = "SHA256") const; -}; - -- cgit v1.2.1