From 95d92e52ed6d71c21433b382f8a178a04b04954b Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Mon, 11 Mar 2019 19:39:06 +0200 Subject: Add PluginLoader class - PluginLoader::verify can be used to check if the plugin has a valid (SHA512/RSA 4096) signature. - Uses nn OpenSSL public key that is embedded during the compile. --- linux/makepkg/PKGBUILD | 27 ++++++++++------ meson.build | 4 ++- src/browser.cpp | 4 ++- src/main.cpp | 6 ++-- src/meson.build | 5 ++- src/plugin/pluginloader.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++++ src/plugin/pluginloader.h | 13 ++++++++ 7 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 src/plugin/pluginloader.cpp create mode 100644 src/plugin/pluginloader.h diff --git a/linux/makepkg/PKGBUILD b/linux/makepkg/PKGBUILD index 8c6bed3..3907ff8 100644 --- a/linux/makepkg/PKGBUILD +++ b/linux/makepkg/PKGBUILD @@ -14,10 +14,10 @@ license=('GPL3') depends=('qt5-webengine>=5.11.0' 'boost-libs>=1.66.0') optdepends=('firejail: launch a sandboxed instance') -makedepends=('git' 'meson' 'pkg-config' 'python-kconfiglib' 'asciidoctor') +makedepends=('git' 'meson' 'pkg-config' 'python-kconfiglib' 'asciidoctor' 'openssl') # this is the central repository -source=("git+https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote.git" +source=("git+https://neueland.iserlohn-fortress.net/gitea/aeon/smolbote.git" "git+https://github.com/itay-grudev/SingleApplication.git") sha512sums=('SKIP' @@ -32,6 +32,17 @@ prepare() { git submodule init git config submodule.3rd-party/SingleApplication/SingleApplication.git.url $srcdir/SingleApplication git submodule update 3rd-party/SingleApplication/SingleApplication.git + + msg "Creating OpenSSL signing key" + mkdir $srcdir/signing + cd $srcdir/signing + # generate rsa keypair + openssl genrsa -out privateKey.pem 4096 + msg2 "RSA/4096 key created in $srcdir/signing/privateKey.pem. Keep this key if you want to sign additional plugins." + + openssl rsa -in privateKey.pem -pubout -out publicKey.pem + xxd -i publicKey.pem $srcdir/smolbote/src/plugin/publicKey.h + msg2 "Public key exported, and will be embedded into the resulting application. This will break reproducible builds." } pkgver() { @@ -76,13 +87,9 @@ package() { cd $srcdir/build DESTDIR="$pkgdir" ninja install - #msg Creating signing key in $srcdir/build/gpg - #mkdir $srcdir/build/gpg - #gpg2 --homedir=$srcdir/build/gpg --batch --generate-key $srcdir/smolbote/tools/gpgkey.preset - - #msg Signing plugins - #for so in $pkgdir/usr/local/lib/smolbote/plugins/*.so; do - # gpg2 --homedir=$srcdir/build/gpg --batch --yes --local-user=smolbote@localhost --detach-sign --output=$so.sig $so - #done + msg Signing plugins + for so in $pkgdir/usr/local/lib/smolbote/plugins/*.so; do + openssl dgst -sha256 -sign $srcdir/signing/privateKey.pem -out $so.sig $so + done } diff --git a/meson.build b/meson.build index 9310eb0..121b472 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('smolbote', 'cpp', - version: 'master', + version: '0.1.0', default_options: ['cpp_std=c++17', 'strip=true', 'warning_level=3'], license: 'GPL3', meson_version: '>=0.49.0' @@ -26,6 +26,8 @@ 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() diff --git a/src/browser.cpp b/src/browser.cpp index 69db6b2..db75fa1 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -38,6 +38,7 @@ #include "adblock/adblocklist.h" #include "hostlist/hostlist.h" #include +#include "plugin/pluginloader.h" Browser::Browser(int &argc, char *argv[], bool allowSecondary) : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion) @@ -150,7 +151,8 @@ QPluginLoader *Browser::addPlugin(const QString &path) if(path.isEmpty()) return nullptr; - auto *loader = new QPluginLoader(path, this); + auto *loader = new PluginLoader(path, this); + spdlog::info("Verifying plugin: {}", loader->verify() ? "passed" : "failed"); loader->load(); auto *info = new PluginInfo(loader); diff --git a/src/main.cpp b/src/main.cpp index dfe58de..0d6f466 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #include #include #include +#include "plugin/pluginloader.h" #include #include #include @@ -69,8 +70,9 @@ int main(int argc, char **argv) CommandHash_t pluginCommands; // Load plugins - for(const QString &path : Util::files(config->value("plugins.path").value())) { - auto *loader = new QPluginLoader(path); + for(const QString &path : Util::files(config->value("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 ef0f989..960cc69 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,7 @@ # 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', @@ -13,13 +14,15 @@ 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_SingleApplication, dep_genheaders, optional_deps, + 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], 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 new file mode 100644 index 0000000..d1626f2 --- /dev/null +++ b/src/plugin/pluginloader.cpp @@ -0,0 +1,77 @@ +#include "pluginloader.h" +#include +#include +#include +#include "publicKey.h" +#include + +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 new file mode 100644 index 0000000..8d186aa --- /dev/null +++ b/src/plugin/pluginloader.h @@ -0,0 +1,13 @@ +#include + +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