Libkleo

gnupg.cpp
1/* -*- mode: c++; c-basic-offset:4 -*-
2 utils/gnupg.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
6
7 SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
8 SPDX-FileContributor: Intevation GmbH
9
10 SPDX-FileCopyrightText: 2020-2022 g10 Code GmbH
11 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
12
13 SPDX-License-Identifier: GPL-2.0-or-later
14*/
15
16#include <config-libkleo.h>
17
18#include "gnupg.h"
19
20#include "assuan.h"
21#include "compat.h"
22#include "compliance.h"
23#include "cryptoconfig.h"
24#include "hex.h"
25
26#include <libkleo_debug.h>
27
28#include <KAboutComponent>
29#include <KLocalizedString>
30
31#include <QGpgME/CryptoConfig>
32#include <QGpgME/Protocol>
33
34#include <QByteArray>
35#include <QCoreApplication>
36#include <QDateTime>
37#include <QDir>
38#include <QFile>
39#include <QPointer>
40#include <QProcess>
41#include <QRegularExpression>
42#include <QStandardPaths>
43#include <QString>
44#include <QThread>
45
46#include <gpgme++/engineinfo.h>
47#include <gpgme++/error.h>
48#include <gpgme++/key.h>
49
50#include <gpg-error.h>
51
52#ifdef Q_OS_WIN
53#include "gnupg-registry.h"
54#endif // Q_OS_WIN
55
56#include <algorithm>
57#include <array>
58
59using namespace GpgME;
60
61QString Kleo::gnupgHomeDirectory()
62{
63 static const QString homeDir = QString::fromUtf8(GpgME::dirInfo("homedir"));
64 return homeDir;
65}
66
67QString Kleo::gnupgPrivateKeysDirectory()
68{
69 static const QString dir = QDir{gnupgHomeDirectory()}.filePath(QStringLiteral("private-keys-v1.d"));
70 return dir;
71}
72
73int Kleo::makeGnuPGError(int code)
74{
75 return gpg_error(static_cast<gpg_err_code_t>(code));
76}
77
78static QString findGpgExe(GpgME::Engine engine, const QString &exe)
79{
80 const GpgME::EngineInfo info = GpgME::engineInfo(engine);
81 return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(exe);
82}
83
84QString Kleo::gpgConfPath()
85{
86 static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral("gpgconf"));
87 return path;
88}
89
90QString Kleo::gpgSmPath()
91{
92 static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral("gpgsm"));
93 return path;
94}
95
96QString Kleo::gpgPath()
97{
98 static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral("gpg"));
99 return path;
100}
101
102QStringList Kleo::gnupgFileWhitelist()
103{
104 return {
105 // The obvious pubring
106 QStringLiteral("pubring.gpg"),
107 // GnuPG 2.1 pubring
108 QStringLiteral("pubring.kbx"),
109 // Trust in X509 Certificates
110 QStringLiteral("trustlist.txt"),
111 // Trustdb controls ownertrust and thus WOT validity
112 QStringLiteral("trustdb.gpg"),
113 // We want to update when smartcard status changes
114 QStringLiteral("reader*.status"),
115 // No longer used in 2.1 but for 2.0 we want this
116 QStringLiteral("secring.gpg"),
117 // Secret keys (living under private-keys-v1.d/)
118 QStringLiteral("*.key"),
119 // the keyboxd database
120 QStringLiteral("pubring.db"),
121 // Changes to the trustmodel / compliance mode might
122 // affect validity so we check this, too.
123 // Globbing for gpg.conf* here will trigger too often
124 // as gpgconf creates files like gpg.conf.bak or
125 // gpg.conf.tmp12312.gpgconf that should not trigger
126 // a change.
127 QStringLiteral("gpg.conf"),
128 QStringLiteral("gpg.conf-?"),
129 QStringLiteral("gpg.conf-?.?"),
130 };
131}
132
133QStringList Kleo::gnupgFolderWhitelist()
134{
135 static const QDir gnupgHome{gnupgHomeDirectory()};
136 return {
137 gnupgHome.path(),
138 gnupgPrivateKeysDirectory(),
139 // for the keyboxd database
140 gnupgHome.filePath(QStringLiteral("public-keys.d")),
141 };
142}
143
144QString Kleo::gpg4winInstallPath()
145{
146#ifdef Q_OS_WIN
147 // QApplication::applicationDirPath is only used as a fallback
148 // to support the case where Kleopatra is not installed from
149 // Gpg4win but Gpg4win is also installed.
150 char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GPG4Win", "Install Directory");
151 if (!instDir) {
152 // Fallback to HKCU
153 instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GPG4Win", "Install Directory");
154 }
155 if (instDir) {
156 QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
157 free(instDir);
158 return ret;
159 }
160 qCDebug(LIBKLEO_LOG) << "Gpg4win not found. Falling back to Kleopatra instdir.";
161#endif
163}
164
165QString Kleo::gnupgInstallPath()
166{
167#ifdef Q_OS_WIN
168 // QApplication::applicationDirPath is only used as a fallback
169 // to support the case where Kleopatra is not installed from
170 // Gpg4win but Gpg4win is also installed.
171 char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GnuPG", "Install Directory");
172 if (!instDir) {
173 // Fallback to HKCU
174 instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GnuPG", "Install Directory");
175 }
176 if (instDir) {
177 QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
178 free(instDir);
179 return ret;
180 }
181 qCDebug(LIBKLEO_LOG) << "GnuPG not found. Falling back to gpgconf list dir.";
182#endif
183 return gpgConfListDir("bindir");
184}
185
186QString Kleo::gpgConfListDir(const char *which)
187{
188 if (!which || !*which) {
189 return QString();
190 }
191 const QString gpgConfPath = Kleo::gpgConfPath();
192 if (gpgConfPath.isEmpty()) {
193 return QString();
194 }
195 QProcess gpgConf;
196 qCDebug(LIBKLEO_LOG) << "gpgConfListDir: starting " << qPrintable(gpgConfPath) << " --list-dirs";
197 gpgConf.start(gpgConfPath, QStringList() << QStringLiteral("--list-dirs"));
198 if (!gpgConf.waitForFinished()) {
199 qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.errorString());
200 qCDebug(LIBKLEO_LOG) << "output was:\n" << gpgConf.readAllStandardError().constData();
201 return QString();
202 }
203 const QList<QByteArray> lines = gpgConf.readAllStandardOutput().split('\n');
204 for (const QByteArray &line : lines) {
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')) {
209 --end;
210 }
211 const QString result = QDir::fromNativeSeparators(QFile::decodeName(hexdecode(line.mid(begin, end - begin))));
212 qCDebug(LIBKLEO_LOG) << "gpgConfListDir: found " << qPrintable(result) << " for '" << which << "'entry";
213 return result;
214 }
215 }
216 qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): didn't find '" << which << "'"
217 << "entry in output:\n"
218 << gpgConf.readAllStandardError().constData();
219 return QString();
220}
221
222static std::array<int, 3> getVersionFromString(const char *actual, bool &ok)
223{
224 std::array<int, 3> ret{-1, -1, -1};
225 ok = false;
226
227 if (!actual) {
228 return ret;
229 }
230
232
233 // Try to fix it up
234 QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1StringView(R"((\d+)\.(\d+)\.(\d+)(?:-svn\d+)?.*)")));
236 for (int i = 0; i < 3; i++) {
237 match = rx.match(versionString);
238 if (!match.hasMatch()) {
239 versionString += QStringLiteral(".0");
240 } else {
241 ok = true;
242 break;
243 }
244 }
245
246 if (!ok) {
247 qCDebug(LIBKLEO_LOG) << "Can't parse version " << actual;
248 return ret;
249 }
250
251 for (int i = 0; i < 3; ++i) {
252 ret[i] = match.capturedView(i + 1).toUInt(&ok);
253 if (!ok) {
254 return ret;
255 }
256 }
257
258 ok = true;
259 return ret;
260}
261
262bool Kleo::versionIsAtLeast(const char *minimum, const char *actual)
263{
264 if (!minimum || !actual) {
265 return false;
266 }
267 bool ok;
268 const auto minimum_version = getVersionFromString(minimum, ok);
269 if (!ok) {
270 return false;
271 }
272 const auto actual_version = getVersionFromString(actual, ok);
273 if (!ok) {
274 return false;
275 }
276
277 return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(minimum_version), std::end(minimum_version));
278}
279
280bool Kleo::engineIsVersion(int major, int minor, int patch, GpgME::Engine engine)
281{
282 static QMap<Engine, std::array<int, 3>> cachedVersions;
283 const int required_version[] = {major, minor, patch};
284 // Gpgconf means spawning processes which is expensive on windows.
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. '";
290 return false;
291 }
292
293 const char *actual = GpgME::engineInfo(engine).version();
294 bool ok;
295 actual_version = getVersionFromString(actual, ok);
296
297 qCDebug(LIBKLEO_LOG) << "Parsed" << actual << "as: " << actual_version[0] << '.' << actual_version[1] << '.' << actual_version[2] << '.';
298 if (!ok) {
299 return false;
300 }
301 cachedVersions.insert(engine, actual_version);
302 } else {
303 actual_version = cachedVersions.value(engine);
304 }
305
306 // return ! ( actual_version < required_version )
307 return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(required_version), std::end(required_version));
308}
309
310const QString &Kleo::paperKeyInstallPath()
311{
312 static const QString pkPath = (QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()).isEmpty()
313 ? QStandardPaths::findExecutable(QStringLiteral("paperkey"))
314 : QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()));
315 return pkPath;
316}
317
318bool Kleo::haveKeyserverConfigured()
319{
320 if (engineIsVersion(2, 4, 4) //
321 || (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) {
322 return Kleo::keyserver() != QLatin1StringView{"none"};
323 }
324 if (engineIsVersion(2, 1, 19)) {
325 // since 2.1.19 there is a builtin keyserver
326 return true;
327 }
328 return !Kleo::keyserver().isEmpty();
329}
330
331QString Kleo::keyserver()
332{
333 QString result = getCryptoConfigStringValue("gpg", "keyserver");
334 if (result.isEmpty()) {
335 result = getCryptoConfigStringValue("dirmngr", "keyserver");
336 }
337 if (result.endsWith(QLatin1StringView{"://none"})) {
338 // map hkps://none, etc., to "none"; see https://dev.gnupg.org/T6708
339 result = QStringLiteral("none");
340 }
341 return result;
342}
343
344bool Kleo::haveX509DirectoryServerConfigured()
345{
346 return !getCryptoConfigUrlList("dirmngr", "ldapserver").empty() //
347 || !getCryptoConfigUrlList("dirmngr", "LDAP Server").empty() //
348 || !getCryptoConfigUrlList("gpgsm", "keyserver").empty();
349}
350
351bool Kleo::gpgComplianceP(const char *mode)
352{
353 const auto conf = QGpgME::cryptoConfig();
354 const auto entry = getCryptoConfigEntry(conf, "gpg", "compliance");
355 return entry && entry->stringValue() == QString::fromLatin1(mode);
356}
357
358bool Kleo::gnupgUsesDeVsCompliance()
359{
360 return DeVSCompliance::isActive();
361}
362
363bool Kleo::gnupgIsDeVsCompliant()
364{
365 return DeVSCompliance::isCompliant();
366}
367
368#ifdef Q_OS_WIN
369static unsigned int guessConsoleOutputCodePage()
370{
371 /* Qt on Windows uses GetACP while GnuPG prefers
372 * GetConsoleOutputCP.
373 *
374 * As we are not a console application GetConsoleOutputCP
375 * usually returns 0.
376 * From experience the closest thing that let's us guess
377 * what GetConsoleOutputCP returns for a console application
378 * it appears to be the OEMCP.
379 */
380 unsigned int cpno = GetConsoleOutputCP();
381 if (!cpno) {
382 cpno = GetOEMCP();
383 }
384 if (!cpno) {
385 cpno = GetACP();
386 }
387 if (!cpno) {
388 qCDebug(LIBKLEO_LOG) << __func__ << "Failed to find native codepage";
389 }
390
391 qCDebug(LIBKLEO_LOG) << __func__ << "returns" << cpno;
392 return cpno;
393}
394
395static QString fromEncoding(unsigned int src_encoding, const char *data)
396{
397 if (!data || !*data) {
398 return {};
399 }
400
401 // returns necessary buffer size including the terminating null character
402 int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0);
403 if (n <= 0) {
404 qCDebug(LIBKLEO_LOG) << __func__ << "determining necessary buffer size failed with error code" << GetLastError();
405 return QString();
406 }
407
408 wchar_t *result = (wchar_t *)malloc((n + 1) * sizeof *result);
409
410 n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n);
411 if (n <= 0) {
412 free(result);
413 qCDebug(LIBKLEO_LOG) << __func__ << "conversion failed with error code" << GetLastError();
414 return QString();
415 }
416 const auto ret = QString::fromWCharArray(result, n - 1);
417 free(result);
418 return ret;
419}
420
421static QString stringFromGpgOutput_legacy(const QByteArray &ba)
422{
423 static const unsigned int cpno = guessConsoleOutputCodePage();
424
425 if (cpno) {
426 qCDebug(LIBKLEO_LOG) << __func__ << "trying to decode" << ba << "using codepage" << cpno;
427 const auto rawData = QByteArray{ba}.replace("\r\n", "\n");
428 const auto s = fromEncoding(cpno, rawData.constData());
429 if (!s.isEmpty() || ba.isEmpty()) {
430 return s;
431 }
432 qCDebug(LIBKLEO_LOG) << __func__ << "decoding output failed; falling back to QString::fromLocal8Bit()";
433 }
434 qCDebug(LIBKLEO_LOG) << __func__ << "decoding from local encoding:" << ba;
435 return QString::fromLocal8Bit(ba);
436}
437#endif
438
439QString Kleo::stringFromGpgOutput(const QByteArray &ba)
440{
441#ifdef Q_OS_WIN
442 // since 2.2.28, GnuPG always uses UTF-8 for console output (and input)
443 if (Kleo::engineIsVersion(2, 2, 28, GpgME::GpgEngine)) {
444 return QString::fromUtf8(ba);
445 } else {
446 return stringFromGpgOutput_legacy(ba);
447 }
448#else
449 return QString::fromLocal8Bit(ba);
450#endif
451}
452
453QStringList Kleo::backendVersionInfo()
454{
455 const auto components = backendComponents();
456
457 QStringList versions;
458 for (const auto &component : components) {
459 versions.push_back(component.name() + u' ' + component.version());
460 }
461 return versions;
462}
463
464QList<KAboutComponent> Kleo::backendComponents()
465{
466 QList<KAboutComponent> components;
467 if (Kleo::engineIsVersion(2, 2, 24, GpgME::GpgConfEngine)) {
468 QProcess p;
469 qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions ...";
470 p.start(Kleo::gpgConfPath(), {QStringLiteral("--show-versions")});
471 // wait at most 1 second
472 if (!p.waitForFinished(1000)) {
473 qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions timed out after 1 second.";
474 } else if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) {
475 qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions failed:" << p.errorString();
476 qCDebug(LIBKLEO_LOG) << "gpgconf stderr:" << p.readAllStandardError();
477 qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput();
478 } else {
479 const QByteArray output = p.readAllStandardOutput().replace("\r\n", "\n");
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(' ');
485 components.append(KAboutComponent(QStringLiteral("GnuPG"),
486 i18nc("@info", "GnuPG provides support for OpenPGP/LibrePGP and S/MIME."),
487 QString::fromLatin1(componentsLine.value(2)),
488 QStringLiteral("https://gnupg.org"),
490 }
491
492 if (line.startsWith("* Libgcrypt")) {
493 const auto componentsLine = line.split(' ');
494 components.append(KAboutComponent(QStringLiteral("Libgcrypt"),
495 i18nc("@info", "Libgcrypt is a general purpose cryptographic library."),
496 QString::fromLatin1(componentsLine.value(2)),
497 QStringLiteral("https://www.gnupg.org/software/libgcrypt/index.html"),
499 }
500 }
501 }
502 }
503 return components;
504}
505
506namespace
507{
508
509void startGpgConfDetached(const QStringList &arguments)
510{
511 const QString gpgconf = Kleo::gpgConfPath();
512 qCDebug(LIBKLEO_LOG) << "Starting" << gpgconf << arguments.join(QLatin1Char(' ')) << " ...";
513 if (QProcess::startDetached(gpgconf, arguments)) {
514 qCDebug(LIBKLEO_LOG) << "gpgconf was started successfully";
515 } else {
516 qCDebug(LIBKLEO_LOG) << "gpgconf failed to start";
517 }
518}
519
520template<typename Function1, typename Function2>
521auto startGpgConf(const QStringList &arguments, Function1 onSuccess, Function2 onFailure)
522{
523 auto process = new QProcess;
524 process->setProgram(Kleo::gpgConfPath());
525 process->setArguments(arguments);
526
527 QObject::connect(process, &QProcess::started, process, [process]() {
528 qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") was started successfully";
529 });
530 QObject::connect(process, &QProcess::errorOccurred, process, [process, onFailure](auto error) {
531 qCDebug(LIBKLEO_LOG).nospace() << "Error while running gpgconf (" << process << "): " << error;
532 process->deleteLater();
533 onFailure();
534 });
535 QObject::connect(process, &QProcess::readyReadStandardError, process, [process]() {
536 for (const auto &line : process->readAllStandardError().trimmed().split('\n')) {
537 qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") stderr: " << line;
538 }
539 });
540 QObject::connect(process, &QProcess::readyReadStandardOutput, process, [process]() {
541 (void)process->readAllStandardOutput(); /* ignore stdout */
542 });
543 QObject::connect(process, &QProcess::finished, process, [process, onSuccess, onFailure](int exitCode, QProcess::ExitStatus exitStatus) {
544 if (exitStatus == QProcess::NormalExit) {
545 qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") exited (exit code: " << exitCode << ")";
546 if (exitCode == 0) {
547 onSuccess();
548 } else {
549 onFailure();
550 }
551 } else {
552 qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") crashed (exit code: " << exitCode << ")";
553 onFailure();
554 }
555 process->deleteLater();
556 });
557
558 qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgconf (" << process << ") with arguments " << process->arguments().join(QLatin1Char(' ')) << " ...";
559 process->start();
560
561 return process;
562}
563
564static void launchGpgAgentWithEventLoop()
565{
566 static thread_local QProcess *process = nullptr;
567 static thread_local qint64 mSecsSinceEpochOfLastLaunch = 0;
568 static thread_local int numberOfFailedLaunches = 0;
569
570 if (process) {
571 qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already being launched";
572 return;
573 }
574 const auto now = QDateTime::currentMSecsSinceEpoch();
575 if (now - mSecsSinceEpochOfLastLaunch < 1000) {
576 // reduce attempts to launch the agent to 1 attempt per second
577 return;
578 }
579 mSecsSinceEpochOfLastLaunch = now;
580 if (numberOfFailedLaunches > 5) {
581 qCWarning(LIBKLEO_LOG) << __func__ << ": Launching gpg-agent failed" << numberOfFailedLaunches << "times in a row. Giving up.";
582 return;
583 }
584
585 process = startGpgConf(
586 {QStringLiteral("--launch"), QStringLiteral("gpg-agent")},
587 []() {
588 numberOfFailedLaunches = 0;
589 process = nullptr;
590 },
591 []() {
592 numberOfFailedLaunches++;
593 process = nullptr;
594 });
595}
596}
597
598void Kleo::launchGpgAgent(Kleo::LaunchGpgAgentOptions options)
599{
600 if ((options == CheckForRunningAgent) && Kleo::Assuan::agentIsRunning()) {
601 qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already running";
602 return;
603 }
604
605 if (QThread::currentThread()->loopLevel() > 0) {
606 launchGpgAgentWithEventLoop();
607 } else {
608 startGpgConfDetached({QStringLiteral("--launch"), QStringLiteral("gpg-agent")});
609 }
610}
611
612void Kleo::restartGpgAgent()
613{
614 static QPointer<QProcess> process;
615
616 if (process) {
617 qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already being restarted";
618 return;
619 }
620
621 auto startAgent = []() {
622 Kleo::launchGpgAgent(SkipCheckForRunningAgent);
623 };
624 process = startGpgConf({QStringLiteral("--kill"), QStringLiteral("all")}, startAgent, startAgent);
625}
626
627const std::vector<std::string> &Kleo::availableAlgorithms()
628{
629 static std::vector<std::string> algos;
630 if (algos.empty()) {
631 algos.reserve(13);
632 algos = {
633 "brainpoolP256r1",
634 "brainpoolP384r1",
635 "brainpoolP512r1",
636 "curve25519",
637 "curve448",
638 "nistp256",
639 "nistp384",
640 "nistp521",
641 "rsa2048",
642 "rsa3072",
643 "rsa4096",
644 // "secp256k1", // Curve secp256k1 is explicitly ignored
645 };
646#if GPGMEPP_SUPPORTS_KYBER
647 if (engineIsVersion(2, 5, 2)) {
648 algos.insert(algos.end(),
649 {
650 "ky768_bp256",
651 "ky1024_bp384",
652 });
653 }
654#endif
655 };
656 return algos;
657}
658
659const std::vector<std::string> &Kleo::preferredAlgorithms()
660{
661 static const std::vector<std::string> algos = {
662 "curve25519",
663 "brainpoolP256r1",
664 "rsa3072",
665 "rsa2048",
666 };
667 return algos;
668}
669
670const std::vector<std::string> &Kleo::ignoredAlgorithms()
671{
672 static const std::vector<std::string> algos = {
673 "secp256k1", // Curve secp256k1 is not useful
674 };
675 return algos;
676}
677
678bool Kleo::gpgvVerify(const QString &filePath, const QString &sigPath, const QString &keyring, const QStringList &additionalSearchPaths)
679{
680 const QFileInfo verifyFi(filePath);
681 if (!verifyFi.isReadable()) {
682 return false;
683 } else {
684 qCDebug(LIBKLEO_LOG) << "Verifying" << filePath;
685 }
686
687 const auto gpgvPath = QStandardPaths::findExecutable(QStringLiteral("gpgv"), additionalSearchPaths);
688 if (gpgvPath.isEmpty()) {
689 qCDebug(LIBKLEO_LOG) << "Could not find gpgv";
690 return false;
691 }
692
693 QFileInfo sigFi;
694 if (!sigPath.isEmpty()) {
695 sigFi.setFile(sigPath);
696 } else {
697 sigFi.setFile(filePath + QStringLiteral(".sig"));
698 }
699
700 if (!sigFi.isReadable()) {
701 qCDebug(LIBKLEO_LOG) << "No signature found at" << sigFi.absoluteFilePath();
702 return false;
703 }
704
705 auto process = QProcess();
706 process.setProgram(gpgvPath);
707 QStringList args;
708 if (!keyring.isEmpty()) {
709 args << QStringLiteral("--keyring") << keyring;
710 }
711 args << QStringLiteral("--") << sigFi.absoluteFilePath() << verifyFi.absoluteFilePath();
712 process.setArguments(args);
713 qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgv (" << gpgvPath << ") with arguments " << args.join(QLatin1Char(' ')) << " ...";
714 process.start();
715
716 if (!process.waitForFinished(-1)) {
717 qCDebug(LIBKLEO_LOG) << "Failed to execute gpgv" << process.errorString();
718 }
719 bool ret = (process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0);
720
721 if (!ret) {
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());
725 }
726 return ret;
727}
728
729std::vector<QByteArray> Kleo::readSecretKeyFile(const QString &keyGrip)
730{
731 const auto filename = QStringLiteral("%1.key").arg(keyGrip);
732 const auto path = QDir{Kleo::gnupgPrivateKeysDirectory()}.filePath(filename);
733
734 QFile file{path};
735 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
736 qCDebug(LIBKLEO_LOG) << "Cannot open the private key file" << path << "for reading";
737 return {};
738 }
739
740 std::vector<QByteArray> lines;
741 while (!file.atEnd()) {
742 lines.push_back(file.readLine());
743 }
744 if (lines.empty()) {
745 qCDebug(LIBKLEO_LOG) << "The private key file" << path << "is empty";
746 }
747 return lines;
748}
QString i18nc(const char *context, const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT QString versionString()
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.
Definition assuan.cpp:51
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)
void started()
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()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:56:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.