Page MenuHome GnuPG

No OneTemporary

diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index e93df8ef..781bac59 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -1,96 +1,101 @@
# SPDX-License-Identifier: CC0-1.0
# SPDX-FileCopyrightText: none
include(ECMAddTests)
find_package(Qt6Test ${QT_REQUIRED_VERSION} CONFIG QUIET)
if(NOT TARGET Qt::Test)
message(STATUS "Qt6Test not found, autotests will not be built.")
return()
endif()
ecm_add_test(
flatkeylistmodeltest.cpp
abstractkeylistmodeltest.cpp
TEST_NAME flatkeylistmodeltest
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
hierarchicalkeylistmodeltest.cpp
abstractkeylistmodeltest.cpp
abstractkeylistmodeltest.h
TEST_NAME hierarchicalkeylistmodeltest
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
keyresolvercoretest.cpp
keyresolvercoretest.qrc
TEST_NAME keyresolvercoretest
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
set_property(TEST keyresolvercoretest PROPERTY
SKIP_REGULAR_EXPRESSION "SKIP.*::initTestCase()"
)
ecm_add_tests(
editdirectoryservicedialogtest.cpp
LINK_LIBRARIES KPim6::Libkleo KF6::WidgetsAddons Qt::Widgets Qt::Test
)
ecm_add_tests(
keyselectioncombotest.cpp
keyserverconfigtest.cpp
newkeyapprovaldialogtest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Widgets Qt::Test
)
ecm_add_test(
expirycheckertest.cpp
expirycheckertest.qrc
testhelpers.h
TEST_NAME expirycheckertest
LINK_LIBRARIES
KPim6::Libkleo
Qt::Test
)
ecm_add_tests(
hextest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
classifytest.cpp
LINK_LIBRARIES
KPim6::Libkleo
Qt::Test
)
ecm_add_test(
compliancetest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_tests(
formattingtest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_tests(
keycachetest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
keyparameterstest.cpp
TEST_NAME keyparameterstest
LINK_LIBRARIES KPim6::Libkleo Gpgmepp Qt::Test
)
ecm_add_tests(
stringutilstest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
+
+ecm_add_tests(
+ assuantest.cpp
+ LINK_LIBRARIES KPim6::Libkleo Qt::Test
+)
diff --git a/autotests/assuantest.cpp b/autotests/assuantest.cpp
new file mode 100644
index 00000000..3e746ba9
--- /dev/null
+++ b/autotests/assuantest.cpp
@@ -0,0 +1,50 @@
+/*
+ This file is part of libkleopatra's test suite.
+ SPDX-FileCopyrightText: 2025 g10 Code GmbH
+ SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <Libkleo/Assuan>
+
+#include <QTest>
+
+using namespace Qt::StringLiterals;
+
+class AssuanTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void test_escapeAttributeValue_data()
+ {
+ QTest::addColumn<QByteArray>("input");
+ QTest::addColumn<QByteArray>("expected");
+
+ QByteArray allCharsExceptControlCharsAndSpaceAndPlus{256 - 32 - 1 - 1, Qt::Uninitialized};
+ // initialize the start of allCharsExceptControlCharsAndSpaceAndPlus with the characters from '!' to '*' (the one before '+')
+ std::iota(allCharsExceptControlCharsAndSpaceAndPlus.begin(), allCharsExceptControlCharsAndSpaceAndPlus.begin() + ('+' - '!'), '!');
+ // initialize the rest of allCharsExceptControlCharsAndSpaceAndPlus with the characters from ',' (the one after '+') to '\xFF'
+ std::iota(allCharsExceptControlCharsAndSpaceAndPlus.begin() + ('+' - '!'), allCharsExceptControlCharsAndSpaceAndPlus.end(), ',');
+
+ QByteArray allControlChars{32, Qt::Uninitialized};
+ std::iota(allControlChars.begin(), allControlChars.end(), 0);
+ QTest::newRow("empty string") << ""_ba << ""_ba;
+ QTest::newRow("nothing to escape") << allCharsExceptControlCharsAndSpaceAndPlus << allCharsExceptControlCharsAndSpaceAndPlus;
+ QTest::newRow("control chars are percent-escaped")
+ << allControlChars << "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F"_ba;
+ QTest::newRow("plus is percent-escaped") << "+"_ba << "%2B"_ba;
+ QTest::newRow("space is plus-escaped") << " "_ba << "+"_ba;
+ }
+
+ void test_escapeAttributeValue()
+ {
+ QFETCH(QByteArray, input);
+ QFETCH(QByteArray, expected);
+
+ QCOMPARE(Kleo::Assuan::escapeAttributeValue(input), expected);
+ }
+};
+
+QTEST_MAIN(AssuanTest)
+#include "assuantest.moc"
diff --git a/src/utils/assuan.cpp b/src/utils/assuan.cpp
index 82261bfb..b688269f 100644
--- a/src/utils/assuan.cpp
+++ b/src/utils/assuan.cpp
@@ -1,150 +1,177 @@
/*
utils/assuan.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "assuan.h"
#include "gnupg.h"
#include <libkleo_debug.h>
#if __has_include(<QGpgME/Debug>)
#include <QGpgME/Debug>
#endif
#include <QThread>
#include <gpgme++/context.h>
#include <gpgme++/defaultassuantransaction.h>
#include <gpgme++/error.h>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Assuan;
using namespace std::chrono_literals;
static const auto initialRetryDelay = 125ms;
static const auto maxRetryDelay = 1000ms;
static const auto maxConnectionAttempts = 10;
namespace
{
static QDebug operator<<(QDebug s, const std::vector<std::pair<std::string, std::string>> &v)
{
using pair = std::pair<std::string, std::string>;
s << '(';
for (const pair &p : v) {
s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n';
}
return s << ')';
}
}
bool Kleo::Assuan::agentIsRunning()
{
Error err;
const std::unique_ptr<Context> ctx = Context::createForEngine(AssuanEngine, &err);
if (err) {
qCWarning(LIBKLEO_LOG) << __func__ << ": Creating context for Assuan engine failed:" << err;
return false;
}
static const char *command = "GETINFO version";
err = ctx->assuanTransact(command);
if (!err) {
// all good
} else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) {
qCDebug(LIBKLEO_LOG) << __func__ << ": Connecting to the agent failed.";
} else {
qCWarning(LIBKLEO_LOG) << __func__ << ": Starting Assuan transaction for" << command << "failed:" << err;
}
return !err;
}
+QByteArray Kleo::Assuan::escapeAttributeValue(QByteArrayView value)
+{
+ static const char *HexChars = "0123456789ABCDEF";
+ const int numberToPercentEscape = std::ranges::count_if(value, [](unsigned char ch) {
+ return (ch < ' ') || (ch == '+');
+ });
+ QByteArray result{value.size() + 2 * numberToPercentEscape, Qt::Uninitialized};
+ auto resultIt = result.begin();
+ std::ranges::for_each(value, [&resultIt](unsigned char ch) {
+ if ((ch < ' ') || (ch == '+')) {
+ *resultIt = '%';
+ resultIt++;
+ *resultIt = HexChars[ch >> 4];
+ resultIt++;
+ *resultIt = HexChars[ch & 0x0F];
+ resultIt++;
+ } else if (ch == ' ') {
+ *resultIt = '+';
+ resultIt++;
+ } else {
+ *resultIt = ch;
+ resultIt++;
+ }
+ });
+ return result;
+}
+
std::unique_ptr<GpgME::AssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<GpgME::Context> &context,
const std::string &command,
std::unique_ptr<GpgME::AssuanTransaction> transaction,
GpgME::Error &err)
{
qCDebug(LIBKLEO_LOG) << __func__ << command;
int connectionAttempts = 1;
err = context->assuanTransact(command.c_str(), std::move(transaction));
auto retryDelay = initialRetryDelay;
while (err.code() == GPG_ERR_ASS_CONNECT_FAILED && connectionAttempts < maxConnectionAttempts) {
if (connectionAttempts == 1) {
Kleo::launchGpgAgent(Kleo::SkipCheckForRunningAgent);
}
// Esp. on Windows the agent processes may take their time so we try
// in increasing waits for them to start up
qCDebug(LIBKLEO_LOG) << "Connecting to the agent failed. Retrying in" << retryDelay.count() << "ms";
QThread::msleep(retryDelay.count());
retryDelay = std::min(retryDelay * 2, maxRetryDelay);
connectionAttempts++;
err = context->assuanTransact(command.c_str(), context->takeLastAssuanTransaction());
}
if (err.code()) {
qCDebug(LIBKLEO_LOG) << __func__ << command << "failed:" << err;
if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) {
qCDebug(LIBKLEO_LOG) << "Assuan problem, killing context";
context.reset();
}
return {};
}
return context->takeLastAssuanTransaction();
}
std::unique_ptr<DefaultAssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<Context> &context, const std::string &command, Error &err)
{
std::unique_ptr<AssuanTransaction> t = sendCommand(context, command, std::make_unique<DefaultAssuanTransaction>(), err);
return std::unique_ptr<DefaultAssuanTransaction>(dynamic_cast<DefaultAssuanTransaction *>(t.release()));
}
std::string Kleo::Assuan::sendDataCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
{
std::string data;
const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
if (t.get()) {
data = t->data();
qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << QString::fromStdString(data);
} else {
qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
}
return data;
}
std::vector<std::pair<std::string, std::string>> Kleo::Assuan::sendStatusLinesCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
{
std::vector<std::pair<std::string, std::string>> statusLines;
const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
if (t.get()) {
statusLines = t->statusLines();
qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << statusLines;
} else {
qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
}
return statusLines;
}
std::string Kleo::Assuan::sendStatusCommand(const std::shared_ptr<Context> &context, const std::string &command, Error &err)
{
const auto lines = sendStatusLinesCommand(context, command, err);
// The status is only the last attribute
// e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO
// it would only be FOO
const auto lastSpace = command.rfind(' ');
const auto needle = lastSpace == std::string::npos ? command : command.substr(lastSpace + 1);
for (const auto &pair : lines) {
if (pair.first == needle) {
return pair.second;
}
}
return {};
}
diff --git a/src/utils/assuan.h b/src/utils/assuan.h
index ebfe580c..f9fca620 100644
--- a/src/utils/assuan.h
+++ b/src/utils/assuan.h
@@ -1,73 +1,78 @@
/*
utils/assuan.h
This file is part of libkleopatra
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kleo_export.h"
+#include <QByteArray>
+
#include <memory>
#include <string>
#include <vector>
namespace GpgME
{
class AssuanTransaction;
class Context;
class DefaultAssuanTransaction;
class Error;
}
namespace Kleo
{
/** The Assuan namespace collects functions for communicating with the GnuPG
* agent via the Assuan protocol. */
namespace Assuan
{
/** Checks if the GnuPG agent is running and accepts connections. */
KLEO_EXPORT bool agentIsRunning();
+/*! Escapes \a value for usage as value in a SETATTR call. */
+KLEO_EXPORT QByteArray escapeAttributeValue(QByteArrayView value);
+
/** Sends the Assuan @p command using the @p transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. The returned transaction can be used
* to retrieve the result.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::unique_ptr<GpgME::AssuanTransaction> sendCommand(std::shared_ptr<GpgME::Context> &assuanContext,
const std::string &command,
std::unique_ptr<GpgME::AssuanTransaction> transaction,
GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. The returned transaction can be used
* to retrieve the result.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::unique_ptr<GpgME::DefaultAssuanTransaction>
sendCommand(std::shared_ptr<GpgME::Context> &assuanContext, const std::string &command, GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. Returns the data that was sent by
* GnuPG agent in response to the @p command.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::string sendDataCommand(std::shared_ptr<GpgME::Context> assuanContext, const std::string &command, GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. Returns the status lines that were sent by
* GnuPG agent in response to the @p command.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::vector<std::pair<std::string, std::string>>
sendStatusLinesCommand(std::shared_ptr<GpgME::Context> assuanContext, const std::string &command, GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. Returns the status that was sent by
* GnuPG agent in response to the @p command.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::string sendStatusCommand(const std::shared_ptr<GpgME::Context> &assuanContext, const std::string &command, GpgME::Error &err);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 29, 7:01 AM (21 h, 15 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
12/48/cd34102bf240d8542dffa4764514

Event Timeline