Browse Source

Add plugin signature verification policies

master
Aqua-sama 7 months ago
parent
commit
c5576c85c9
Signed by: Aqua-sama <aqua@iserlohn-fortress.net> GPG Key ID: 5378B8349C1D5ADA
12 changed files with 110 additions and 43 deletions
  1. +1
    -1
      .gitignore
  2. +1
    -1
      Kconfig
  3. +0
    -3
      lib/pluginloader/Kconfig
  4. +13
    -0
      lib/pluginloader/meson.build
  5. +27
    -11
      lib/pluginloader/pluginloader.cpp
  6. +55
    -0
      lib/pluginloader/pluginloader.h
  7. +0
    -1
      linux/.config
  8. +1
    -2
      meson.build
  9. +9
    -4
      src/browser.cpp
  10. +1
    -2
      src/main.cpp
  11. +2
    -5
      src/meson.build
  12. +0
    -13
      src/plugin/pluginloader.h

+ 1
- 1
.gitignore View File

@@ -8,7 +8,7 @@ build*
subprojects/
lang/*.qm
test/plugins.d
publicKey.h
.config
.config.old


+ 1
- 1
Kconfig View File

@@ -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"

lib/plugin/Kconfig → lib/pluginloader/Kconfig View File

@@ -12,9 +12,6 @@ menu "Plugin Settings"
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"


+ 13
- 0
lib/pluginloader/meson.build View File

@@ -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])
)

src/plugin/pluginloader.cpp → lib/pluginloader/pluginloader.cpp View File

@@ -1,21 +1,28 @@
/*
* 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"
#include <spdlog/spdlog.h>

PluginLoader::PluginLoader(const QString &fileName, QObject *parent)
PluginLoader::PluginLoader(const QString &fileName, const VerifyState sigLevel, QObject *parent)
: QPluginLoader(fileName, parent)
, requiredSignatureLevel(sigLevel)
{
}

bool PluginLoader::verify(const char *hashName) const
PluginLoader::VerifyState 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;
return SignatureMissing;
}

auto *bio = BIO_new_mem_buf(publicKey_pem, publicKey_pem_len);
@@ -32,8 +39,7 @@ bool PluginLoader::verify(const char *hashName) const

int rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, key);
if(rc != 1) {
spdlog::error("DigestVerifyInit failed: %i", rc);
return false;
return SignatureMismatched;
}

// read plugin into DigestVerifyUpdate
@@ -48,7 +54,7 @@ bool PluginLoader::verify(const char *hashName) const

rc = EVP_DigestVerifyUpdate(ctx, buf, read);
if(rc != 1)
spdlog::error("DigestVerifyUpdate failed: %i", rc);
return SignatureComputeFailed;
}
delete buf;
plugin.close();
@@ -68,10 +74,20 @@ bool PluginLoader::verify(const char *hashName) const
delete sig;

if(rc == 1)
return true;
return SignatureMatched;
else {
spdlog::error("DigestVerifyFinal failed: %i", rc);
return false;
return SignatureMismatched;
}
}
/*
bool PluginLoader::load()
{
if(signature == SignatureUnverified)
signature = this->verify();

if(signature > requiredSignatureLevel)
return QPluginLoader::load();
else
return false;
}
*/

+ 55
- 0
lib/pluginloader/pluginloader.h View File

@@ -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;
};


+ 0
- 1
linux/.config View File

@@ -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"

+ 1
- 2
meson.build View File

@@ -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')


+ 9
- 4
src/browser.cpp View File

@@ -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);

+ 1
- 2
src/main.cpp View File

@@ -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));


+ 2
- 5
src/meson.build View File

@@ -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',

+ 0
- 13
src/plugin/pluginloader.h View File

@@ -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;
};


Loading…
Cancel
Save