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

---
 lib/plugin/Kconfig                | 29 ------------
 lib/pluginloader/Kconfig          | 26 +++++++++++
 lib/pluginloader/meson.build      | 13 ++++++
 lib/pluginloader/pluginloader.cpp | 93 +++++++++++++++++++++++++++++++++++++++
 lib/pluginloader/pluginloader.h   | 55 +++++++++++++++++++++++
 5 files changed, 187 insertions(+), 29 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

(limited to 'lib')

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;
+};
+
-- 
cgit v1.2.1