diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ set(KDEPIMTEXTEDIT_VERSION "5.3.40") set(PIMCOMMON_LIB_VERSION "5.3.40") -find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets) +find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test) find_package(KF5I18n ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5WidgetsAddons ${KF5_VERSION} CONFIG REQUIRED) @@ -84,8 +84,10 @@ DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) - - add_subdirectory(src) +if(BUILD_TESTING) + add_subdirectory(autotests) +endif() + install( FILES libkleo.renamecategories libkleo.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1 @@ +ecm_add_test(tofutest.cpp LINK_LIBRARIES KF5Libkleo Qt5::Test NAME_PREFIX kleo) diff --git a/autotests/fakehomes/disabled/gpg.conf b/autotests/fakehomes/disabled/gpg.conf new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/disabled/gpg.conf @@ -0,0 +1,11 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +use-agent diff --git a/autotests/fakehomes/disabled/gpg.conf.disabled b/autotests/fakehomes/disabled/gpg.conf.disabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/disabled/gpg.conf.disabled @@ -0,0 +1,11 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +use-agent diff --git a/autotests/fakehomes/disabled/gpg.conf.enabled b/autotests/fakehomes/disabled/gpg.conf.enabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/disabled/gpg.conf.enabled @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +use-agent +trust-model tofu diff --git a/autotests/fakehomes/new/gpg.conf b/autotests/fakehomes/new/gpg.conf new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/new/gpg.conf @@ -0,0 +1 @@ + diff --git a/autotests/fakehomes/new/gpg.conf.disabled b/autotests/fakehomes/new/gpg.conf.disabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/new/gpg.conf.disabled @@ -0,0 +1 @@ + diff --git a/autotests/fakehomes/new/gpg.conf.enabled b/autotests/fakehomes/new/gpg.conf.enabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/new/gpg.conf.enabled @@ -0,0 +1 @@ +trust-model tofu diff --git a/autotests/fakehomes/trust-model-auto/gpg.conf b/autotests/fakehomes/trust-model-auto/gpg.conf new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-auto/gpg.conf @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +trust-model auto +use-agent diff --git a/autotests/fakehomes/trust-model-auto/gpg.conf.disabled b/autotests/fakehomes/trust-model-auto/gpg.conf.disabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-auto/gpg.conf.disabled @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +trust-model auto +use-agent diff --git a/autotests/fakehomes/trust-model-auto/gpg.conf.enabled b/autotests/fakehomes/trust-model-auto/gpg.conf.enabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-auto/gpg.conf.enabled @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +trust-model tofu +use-agent diff --git a/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf b/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +trust-model tofu+pgp +use-agent diff --git a/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf.disabled b/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf.disabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf.disabled @@ -0,0 +1,11 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +use-agent diff --git a/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf.enabled b/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf.enabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-tofu-pgp/gpg.conf.enabled @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +trust-model tofu+pgp +use-agent diff --git a/autotests/fakehomes/trust-model-tofu/gpg.conf b/autotests/fakehomes/trust-model-tofu/gpg.conf new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-tofu/gpg.conf @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +trust-model tofu +use-agent diff --git a/autotests/fakehomes/trust-model-tofu/gpg.conf.disabled b/autotests/fakehomes/trust-model-tofu/gpg.conf.disabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-tofu/gpg.conf.disabled @@ -0,0 +1,11 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +use-agent diff --git a/autotests/fakehomes/trust-model-tofu/gpg.conf.enabled b/autotests/fakehomes/trust-model-tofu/gpg.conf.enabled new file mode 100644 --- /dev/null +++ b/autotests/fakehomes/trust-model-tofu/gpg.conf.enabled @@ -0,0 +1,12 @@ +###+++--- GPGConf ---+++### +utf8-strings +keyserver hkp://keys.gnupg.net +debug-level basic +log-file socket:///home/dvratil/.gnupg/log-socket +###+++--- GPGConf ---+++### Tue 22 Mar 2016 10:18:53 PM CET +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. + +trust-model tofu +use-agent diff --git a/autotests/gpgconf.py b/autotests/gpgconf.py new file mode 100755 --- /dev/null +++ b/autotests/gpgconf.py @@ -0,0 +1,59 @@ +#!/usr/bin/python + +import argparse +import os +import sys + +# Fake the gpgconf --version output with the actual version number customizable +# via FAKEGPG_VERSION env variable +def version(): + print("gpgconf (GnuPG) {0}".format(os.environ['FAKEGPG_VERSION'])) + print("Copyright (C) 2016 Free Software Foundation, Inc.") + print("License GPLv3+: GNU GPL version 3 or later ") + print("This is free software: you are free to change and redistribute it.") + print("There is NO WARRANTY, to the extent permitted by law.") + return 0 + +# Fake the gpgconf --list-dirs output with the actual paths being based off +# FAKEGPG_HOME env variable +def listDirs(): + print("sysconfdir:/etc/gnupg") + print("bindir:/usr/bin") + print("libexecdir:/usr/lib64/libexec") + print("libdir:/usr/lib64/gnupg") + print("datadir:/usr/share/gnupg") + print("localedir:/usr/share/locale") + print("socketdir:/run/user/1000/gnupg") + print("dirmngr-socket:/run/user/1000/gnupg/S.dirmngr") + print("agent-ssh-socket:/run/user/1000/gnupg/S.gpg-agent.ssh") + print("agent-extra-socket:/run/user/1000/gnupg/S.gpg-agent.extra") + print("agent-browser-socket:/run/user/1000/gnupg/S.gpg-agent.browser") + print("agent-socket:/run/user/1000/gnupg/S.gpg-agent") + print("homedir:{0}".format(os.environ['FAKEGPG_HOME'])) + return 0 + +# Simluate call to gpgconf --reload. Always succeeds. +def reloadComponents(): + return 0 + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--version', action='store_true', dest='version') + parser.add_argument('--list-dirs', action='store_true', dest='listdirs') + parser.add_argument('--reload', action='store_true', dest='reload') + + args = parser.parse_args() + if args.version: + ret = version() + elif args.listdirs: + ret = listDirs() + elif args.reload: + ret = reloadComponents() + else: + ret = 1 + + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/autotests/tofutest.cpp b/autotests/tofutest.cpp new file mode 100644 --- /dev/null +++ b/autotests/tofutest.cpp @@ -0,0 +1,177 @@ +/* This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2016 Klarälvdalens Datakonsult AB + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include +#include + +#define GPGCONF_EXECUTABLE QFINDTESTDATA("gpgconf.py") +#include "../src/kleo/tofu.cpp" + +class TOFUTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testIsSupported_data() + { + QTest::addColumn("version"); + QTest::addColumn("supported"); + + QTest::newRow("old version") << QStringLiteral("1.1.1") << false;; + QTest::newRow("old version beta") << QStringLiteral("1.2.3-beta296") << false; + QTest::newRow("new version") << QStringLiteral("2.1.16") << true; + QTest::newRow("new version beta") << QStringLiteral("2.1.16-beta296") << true; + + const QString minVersion = QString::fromLatin1("%1.%2.%3").arg(MinVersion >> 16) + .arg((MinVersion >> 8) & 0xff) + .arg(MinVersion & 0xff); + QTest::newRow("min version") << minVersion << true; + QTest::newRow("min version beta") << minVersion + QStringLiteral("-beta1235") << true; + } + + void testIsSupported() + { + QFETCH(QString, version); + QFETCH(bool, supported); + + qputenv("FAKEGPG_VERSION", version.toLatin1()); + + QCOMPARE(TOFU::isSupported(), supported); + } + + + void testIsEnabled_data() + { + QTest::addColumn("dirExists"); + QTest::addColumn("fileExists"); + QTest::addColumn("supported"); + QTest::addColumn("sample"); + QTest::addColumn("enabled"); + + QTest::newRow("unsupported") << false << false << false << QString() << false; + QTest::newRow("no dir") << false << false << true << QString() << false; + QTest::newRow("no file") << true << false << true << QString() << false; + QTest::newRow("disabled") << true << true << true << QStringLiteral("disabled") << false; + QTest::newRow("trust-model: auto") << true << true << true << QStringLiteral("trust-model-auto") << false; + QTest::newRow("trust-model: tofu") << true << true << true << QStringLiteral("trust-model-tofu") << true; + QTest::newRow("trust-model: tofu+pgp") << true << true << true << QStringLiteral("trust-model-tofu-pgp") << true; + } + + void testIsEnabled() + { + QFETCH(bool, dirExists); + QFETCH(bool, fileExists); + QFETCH(bool, supported); + QFETCH(QString, sample); + QFETCH(bool, enabled); + + QScopedPointer tmpDir; + QString gnupgHome; + if (dirExists) { + tmpDir.reset(new QTemporaryDir()); + gnupgHome = tmpDir->path() + QStringLiteral("/.gnupg"); + QVERIFY(QDir().mkpath(gnupgHome)); + } + + if (dirExists && fileExists) { + QVERIFY(QFile::copy(QFINDTESTDATA(QStringLiteral("fakehomes/%1/gpg.conf").arg(sample)), + gnupgHome + QStringLiteral("/gpg.conf"))); + } + + qputenv("FAKEGPG_VERSION", supported ? "2.1.16" : "1.4.21"); + qputenv("FAKEGPG_HOME", gnupgHome.toLatin1()); + + QCOMPARE(TOFU::isEnabled(), enabled); + } + + void testSetEnabled_data() + { + QTest::addColumn("dirExists"); + QTest::addColumn("fileExists"); + QTest::addColumn("sample"); + QTest::addColumn("enable"); + + QTest::newRow("no dir, disable") << false << false << QString() << false; + QTest::newRow("no dir, enable") << false << false << QStringLiteral("new") << true; + QTest::newRow("no file, disable") << true << false << QString() << false; + QTest::newRow("no file, enable") << true << false << QStringLiteral("new") << true; + QTest::newRow("disabled, enable") << true << true << QStringLiteral("disabled") << true; + QTest::newRow("disabled, disable") << true << true << QStringLiteral("disabled") << false; + QTest::newRow("trust-model-auto, enable") << true << true << QStringLiteral("trust-model-auto") << true; + QTest::newRow("trust-model-auto, disable") << true << true << QStringLiteral("trust-model-auto") << false; + QTest::newRow("trust-model-tofu, enable") << true << true << QStringLiteral("trust-model-tofu") << true; + QTest::newRow("trust-model-tofu, disable") << true << true << QStringLiteral("trust-model-tofu") << false; + QTest::newRow("trust-model-tofu-pgp, enable") << true << true << QStringLiteral("trust-model-tofu-pgp") << true; + QTest::newRow("trust-model-tofu-pgp, disable") << true << true << QStringLiteral("trust-model-tofu-pgp") << false; + } + + void testSetEnabled() + { + QFETCH(bool, dirExists); + QFETCH(bool, fileExists); + QFETCH(QString, sample); + QFETCH(bool, enable); + + QTemporaryDir tmpDir; + const QString gnupgHome = tmpDir.path() + QStringLiteral("/.gnupg"); + if (dirExists) { + QVERIFY(QDir().mkpath(gnupgHome)); + } else { + tmpDir.remove(); + } + + if (dirExists && fileExists) { + QVERIFY(QFile::copy(QFINDTESTDATA(QStringLiteral("fakehomes/%1/gpg.conf").arg(sample)), + gnupgHome + QStringLiteral("/gpg.conf"))); + } + + qputenv("FAKEGPG_HOME", gnupgHome.toLatin1()); + + QCOMPARE(TOFU::setEnabled(enable), true); + QCOMPARE(TOFU::isEnabled(), enable); + + QFile sourceFile(gnupgHome + QStringLiteral("/gpg.conf")); + QCOMPARE(sourceFile.exists(), (dirExists && fileExists) || enable); + if (!sourceFile.exists()) { + return; + } + + QVERIFY(sourceFile.open(QIODevice::ReadOnly)); + const QByteArray sourceData = sourceFile.readAll(); + + QFile referenceFile(QFINDTESTDATA(QStringLiteral("fakehomes/%1/gpg.conf.%2") + .arg(sample, + enable ? QStringLiteral("enabled") : QStringLiteral("disabled")))); + QVERIFY(referenceFile.exists()); + QVERIFY(referenceFile.open(QIODevice::ReadOnly)); + const QByteArray referenceData = referenceFile.readAll(); + + if (sourceData != referenceData) { + qDebug() << sourceData; + qDebug() << referenceData; + } + QCOMPARE(sourceData, referenceData); + } +}; + +QTEST_MAIN(TOFUTest) + +#include "tofutest.moc" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,7 @@ kleo/exception.cpp kleo/kconfigbasedkeyfilter.cpp kleo/keyfiltermanager.cpp + kleo/tofu.cpp models/keycache.cpp models/keylistmodel.cpp models/keylistsortfilterproxymodel.cpp diff --git a/src/kleo/tofu.h b/src/kleo/tofu.h new file mode 100644 --- /dev/null +++ b/src/kleo/tofu.h @@ -0,0 +1,64 @@ +/* This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2016 Klarälvdalens Datakonsult AB + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef KLEO_TOFU_H_ +#define KLEO_TOFU_H_ + +#include "kleo_export.h" + +namespace Kleo { + +namespace TOFU { + +/** + * Returns whether currently installed version of GnuPG supports the + * TOFU trust model + * + * @since 5.4 + */ +KLEO_EXPORT bool isSupported(); + +/** + * Returns whether TOFU trust model is enabled. + * + * Returns false when TOFU is not supported by the currently installed + * version of GnuPG. + * + * @since 5.4 + */ +KLEO_EXPORT bool isEnabled(); + +/** + * Toggles whether TOFU trust mode. + * + * This function modifies $GNUPG_HOME/gpg.conf configuration file and + * sets trust-model to tofu when @p enabled is true or removes the line + * when @p enabled is false. + * + * When @p enabled is true and the configuration file already has trust-model + * set to tofu or tofu+pgp, this method does nothing. + * + * @since 5.4 + */ +KLEO_EXPORT bool setEnabled(bool enabled); + +} + +} + +#endif diff --git a/src/kleo/tofu.cpp b/src/kleo/tofu.cpp new file mode 100644 --- /dev/null +++ b/src/kleo/tofu.cpp @@ -0,0 +1,208 @@ +/* This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2016 Klarälvdalens Datakonsult AB + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "tofu.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace Kleo; + +#define GPG_VERSION(maj, min, patch) ((maj << 16) + (min << 8) + (patch)) + +// Re-defined in unit-tests +#ifndef GPGCONF_EXECUTABLE +#define GPGCONF_EXECUTABLE QStandardPaths::findExecutable(QStringLiteral("gpgconf")) +#endif + +namespace { + +static const int MinVersion = GPG_VERSION(2, 1, 14); + +typedef std::unique_ptr GpgConfProcess; + +GpgConfProcess runGpgConf(const QStringList &options) +{ + const QString gpgConfExec = GPGCONF_EXECUTABLE; + if (gpgConfExec.isEmpty()) { + return GpgConfProcess(); + } + + GpgConfProcess gpgConf(new QProcess); + auto env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("LANG"), QStringLiteral("C")); + gpgConf->setProcessEnvironment(env); + gpgConf->start(gpgConfExec, options, QIODevice::ReadOnly); + gpgConf->waitForFinished(); + gpgConf->waitForReadyRead(); + + return gpgConf; +} + +QString configFile() +{ + auto gpgConf = runGpgConf({ QStringLiteral("--list-dirs") }); + if (!gpgConf) { + return QString(); + } + + QString homeDir; + while (!gpgConf->atEnd()) { + const QByteArray line = gpgConf->readLine().trimmed(); + if (line.startsWith("homedir:")) { + homeDir = QString::fromUtf8(line.mid(sizeof("homedir:") - 1)); + break; + } + } + + if (homeDir.isEmpty()) { + return QString(); + } + + return homeDir + QStringLiteral("/gpg.conf"); +} + +} + +bool TOFU::isSupported() +{ + auto gpgConf = runGpgConf({ QStringLiteral("--version") }); + if (!gpgConf) { + return false; + } + + int version = 0; + const QRegularExpression rx(QStringLiteral("^gpgconf \\(GnuPG\\) ([0-9]+).([0-9]+).([0-9]+)")); + while (gpgConf->canReadLine()) { + const QByteArray line = gpgConf->readLine(); + auto match = rx.match(QString::fromUtf8(line)); + if (match.hasMatch()) { + version = GPG_VERSION(match.capturedRef(1).toInt(), + match.capturedRef(2).toInt(), + match.capturedRef(3).toInt()); + break; + } + } + + return (version >= MinVersion); +} + +bool TOFU::isEnabled() +{ + if (!isSupported()) { + return false; + } + + const QString confFile = configFile(); + if (confFile.isEmpty()) { + return false; + } + + QFile gpgConf(confFile); + if (!gpgConf.exists()) { + return false; + } + + if (!gpgConf.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + + QByteArray trustModel; + while (!gpgConf.atEnd()) { + const QByteArray line = gpgConf.readLine().trimmed(); + if (line.startsWith("trust-model ")) { + trustModel = line.mid(sizeof("trust-model ") - 1).trimmed(); + break; + } + } + + if (trustModel.isEmpty()) { + return false; + } + + return (trustModel == "tofu" || trustModel == "tofu+pgp"); +} + +bool TOFU::setEnabled(bool enable) +{ + const QString confFile = configFile(); + if (confFile.isEmpty()) { + return false; + } + + QFileInfo fi(confFile); + auto dir = fi.dir(); + if (!dir.exists()) { + dir.mkpath(dir.absolutePath()); + } + + QByteArray outBuffer; + bool isEnabled = false; + bool appendTrustModel = enable; + QFile gpgFile(confFile); + if (gpgFile.exists()) { + if (gpgFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + while (!gpgFile.atEnd()) { + const QByteArray line = gpgFile.readLine(); + if (line.startsWith("trust-model ")) { + const QByteArray val = line.mid(sizeof("trust-model ") -1).trimmed(); + isEnabled = (val == "tofu" || val == "tofu+pgp"); + if (isEnabled && enable) { // preserve current state, copy line + outBuffer += line; + } else if (!isEnabled && enable) { // not enabled, overwrite the old value + isEnabled = true; + outBuffer += "trust-model tofu\n"; + } else { // !isEnabled || !enable + // do nothing, skip the line + } + appendTrustModel = false; + } else { + outBuffer += line; + } + } + gpgFile.close(); + } else { + return false; + } + } + + if (!isEnabled && !enable) { + return true; + } + + if (appendTrustModel) { + outBuffer += "trust-model tofu\n"; + } + + if (!gpgFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + gpgFile.write(outBuffer); + gpgFile.close(); + + // Send SIGHUP to all components + runGpgConf({ QStringLiteral("--reload") }); + return true; +}