Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F26765962
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
22 KB
Subscribers
None
View Options
diff --git a/src/utils/gnupg.cpp b/src/utils/gnupg.cpp
index b779fd4d..f0efe10f 100644
--- a/src/utils/gnupg.cpp
+++ b/src/utils/gnupg.cpp
@@ -1,549 +1,566 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/gnupg.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "gnupg.h"
#include "utils/compat.h"
+#include "utils/cryptoconfig.h"
#include "utils/hex.h"
#include <gpgme++/engineinfo.h>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <QGpgME/Protocol>
#include <QGpgME/CryptoConfig>
#include "libkleo_debug.h"
#include <QDir>
#include <QFile>
#include <QString>
#include <QProcess>
#include <QByteArray>
#include <QStandardPaths>
#include <QCoreApplication>
#include <QRegularExpression>
#include <gpg-error.h>
#ifdef Q_OS_WIN
#include "gnupg-registry.h"
#endif // Q_OS_WIN
#include <algorithm>
#include <array>
#include <KLocalizedString>
using namespace GpgME;
QString Kleo::gnupgHomeDirectory()
{
static QString homeDir = QString::fromUtf8(GpgME::dirInfo("homedir"));
return homeDir;
}
int Kleo::makeGnuPGError(int code)
{
return gpg_error(static_cast<gpg_err_code_t>(code));
}
static QString findGpgExe(GpgME::Engine engine, const QString &exe)
{
const GpgME::EngineInfo info = GpgME::engineInfo(engine);
return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(exe);
}
QString Kleo::gpgConfPath()
{
static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral("gpgconf"));
return path;
}
QString Kleo::gpgSmPath()
{
static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral("gpgsm"));
return path;
}
QString Kleo::gpgPath()
{
static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral("gpg"));
return path;
}
QStringList Kleo::gnupgFileWhitelist()
{
return QStringList()
// The obvious pubring
<< QStringLiteral("pubring.gpg")
// GnuPG 2.1 pubring
<< QStringLiteral("pubring.kbx")
// Trust in X509 Certificates
<< QStringLiteral("trustlist.txt")
// Trustdb controls ownertrust and thus WOT validity
<< QStringLiteral("trustdb.gpg")
// We want to update when smartcard status changes
<< QStringLiteral("reader*.status")
// No longer used in 2.1 but for 2.0 we want this
<< QStringLiteral("secring.gpg")
// Changes to the trustmodel / compliance mode might
// affect validity so we check this, too.
// Globbing for gpg.conf* here will trigger too often
// as gpgconf creates files like gpg.conf.bak or
// gpg.conf.tmp12312.gpgconf that should not trigger
// a change.
<< QStringLiteral("gpg.conf")
<< QStringLiteral("gpg.conf-?")
<< QStringLiteral("gpg.conf-?.?")
;
}
namespace
{
class Gpg4win
{
public:
static const Gpg4win *instance()
{
// We use singleton to do the signature check only once.
static Gpg4win *inst = nullptr;
if (!inst) {
inst = new Gpg4win();
}
return inst;
}
private:
QString mVersion;
QString mDescription;
QString mDescLong;
bool mSignedVersion;
Gpg4win() :
mVersion(QStringLiteral("Unknown Windows Version")),
mDescription(i18n("Certificate Manager and Unified Crypto GUI")),
mDescLong(QStringLiteral("<a href=https://www.gpg4win.org>Visit the Gpg4win homepage</a>")),
mSignedVersion(false)
{
const QString instPath = Kleo::gpg4winInstallPath();
const QString verPath = instPath + QStringLiteral("/../VERSION");
QFile versionFile(verPath);
QString versVersion;
QString versDescription;
QString versDescLong;
// Open the file first to avoid a verify and then read issue where
// "auditors" might say its an issue,...
if (!versionFile.open(QIODevice::ReadOnly)) {
// No need to translate this should only be the case in development
// builds.
return;
} else {
// Expect a three line format of three HTML strings.
versVersion = QString::fromUtf8(versionFile.readLine()).trimmed();
versDescription = QString::fromUtf8(versionFile.readLine()).trimmed();
versDescLong = QString::fromUtf8(versionFile.readLine()).trimmed();
}
const QString sigPath = verPath + QStringLiteral(".sig");
QFileInfo versionSig(instPath + QStringLiteral("/../VERSION.sig"));
if (versionSig.exists()) {
/* We have a signed version so let us check it against the GnuPG
* release keys. */
QProcess gpgv;
gpgv.setProgram(Kleo::gpgPath().replace(QStringLiteral("gpg.exe"), QStringLiteral("gpgv.exe")));
const QString keyringPath(QStringLiteral("%1/../share/gnupg/distsigkey.gpg").arg(Kleo::gnupgInstallPath()));
gpgv.setArguments(QStringList() << QStringLiteral("--keyring")
<< keyringPath
<< QStringLiteral("--")
<< sigPath
<< verPath);
gpgv.start();
gpgv.waitForFinished();
if (gpgv.exitStatus() == QProcess::NormalExit &&
!gpgv.exitCode()) {
qCDebug(LIBKLEO_LOG) << "Valid Version: " << versVersion;
mVersion = versVersion;
mDescription = versDescription;
mDescLong = versDescLong;
mSignedVersion = true;
} else {
qCDebug(LIBKLEO_LOG) << "gpgv failed with stderr: " << gpgv.readAllStandardError();
qCDebug(LIBKLEO_LOG) << "gpgv stdout" << gpgv.readAllStandardOutput();
}
} else {
qCDebug(LIBKLEO_LOG) << "No signed VERSION file found.";
}
// Also take Version information from unsigned Versions.
mVersion = versVersion;
}
public:
const QString &version() const
{
return mVersion;
}
const QString &description() const
{
return mDescription;
}
const QString &longDescription() const
{
return mDescLong;
}
bool isSignedVersion() const
{
return mSignedVersion;
}
};
} // namespace
bool Kleo::gpg4winSignedversion()
{
return Gpg4win::instance()->isSignedVersion();
}
QString Kleo::gpg4winVersionNumber()
{
// extract the actual version number from the string returned by Gpg4win::version();
// we assume that Gpg4win::version() returns a version number (conforming to the semantic
// versioning spec) optionally prefixed with some text followed by a dash,
// e.g. "Gpg4win-3.1.15-beta15"; see https://dev.gnupg.org/T5663
static const QRegularExpression catchSemVerRegExp{
QLatin1String{R"(-([0-9]+(?:\.[0-9]+)*(?:-[.0-9A-Za-z-]+)?(?:\+[.0-9a-zA-Z-]+)?)$)"}};
QString ret;
const auto match = catchSemVerRegExp.match(gpg4winVersion());
if (match.hasMatch()) {
ret = match.captured(1);
} else {
ret = gpg4winVersion();
}
qCDebug(LIBKLEO_LOG) << __func__ << "returns" << ret;
return ret;
}
QString Kleo::gpg4winVersion()
{
return Gpg4win::instance()->version();
}
QString Kleo::gpg4winDescription()
{
return Gpg4win::instance()->description();
}
QString Kleo::gpg4winLongDescription()
{
return Gpg4win::instance()->longDescription();
}
QString Kleo::gpg4winInstallPath()
{
#ifdef Q_OS_WIN
// QApplication::applicationDirPath is only used as a fallback
// to support the case where Kleopatra is not installed from
// Gpg4win but Gpg4win is also installed.
char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE",
"Software\\GPG4Win",
"Install Directory");
if (!instDir) {
// Fallback to HKCU
instDir = read_w32_registry_string("HKEY_CURRENT_USER",
"Software\\GPG4Win",
"Install Directory");
}
if (instDir) {
QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
free(instDir);
return ret;
}
qCDebug(LIBKLEO_LOG) << "Gpg4win not found. Falling back to Kleopatra instdir.";
#endif
return QCoreApplication::applicationDirPath();
}
QString Kleo::gnupgInstallPath()
{
#ifdef Q_OS_WIN
// QApplication::applicationDirPath is only used as a fallback
// to support the case where Kleopatra is not installed from
// Gpg4win but Gpg4win is also installed.
char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE",
"Software\\GnuPG",
"Install Directory");
if (!instDir) {
// Fallback to HKCU
instDir = read_w32_registry_string("HKEY_CURRENT_USER",
"Software\\GnuPG",
"Install Directory");
}
if (instDir) {
QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
free(instDir);
return ret;
}
qCDebug(LIBKLEO_LOG) << "GnuPG not found. Falling back to gpgconf list dir.";
#endif
return gpgConfListDir("bindir");
}
QString Kleo::gpgConfListDir(const char *which)
{
if (!which || !*which) {
return QString();
}
const QString gpgConfPath = Kleo::gpgConfPath();
if (gpgConfPath.isEmpty()) {
return QString();
}
QProcess gpgConf;
qCDebug(LIBKLEO_LOG) << "gpgConfListDir: starting " << qPrintable(gpgConfPath) << " --list-dirs";
gpgConf.start(gpgConfPath, QStringList() << QStringLiteral("--list-dirs"));
if (!gpgConf.waitForFinished()) {
qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.errorString());
qCDebug(LIBKLEO_LOG) << "output was:\n" << gpgConf.readAllStandardError().constData();
return QString();
}
const QList<QByteArray> lines = gpgConf.readAllStandardOutput().split('\n');
for (const QByteArray &line : lines) {
if (line.startsWith(which) && line[qstrlen(which)] == ':') {
const int begin = qstrlen(which) + 1;
int end = line.size();
while (end && (line[end - 1] == '\n' || line[end - 1] == '\r')) {
--end;
}
const QString result = QDir::fromNativeSeparators(QFile::decodeName(hexdecode(line.mid(begin, end - begin))));
qCDebug(LIBKLEO_LOG) << "gpgConfListDir: found " << qPrintable(result)
<< " for '" << which << "'entry";
return result;
}
}
qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): didn't find '" << which << "'"
<< "entry in output:\n" << gpgConf.readAllStandardError().constData();
return QString();
}
static std::array<int, 3> getVersionFromString(const char *actual, bool &ok)
{
std::array<int, 3> ret;
ok = false;
if (!actual) {
return ret;
}
QString versionString = QString::fromLatin1(actual);
// Try to fix it up
QRegExp rx(QLatin1String(R"((\d+)\.(\d+)\.(\d+)(?:-svn\d+)?.*)"));
for (int i = 0; i < 3; i++) {
if (!rx.exactMatch(versionString)) {
versionString += QStringLiteral(".0");
} else {
ok = true;
break;
}
}
if (!ok) {
qCDebug(LIBKLEO_LOG) << "Can't parse version " << actual;
return ret;
}
for (int i = 0; i < 3; ++i) {
ret[i] = rx.cap(i + 1).toUInt(&ok);
if (!ok) {
return ret;
}
}
ok = true;
return ret;
}
bool Kleo::versionIsAtLeast(const char *minimum, const char *actual)
{
if (!minimum || !actual) {
return false;
}
bool ok;
const auto minimum_version = getVersionFromString(minimum, ok);
if (!ok) {
return false;
}
const auto actual_version = getVersionFromString(actual, ok);
if (!ok) {
return false;
}
return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version),
std::begin(minimum_version), std::end(minimum_version));
}
bool Kleo::engineIsVersion(int major, int minor, int patch, GpgME::Engine engine)
{
static QMap<Engine, std::array<int, 3> > cachedVersions;
const int required_version[] = {major, minor, patch};
// Gpgconf means spawning processes which is expensive on windows.
std::array<int, 3> actual_version;
if (!cachedVersions.contains(engine)) {
const Error err = checkEngine(engine);
if (err.code() == GPG_ERR_INV_ENGINE) {
qCDebug(LIBKLEO_LOG) << "isVersion: invalid engine. '";
return false;
}
const char *actual = GpgME::engineInfo(engine).version();
bool ok;
actual_version = getVersionFromString(actual, ok);
qCDebug(LIBKLEO_LOG) << "Parsed" << actual << "as: "
<< actual_version[0] << '.'
<< actual_version[1] << '.'
<< actual_version[2] << '.';
if (!ok) {
return false;
}
cachedVersions.insert(engine, actual_version);
} else {
actual_version = cachedVersions.value(engine);
}
// return ! ( actual_version < required_version )
return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version),
std::begin(required_version), std::end(required_version));
}
const QString& Kleo::paperKeyInstallPath()
{
static const QString pkPath = QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()).isEmpty() ?
QStandardPaths::findExecutable(QStringLiteral("paperkey")) :
QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath());
return pkPath;
}
bool Kleo::haveKeyserverConfigured()
{
if (engineIsVersion(2, 1, 19)) {
// since 2.1.19 there is a builtin keyserver
return true;
}
const QGpgME::CryptoConfig *const config = QGpgME::cryptoConfig();
if (!config) {
return false;
}
const QGpgME::CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(config, "gpg", "keyserver");
return entry && !entry->stringValue().isEmpty();
}
bool Kleo::gpgComplianceP(const char *mode)
{
const auto conf = QGpgME::cryptoConfig();
const auto entry = getCryptoConfigEntry(conf, "gpg", "compliance");
return entry && entry->stringValue() == QString::fromLatin1(mode);
}
+bool Kleo::gnupgUsesDeVsCompliance()
+{
+ return getCryptoConfigStringValue("gpg", "compliance") == QLatin1String{"de-vs"};
+}
+
+bool Kleo::gnupgIsDeVsCompliant()
+{
+ if (!gnupgUsesDeVsCompliance()) {
+ return false;
+ }
+ // the pseudo option compliance_de_vs was added in 2.2.28; for older versions of GnuPG
+ // we assume non-compliance to be on the safe side; versions of Kleopatra for which
+ // this matters are bundled with new enough versions of GnuPG anyway
+ return getCryptoConfigIntValue("gpg", "compliance_de_vs", 0) != 0;
+}
+
enum GpgME::UserID::Validity Kleo::keyValidity(const GpgME::Key &key)
{
enum UserID::Validity validity = UserID::Validity::Unknown;
for (const auto &uid: key.userIDs()) {
if (validity == UserID::Validity::Unknown
|| validity > uid.validity()) {
validity = uid.validity();
}
}
return validity;
}
#ifdef Q_OS_WIN
static QString fromEncoding (unsigned int src_encoding, const char *data)
{
int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0);
if (n < 0) {
return QString();
}
wchar_t *result = (wchar_t *) malloc ((n+1) * sizeof *result);
n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n);
if (n < 0) {
free(result);
return QString();
}
const auto ret = QString::fromWCharArray(result, n);
free(result);
return ret;
}
#endif
QString Kleo::stringFromGpgOutput(const QByteArray &ba)
{
#ifdef Q_OS_WIN
/* Qt on Windows uses GetACP while GnuPG prefers
* GetConsoleOutputCP.
*
* As we are not a console application GetConsoleOutputCP
* usually returns 0.
* From experience the closest thing that let's us guess
* what GetConsoleOutputCP returns for a console application
* it appears to be the OEMCP.
*/
unsigned int cpno = GetConsoleOutputCP ();
if (!cpno) {
cpno = GetOEMCP();
}
if (!cpno) {
cpno = GetACP();
}
if (!cpno) {
qCDebug(LIBKLEO_LOG) << "Failed to find native codepage";
return QString();
}
return fromEncoding(cpno, ba.constData());
#else
return QString::fromLocal8Bit(ba);
#endif
}
QStringList Kleo::backendVersionInfo()
{
QStringList versions;
if (Kleo::engineIsVersion(2, 2, 24, GpgME::GpgConfEngine)) {
QProcess p;
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions ...";
p.start(Kleo::gpgConfPath(), {QStringLiteral("--show-versions")});
// wait at most 1 second
if (!p.waitForFinished(1000)) {
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions timed out after 1 second.";
} else if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) {
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions failed:" << p.errorString();
qCDebug(LIBKLEO_LOG) << "gpgconf stderr:" << p.readAllStandardError();
qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput();
} else {
const QByteArray output = p.readAllStandardOutput().replace("\r\n", "\n");
qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput();
const auto lines = output.split('\n');
for (const auto &line : lines) {
if (line.startsWith("* GnuPG") || line.startsWith("* Libgcrypt")) {
const auto components = line.split(' ');
versions.push_back(QString::fromLatin1(components.at(1) + ' ' + components.value(2)));
}
}
}
}
return versions;
}
diff --git a/src/utils/gnupg.h b/src/utils/gnupg.h
index 8e3a17c4..c5c2786c 100644
--- a/src/utils/gnupg.h
+++ b/src/utils/gnupg.h
@@ -1,70 +1,84 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/gnupg.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <gpgme++/engineinfo.h>
#include <gpgme++/key.h>
/* Does the given object comply with DE_VS? This macro can be used to
ensure that we can still build against older versions of GPGME
without cluttering the code with preprocessor conditionals. */
#define IS_DE_VS(x) (x).isDeVs()
#include <kleo_export.h>
class QString;
#include <QStringList>
class QByteArray;
namespace Kleo
{
KLEO_EXPORT QString gnupgHomeDirectory();
KLEO_EXPORT QString gpgConfPath();
KLEO_EXPORT QString gpgSmPath();
KLEO_EXPORT QString gpgPath();
KLEO_EXPORT QString gpgConfListDir(const char *which);
KLEO_EXPORT QString gpg4winInstallPath();
// Returns the version number.
KLEO_EXPORT QString gpg4winVersionNumber();
// Returns the version number with an optional product specific prefix.
KLEO_EXPORT QString gpg4winVersion();
KLEO_EXPORT bool gpg4winSignedversion();
KLEO_EXPORT QString gpg4winDescription();
KLEO_EXPORT QString gpg4winLongDescription();
KLEO_EXPORT QString gnupgInstallPath();
KLEO_EXPORT const QString& paperKeyInstallPath();
KLEO_EXPORT QStringList gnupgFileWhitelist();
KLEO_EXPORT int makeGnuPGError(int code);
KLEO_EXPORT bool engineIsVersion(int major, int minor, int patch, GpgME::Engine = GpgME::GpgConfEngine);
KLEO_EXPORT bool haveKeyserverConfigured();
-KLEO_EXPORT bool gpgComplianceP(const char *mode);
+
+/* Use gnupgUsesDeVsCompliance() or gnupgIsDeVsCompliant() instead. */
+KLEO_DEPRECATED_EXPORT bool gpgComplianceP(const char *mode);
+
+/** Returns true, if compliance mode "de-vs" is configured for GnuPG.
+ * Note: It does not check whether the used GnuPG is actually compliant.
+ */
+KLEO_EXPORT bool gnupgUsesDeVsCompliance();
+
+/** Returns true, if compliance mode "de-vs" is configured for GnuPG and if
+ * GnuPG passes a basic compliance check, i.e. at least libgcrypt and the used
+ * RNG are compliant.
+ */
+KLEO_EXPORT bool gnupgIsDeVsCompliant();
+
KLEO_EXPORT enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key);
/* Convert GnuPG output to a QString with proper encoding.
* Takes Gpg Quirks into account and might handle future
* changes in GnuPG Output. */
KLEO_EXPORT QString stringFromGpgOutput(const QByteArray &ba);
/* Check if a minimum version is there. Strings should be in the format:
* 1.2.3 */
KLEO_EXPORT bool versionIsAtLeast(const char *minimum, const char *actual);
/** Returns a list of component names (e.g. GnuPG, libgcrypt) followed by
* version numbers. This is meant for displaying in the About dialog.
*/
KLEO_EXPORT QStringList backendVersionInfo();
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Aug 6, 9:05 PM (23 h, 10 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
fe/c1/e2a5cbcc48d3ea53d833d8e96776
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment