From 0bf5450365934c39ed0bb480712adaab2fa54386 Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Sun, 26 Jan 2020 23:14:53 +0200 Subject: pluginloader: add test for PluginLoader::verify --- lib/pluginloader/meson.build | 27 ++++++++++++++++ lib/pluginloader/pluginloader.cpp | 40 +++++++++++------------ lib/pluginloader/pluginloader.h | 42 ++++++++++--------------- lib/pluginloader/test/pluginloader-sigmatch.cpp | 17 ++++++++++ lib/pluginloader/write-random.py | 15 +++++++++ src/browser.cpp | 8 ++++- src/main.cpp | 8 ++++- 7 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 lib/pluginloader/test/pluginloader-sigmatch.cpp create mode 100755 lib/pluginloader/write-random.py diff --git a/lib/pluginloader/meson.build b/lib/pluginloader/meson.build index cbca725..534f385 100644 --- a/lib/pluginloader/meson.build +++ b/lib/pluginloader/meson.build @@ -25,3 +25,30 @@ dep_pluginloader = declare_dependency( dependencies: [dep_qt5, dependency('openssl', required: true)]) ) +openssl = find_program('openssl', required: true) + +# generate a test file that would be signed +signedfile_dat = custom_target('signedfile.dat', + input: 'write-random.py', + output: 'signedfile.dat', + command: [ python3, '@INPUT@', '--output=@OUTPUT@' ] +) +# sign test file +signedfile_sig = custom_target('signedfile.dat.sig', + input: signedfile_dat, + output: 'signedfile.dat.sig', + command: [ openssl, 'dgst', '-sha256', '-sign', private_pem, '-out', '@OUTPUT@', '@INPUT@' ] +) + +signedfile_idep = declare_dependency(sources: [ signedfile_dat, signedfile_sig ]) + +pluginloader_sigmatch = executable('pluginloader-sigmatch', + sources: [ 'test/pluginloader-sigmatch.cpp' ], + dependencies: [ dep_qt5, dep_gtest, dep_pluginloader, signedfile_idep ] +) + +test('pluginloader: signature matching', pluginloader_sigmatch, + env: environment({ 'SIGNEDFILE' : 'signedfile.dat' }), + workdir: meson.current_build_dir() +) + diff --git a/lib/pluginloader/pluginloader.cpp b/lib/pluginloader/pluginloader.cpp index c8358bf..e5c4b89 100644 --- a/lib/pluginloader/pluginloader.cpp +++ b/lib/pluginloader/pluginloader.cpp @@ -12,17 +12,21 @@ #include #include "publicKey.h" -PluginLoader::PluginLoader(const QString &fileName, const VerifyState sigLevel, QObject *parent) +PluginLoader::PluginLoader(const QString &fileName, PluginLoader::SignatureState state, QObject *parent) : QPluginLoader(fileName, parent) - , requiredSignatureLevel(sigLevel) + , m_state(state) { } -PluginLoader::VerifyState PluginLoader::verify(const char *hashName) const +bool PluginLoader::verify(const char *hashName) { const QString sigName = this->fileName() + ".sig"; if(!QFile::exists(sigName)) { - return SignatureMissing; + if(m_state.ignored || m_state.checked) + return true; + + m_sigError = tr("A signature is required, but none was found."); + return false; } auto *bio = BIO_new_mem_buf(publicKey_pem, publicKey_pem_len); @@ -39,7 +43,8 @@ PluginLoader::VerifyState PluginLoader::verify(const char *hashName) const int rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, key); if(rc != 1) { - return SignatureMismatched; + m_sigError = tr("Failed to compute signature (stage=init)"); + return false; } // read plugin into DigestVerifyUpdate @@ -53,8 +58,10 @@ PluginLoader::VerifyState PluginLoader::verify(const char *hashName) const len -= read; rc = EVP_DigestVerifyUpdate(ctx, buf, read); - if(rc != 1) - return SignatureComputeFailed; + if(rc != 1) { + m_sigError = tr("Failed to compute signature (staga=update)"); + return false; + } } delete[] buf; plugin.close(); @@ -74,20 +81,13 @@ PluginLoader::VerifyState PluginLoader::verify(const char *hashName) const delete sig; if(rc == 1) - return SignatureMatched; + return true; else { - return SignatureMismatched; - } -} -/* -bool PluginLoader::load() -{ - if(signature == SignatureUnverified) - signature = this->verify(); + if(m_state.ignored) + return true; - if(signature > requiredSignatureLevel) - return QPluginLoader::load(); - else + m_sigError = tr("Signature does not match"); return false; + } } -*/ + diff --git a/lib/pluginloader/pluginloader.h b/lib/pluginloader/pluginloader.h index 48be61d..b602f5b 100644 --- a/lib/pluginloader/pluginloader.h +++ b/lib/pluginloader/pluginloader.h @@ -13,45 +13,35 @@ class PluginLoader : public QPluginLoader Q_OBJECT public: - enum VerifyState { - // uninitialized state - SignatureUnverified = -1, - // signature is optional, match is optional - SignatureCheckIfAvailable = 1, + struct SignatureState { + bool ignored; // always ignore signature + bool checked; // check if available + bool enforced; // always check - 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 + SignatureState(bool ignore, bool check, bool enforce) { + ignored = ignore; + checked = check; + enforced = enforce; + } }; - PluginLoader(const QString &fileName, const VerifyState sigLevel = SignatureMissing, QObject *parent = nullptr); + PluginLoader(const QString &fileName, SignatureState state, QObject *parent = nullptr); ~PluginLoader() = default; QString errorString() const { - if(signature < requiredSignatureLevel) - return QString("Required signature level: %2; Signature level: %3").arg( - QString::number(static_cast(requiredSignatureLevel)), - QString::number(static_cast(signature))); + if(!m_sigError.isEmpty()) + return m_sigError; else return QPluginLoader::errorString(); } - VerifyState verify(const char *hashName = "SHA256") const; - //bool load(); + bool verify(const char *hashName = "SHA256"); private: - const VerifyState requiredSignatureLevel; - VerifyState signature = SignatureUnverified; + const SignatureState m_state; + + QString m_sigError; }; diff --git a/lib/pluginloader/test/pluginloader-sigmatch.cpp b/lib/pluginloader/test/pluginloader-sigmatch.cpp new file mode 100644 index 0000000..2e5a1ff --- /dev/null +++ b/lib/pluginloader/test/pluginloader-sigmatch.cpp @@ -0,0 +1,17 @@ +#include "pluginloader.h" +#include + +PluginLoader *loader = nullptr; + +TEST(PluginLoader, SignatureMatcher) { + EXPECT_TRUE(loader->verify()); +} + +int main(int argc, char **argv) +{ + const PluginLoader::SignatureState state(false, true, false); + loader = new PluginLoader(qgetenv("SIGNEDFILE"), state); + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/lib/pluginloader/write-random.py b/lib/pluginloader/write-random.py new file mode 100755 index 0000000..6834f3b --- /dev/null +++ b/lib/pluginloader/write-random.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Generate file containing random data') + parser.add_argument('--output', type=argparse.FileType('wb'), default=sys.stdout, help='Output file') + parser.add_argument('--length', type=int, default=256, help='Length of file') + + args=parser.parse_args() + + args.output.write(os.urandom(args.length)) + diff --git a/src/browser.cpp b/src/browser.cpp index 61cd2c6..86d8f13 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -130,7 +130,13 @@ QPluginLoader *Browser::addPlugin(const QString &path) if(path.isEmpty()) return nullptr; - auto *loader = new PluginLoader(path, PluginLoader::SignatureMatchIfAvailable, this); + Configuration conf; + const PluginLoader::SignatureState state( + conf.value("plugins.signature.ignored").value(), + conf.value("plugins.signature.checked").value(), + conf.value("plugins.signature.enforced").value()); + + auto *loader = new PluginLoader(path, state, this); const bool loaded = loader->load(); spdlog::info("Loading plugin [{}]: {}", qUtf8Printable(path), loaded ? "passed" : "failed"); diff --git a/src/main.cpp b/src/main.cpp index 98e8ef9..3394a84 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,8 +105,14 @@ int main(int argc, char **argv) [&]() { Configuration conf; spdlog::debug("plugins.path={}", conf.value("plugins.path").value()); + + const PluginLoader::SignatureState state( + conf.value("plugins.signature.ignored").value(), + conf.value("plugins.signature.checked").value(), + conf.value("plugins.signature.enforced").value()); + for(const QString &path : Util::files(conf.value("plugins.path").value(), { "*.so", "*.dll" })) { - auto *loader = new PluginLoader(path); + auto *loader = new PluginLoader(path, state); const bool loaded = loader->load(); spdlog::info("{} plugin {}", loaded ? "Loaded" : "Failed to load", qUtf8Printable(path)); -- cgit v1.2.1