16#include <config-libkleo.h>
22#include "compliance.h"
23#include "cryptoconfig.h"
26#include <libkleo_debug.h>
28#include <KAboutComponent>
29#include <KLocalizedString>
31#include <QGpgME/CryptoConfig>
32#include <QGpgME/Protocol>
35#include <QCoreApplication>
41#include <QRegularExpression>
42#include <QStandardPaths>
46#include <gpgme++/engineinfo.h>
47#include <gpgme++/error.h>
48#include <gpgme++/key.h>
53#include "gnupg-registry.h"
61QString Kleo::gnupgHomeDirectory()
67QString Kleo::gnupgPrivateKeysDirectory()
73int Kleo::makeGnuPGError(
int code)
75 return gpg_error(
static_cast<gpg_err_code_t
>(code));
80 const GpgME::EngineInfo info = GpgME::engineInfo(engine);
86 static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral(
"gpgconf"));
92 static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral(
"gpgsm"));
98 static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral(
"gpg"));
106 QStringLiteral(
"pubring.gpg"),
108 QStringLiteral(
"pubring.kbx"),
110 QStringLiteral(
"trustlist.txt"),
112 QStringLiteral(
"trustdb.gpg"),
114 QStringLiteral(
"reader*.status"),
116 QStringLiteral(
"secring.gpg"),
118 QStringLiteral(
"*.key"),
120 QStringLiteral(
"pubring.db"),
127 QStringLiteral(
"gpg.conf"),
128 QStringLiteral(
"gpg.conf-?"),
129 QStringLiteral(
"gpg.conf-?.?"),
135 static const QDir gnupgHome{gnupgHomeDirectory()};
138 gnupgPrivateKeysDirectory(),
140 gnupgHome.filePath(QStringLiteral(
"public-keys.d")),
144QString Kleo::gpg4winInstallPath()
150 char *instDir = read_w32_registry_string(
"HKEY_LOCAL_MACHINE",
"Software\\GPG4Win",
"Install Directory");
153 instDir = read_w32_registry_string(
"HKEY_CURRENT_USER",
"Software\\GPG4Win",
"Install Directory");
160 qCDebug(LIBKLEO_LOG) <<
"Gpg4win not found. Falling back to Kleopatra instdir.";
165QString Kleo::gnupgInstallPath()
171 char *instDir = read_w32_registry_string(
"HKEY_LOCAL_MACHINE",
"Software\\GnuPG",
"Install Directory");
174 instDir = read_w32_registry_string(
"HKEY_CURRENT_USER",
"Software\\GnuPG",
"Install Directory");
181 qCDebug(LIBKLEO_LOG) <<
"GnuPG not found. Falling back to gpgconf list dir.";
183 return gpgConfListDir(
"bindir");
186QString Kleo::gpgConfListDir(
const char *which)
188 if (!which || !*which) {
191 const QString gpgConfPath = Kleo::gpgConfPath();
196 qCDebug(LIBKLEO_LOG) <<
"gpgConfListDir: starting " << qPrintable(gpgConfPath) <<
" --list-dirs";
199 qCDebug(LIBKLEO_LOG) <<
"gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.
errorString());
205 if (line.startsWith(which) && line[qstrlen(which)] ==
':') {
206 const int begin = qstrlen(which) + 1;
207 int end = line.size();
208 while (end && (line[end - 1] ==
'\n' || line[end - 1] ==
'\r')) {
212 qCDebug(LIBKLEO_LOG) <<
"gpgConfListDir: found " << qPrintable(result) <<
" for '" << which <<
"'entry";
216 qCDebug(LIBKLEO_LOG) <<
"gpgConfListDir(): didn't find '" << which <<
"'"
217 <<
"entry in output:\n"
222static std::array<int, 3> getVersionFromString(
const char *actual,
bool &ok)
224 std::array<int, 3> ret{-1, -1, -1};
236 for (
int i = 0; i < 3; i++) {
237 match = rx.match(versionString);
238 if (!
match.hasMatch()) {
247 qCDebug(LIBKLEO_LOG) <<
"Can't parse version " << actual;
251 for (
int i = 0; i < 3; ++i) {
252 ret[i] =
match.capturedView(i + 1).toUInt(&ok);
262bool Kleo::versionIsAtLeast(
const char *minimum,
const char *actual)
264 if (!minimum || !actual) {
268 const auto minimum_version = getVersionFromString(minimum, ok);
272 const auto actual_version = getVersionFromString(actual, ok);
277 return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(minimum_version), std::end(minimum_version));
280bool Kleo::engineIsVersion(
int major,
int minor,
int patch, GpgME::Engine engine)
283 const int required_version[] = {major, minor, patch};
285 std::array<int, 3> actual_version;
286 if (!cachedVersions.
contains(engine)) {
287 const Error err = checkEngine(engine);
288 if (err.code() == GPG_ERR_INV_ENGINE) {
289 qCDebug(LIBKLEO_LOG) <<
"isVersion: invalid engine. '";
293 const char *actual = GpgME::engineInfo(engine).version();
295 actual_version = getVersionFromString(actual, ok);
297 qCDebug(LIBKLEO_LOG) <<
"Parsed" << actual <<
"as: " << actual_version[0] <<
'.' << actual_version[1] <<
'.' << actual_version[2] <<
'.';
301 cachedVersions.
insert(engine, actual_version);
303 actual_version = cachedVersions.
value(engine);
307 return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(required_version), std::end(required_version));
310const QString &Kleo::paperKeyInstallPath()
318bool Kleo::haveKeyserverConfigured()
320 if (engineIsVersion(2, 4, 4)
321 || (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) {
324 if (engineIsVersion(2, 1, 19)) {
328 return !Kleo::keyserver().
isEmpty();
333 QString result = getCryptoConfigStringValue(
"gpg",
"keyserver");
335 result = getCryptoConfigStringValue(
"dirmngr",
"keyserver");
339 result = QStringLiteral(
"none");
344bool Kleo::haveX509DirectoryServerConfigured()
346 return !getCryptoConfigUrlList(
"dirmngr",
"ldapserver").empty()
347 || !getCryptoConfigUrlList(
"dirmngr",
"LDAP Server").empty()
348 || !getCryptoConfigUrlList(
"gpgsm",
"keyserver").empty();
351bool Kleo::gpgComplianceP(
const char *mode)
353 const auto conf = QGpgME::cryptoConfig();
354 const auto entry = getCryptoConfigEntry(conf,
"gpg",
"compliance");
358bool Kleo::gnupgUsesDeVsCompliance()
360 return DeVSCompliance::isActive();
363bool Kleo::gnupgIsDeVsCompliant()
365 return DeVSCompliance::isCompliant();
369static unsigned int guessConsoleOutputCodePage()
380 unsigned int cpno = GetConsoleOutputCP();
388 qCDebug(LIBKLEO_LOG) << __func__ <<
"Failed to find native codepage";
391 qCDebug(LIBKLEO_LOG) << __func__ <<
"returns" << cpno;
395static QString fromEncoding(
unsigned int src_encoding,
const char *data)
397 if (!data || !*data) {
402 int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0);
404 qCDebug(LIBKLEO_LOG) << __func__ <<
"determining necessary buffer size failed with error code" << GetLastError();
408 wchar_t *result = (
wchar_t *)malloc((n + 1) *
sizeof *result);
410 n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n);
413 qCDebug(LIBKLEO_LOG) << __func__ <<
"conversion failed with error code" << GetLastError();
423 static const unsigned int cpno = guessConsoleOutputCodePage();
426 qCDebug(LIBKLEO_LOG) << __func__ <<
"trying to decode" << ba <<
"using codepage" << cpno;
428 const auto s = fromEncoding(cpno, rawData.constData());
429 if (!s.isEmpty() || ba.
isEmpty()) {
432 qCDebug(LIBKLEO_LOG) << __func__ <<
"decoding output failed; falling back to QString::fromLocal8Bit()";
434 qCDebug(LIBKLEO_LOG) << __func__ <<
"decoding from local encoding:" << ba;
443 if (Kleo::engineIsVersion(2, 2, 28, GpgME::GpgEngine)) {
446 return stringFromGpgOutput_legacy(ba);
455 const auto components = backendComponents();
458 for (
const auto &component : components) {
459 versions.
push_back(component.name() + u
' ' + component.version());
467 if (Kleo::engineIsVersion(2, 2, 24, GpgME::GpgConfEngine)) {
469 qCDebug(LIBKLEO_LOG) <<
"Running gpgconf --show-versions ...";
470 p.
start(Kleo::gpgConfPath(), {QStringLiteral(
"--show-versions")});
473 qCDebug(LIBKLEO_LOG) <<
"Running gpgconf --show-versions timed out after 1 second.";
475 qCDebug(LIBKLEO_LOG) <<
"Running gpgconf --show-versions failed:" << p.
errorString();
480 qCDebug(LIBKLEO_LOG) <<
"gpgconf stdout:" << output;
481 const auto lines = output.
split(
'\n');
482 for (
const auto &line : lines) {
483 if (line.startsWith(
"* GnuPG")) {
484 const auto componentsLine = line.split(
' ');
486 i18nc(
"@info",
"GnuPG provides support for OpenPGP/LibrePGP and S/MIME."),
488 QStringLiteral(
"https://gnupg.org"),
492 if (line.startsWith(
"* Libgcrypt")) {
493 const auto componentsLine = line.split(
' ');
495 i18nc(
"@info",
"Libgcrypt is a general purpose cryptographic library."),
497 QStringLiteral(
"https://www.gnupg.org/software/libgcrypt/index.html"),
509void startGpgConfDetached(
const QStringList &arguments)
511 const QString gpgconf = Kleo::gpgConfPath();
512 qCDebug(LIBKLEO_LOG) <<
"Starting" << gpgconf << arguments.
join(
QLatin1Char(
' ')) <<
" ...";
514 qCDebug(LIBKLEO_LOG) <<
"gpgconf was started successfully";
516 qCDebug(LIBKLEO_LOG) <<
"gpgconf failed to start";
520template<
typename Function1,
typename Function2>
521auto startGpgConf(
const QStringList &arguments, Function1 onSuccess, Function2 onFailure)
525 process->setArguments(arguments);
528 qCDebug(LIBKLEO_LOG).nospace() <<
"gpgconf (" << process <<
") was started successfully";
531 qCDebug(LIBKLEO_LOG).nospace() <<
"Error while running gpgconf (" << process <<
"): " <<
error;
532 process->deleteLater();
536 for (
const auto &line : process->readAllStandardError().trimmed().split(
'\n')) {
537 qCDebug(LIBKLEO_LOG).nospace() <<
"gpgconf (" << process <<
") stderr: " << line;
541 (void)process->readAllStandardOutput();
545 qCDebug(LIBKLEO_LOG).nospace() <<
"gpgconf (" << process <<
") exited (exit code: " << exitCode <<
")";
552 qCDebug(LIBKLEO_LOG).nospace() <<
"gpgconf (" << process <<
") crashed (exit code: " << exitCode <<
")";
555 process->deleteLater();
558 qCDebug(LIBKLEO_LOG).nospace() <<
"Starting gpgconf (" << process <<
") with arguments " << process->arguments().join(
QLatin1Char(
' ')) <<
" ...";
564static void launchGpgAgentWithEventLoop()
566 static thread_local QProcess *process =
nullptr;
567 static thread_local qint64 mSecsSinceEpochOfLastLaunch = 0;
568 static thread_local int numberOfFailedLaunches = 0;
571 qCDebug(LIBKLEO_LOG) << __func__ <<
": gpg-agent is already being launched";
575 if (now - mSecsSinceEpochOfLastLaunch < 1000) {
579 mSecsSinceEpochOfLastLaunch = now;
580 if (numberOfFailedLaunches > 5) {
581 qCWarning(LIBKLEO_LOG) << __func__ <<
": Launching gpg-agent failed" << numberOfFailedLaunches <<
"times in a row. Giving up.";
585 process = startGpgConf(
586 {QStringLiteral(
"--launch"), QStringLiteral(
"gpg-agent")},
588 numberOfFailedLaunches = 0;
592 numberOfFailedLaunches++;
598void Kleo::launchGpgAgent(Kleo::LaunchGpgAgentOptions options)
601 qCDebug(LIBKLEO_LOG) << __func__ <<
": gpg-agent is already running";
606 launchGpgAgentWithEventLoop();
608 startGpgConfDetached({QStringLiteral(
"--launch"), QStringLiteral(
"gpg-agent")});
612void Kleo::restartGpgAgent()
617 qCDebug(LIBKLEO_LOG) << __func__ <<
": gpg-agent is already being restarted";
621 auto startAgent = []() {
622 Kleo::launchGpgAgent(SkipCheckForRunningAgent);
624 process = startGpgConf({QStringLiteral(
"--kill"), QStringLiteral(
"all")}, startAgent, startAgent);
627const std::vector<std::string> &Kleo::availableAlgorithms()
629 static std::vector<std::string> algos;
646#if GPGMEPP_SUPPORTS_KYBER
647 if (engineIsVersion(2, 5, 2)) {
648 algos.insert(algos.end(),
659const std::vector<std::string> &Kleo::preferredAlgorithms()
661 static const std::vector<std::string> algos = {
670const std::vector<std::string> &Kleo::ignoredAlgorithms()
672 static const std::vector<std::string> algos = {
681 if (!verifyFi.isReadable()) {
684 qCDebug(LIBKLEO_LOG) <<
"Verifying" << filePath;
688 if (gpgvPath.isEmpty()) {
689 qCDebug(LIBKLEO_LOG) <<
"Could not find gpgv";
697 sigFi.
setFile(filePath + QStringLiteral(
".sig"));
701 qCDebug(LIBKLEO_LOG) <<
"No signature found at" << sigFi.
absoluteFilePath();
706 process.setProgram(gpgvPath);
709 args << QStringLiteral(
"--keyring") << keyring;
711 args << QStringLiteral(
"--") << sigFi.
absoluteFilePath() << verifyFi.absoluteFilePath();
712 process.setArguments(args);
713 qCDebug(LIBKLEO_LOG).nospace() <<
"Starting gpgv (" << gpgvPath <<
") with arguments " << args.
join(
QLatin1Char(
' ')) <<
" ...";
716 if (!process.waitForFinished(-1)) {
717 qCDebug(LIBKLEO_LOG) <<
"Failed to execute gpgv" << process.errorString();
722 qCDebug(LIBKLEO_LOG) <<
"Failed to verify file";
723 qCDebug(LIBKLEO_LOG) <<
"gpgv stdout:" <<
QString::fromUtf8(process.readAllStandardOutput());
724 qCDebug(LIBKLEO_LOG) <<
"gpgv stderr:" <<
QString::fromUtf8(process.readAllStandardError());
729std::vector<QByteArray> Kleo::readSecretKeyFile(
const QString &keyGrip)
731 const auto filename = QStringLiteral(
"%1.key").arg(keyGrip);
732 const auto path =
QDir{Kleo::gnupgPrivateKeysDirectory()}.
filePath(filename);
736 qCDebug(LIBKLEO_LOG) <<
"Cannot open the private key file" <<
path <<
"for reading";
740 std::vector<QByteArray> lines;
741 while (!file.atEnd()) {
742 lines.push_back(file.readLine());
745 qCDebug(LIBKLEO_LOG) <<
"The private key file" <<
path <<
"is empty";
QString i18nc(const char *context, const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QString dir(const QString &fileClass)
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
KLEO_EXPORT bool agentIsRunning()
Checks if the GnuPG agent is running and accepts connections.
const char * versionString()
const char * constData() const const
bool isEmpty() const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QList< QByteArray > split(char sep) const const
QString applicationDirPath()
qint64 currentMSecsSinceEpoch()
QString filePath(const QString &fileName) const const
QString fromNativeSeparators(const QString &pathName)
QString decodeName(const QByteArray &localFileName)
QString absoluteFilePath() const const
bool isReadable() const const
void setFile(const QDir &dir, const QString &path)
QString errorString() const const
void append(QList< T > &&value)
void push_back(parameter_type value)
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
T value(const Key &key, const T &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void errorOccurred(QProcess::ProcessError error)
int exitCode() const const
QProcess::ExitStatus exitStatus() const const
void finished(int exitCode, QProcess::ExitStatus exitStatus)
QByteArray readAllStandardError()
QByteArray readAllStandardOutput()
void readyReadStandardError()
void readyReadStandardOutput()
void setProgram(const QString &program)
void start(OpenMode mode)
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
bool waitForFinished(int msecs)
QString anchoredPattern(QStringView expression)
QString findExecutable(const QString &executableName, const QStringList &paths)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString fromWCharArray(const wchar_t *string, qsizetype size)
bool isEmpty() const const
QString join(QChar separator) const const
QThread * currentThread()