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