Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0dae81c7c..b3168e58e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,285 +1,285 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(RELEASE_SERVICE_VERSION_MAJOR "23")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MICRO "70")
# The RELEASE_SERVICE_VERSION is used by Gpg4win to add the Gpg4win version
if (NOT RELEASE_SERVICE_VERSION)
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
endif()
if(RELEASE_SERVICE_VERSION_MICRO LESS 10)
set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}0${RELEASE_SERVICE_VERSION_MICRO}")
else()
set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}${RELEASE_SERVICE_VERSION_MICRO}")
endif()
set(KLEOPATRA_VERSION_MAJOR "3")
set(KLEOPATRA_VERSION_MINOR "1")
set(KLEOPATRA_VERSION_MICRO "26")
set(kleopatra_version "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}.${KDE_APPLICATIONS_COMPACT_VERSION}")
# The following is for Windows
set(kleopatra_version_win "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}")
set(kleopatra_fileversion_win "${KLEOPATRA_VERSION_MAJOR},${KLEOPATRA_VERSION_MINOR},${KLEOPATRA_VERSION_MICRO},0")
if (NOT KLEOPATRA_DISTRIBUTION_TEXT)
# This is only used on Windows for the file attributes of Kleopatra
set(KLEOPATRA_DISTRIBUTION_TEXT "KDE")
endif()
project(kleopatra VERSION ${kleopatra_version})
option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF)
# Standalone build. Find / include everything necessary.
set(KF_MIN_VERSION "5.105.0")
set(KIDENTITYMANAGEMENT_VERSION "5.23.40")
set(KMAILTRANSPORT_VERSION "5.23.40")
set(AKONADI_MIME_VERSION "5.23.41")
set(KMIME_VERSION "5.23.40")
-set(LIBKLEO_VERSION "5.23.43")
+set(LIBKLEO_VERSION "5.23.44")
set(QT_REQUIRED_VERSION "5.15.2")
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
endif()
set(GPGME_REQUIRED_VERSION "1.16.0")
set(LIBASSUAN_REQUIRED_VERSION "2.4.2")
set(GPG_ERROR_REQUIRED_VERSION "1.36")
if (WIN32)
set(KF5_WANT_VERSION "5.70.0")
set(KMIME_WANT_VERSION "5.12.0")
else ()
set(KF5_WANT_VERSION ${KF_MIN_VERSION})
set(KMIME_WANT_VERSION ${KMIME_VERSION})
endif ()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF5_WANT_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
include(ECMInstallIcons)
include(ECMSetupVersion)
include(ECMAddTests)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
include(FeatureSummary)
include(CheckFunctionExists)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(KDEClangFormat)
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
set(KF_MIN_VERSION "5.240.0")
set(KF_MAJOR_VERSION "6")
else()
set(KF_MIN_VERSION "5.105.0")
set(KF_MAJOR_VERSION "5")
endif()
# Find KF5 packages
find_package(KF${KF_MAJOR_VERSION} ${KF5_WANT_VERSION}
REQUIRED COMPONENTS
Codecs
Config
ConfigWidgets
CoreAddons
Crash
I18n
IconThemes
ItemModels
KCMUtils
KIO
WidgetsAddons
WindowSystem
XmlGui
OPTIONAL_COMPONENTS
DocTools
)
set_package_properties(KF${KF_MAJOR_VERSION}DocTools PROPERTIES
DESCRIPTION "Documentation tools"
PURPOSE "Required to generate Kleopatra documentation."
TYPE OPTIONAL)
# Optional packages
if (WIN32)
# Only a replacement available for Windows so this
# is required on other platforms.
find_package(KF${KF_MAJOR_VERSION}DBusAddons ${KF5_WANT_VERSION} CONFIG)
set_package_properties(KF${KF_MAJOR_VERSION}DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus"
PURPOSE "DBus session integration"
URL "https://inqlude.org/libraries/kdbusaddons.html"
TYPE OPTIONAL)
else()
find_package(KF${KF_MAJOR_VERSION}DBusAddons ${KF5_WANT_VERSION} CONFIG REQUIRED)
set(_kleopatra_dbusaddons_libs KF${KF_MAJOR_VERSION}::DBusAddons)
endif()
set(HAVE_QDBUS ${Qt${QT_MAJOR_VERSION}DBus_FOUND})
find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
if (Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.19.0")
set(GPGMEPP_SUPPORTS_SET_CURVE 1)
endif()
if (Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.19.1")
set(GPGMEPP_KEY_CANSIGN_IS_FIXED 1)
endif()
if (QT_MAJOR_VERSION STREQUAL "6")
find_package(QGpgmeQt6 ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
else()
find_package(QGpgme ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
endif()
if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.17.0")
set(QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY 1)
set(QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE 1)
set(QGPGME_SUPPORTS_WKDLOOKUP 1)
set(QGPGME_SUPPORTS_IMPORT_WITH_FILTER 1)
set(QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1)
set(QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1)
set(QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1)
set(QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID 1)
endif()
if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.18.0")
set(QGPGME_SUPPORTS_KEY_REVOCATION 1)
set(QGPGME_SUPPORTS_KEY_REFRESH 1)
set(QGPGME_SUPPORTS_SET_FILENAME 1)
set(QGPGME_SUPPORTS_SET_PRIMARY_UID 1)
endif()
if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.19.0")
set(QGPGME_SUPPORTS_DEFERRED_IMPORT_JOB 1)
set(QGPGME_SUPPORTS_ARCHIVE_JOBS 1)
set(QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS 1)
endif()
if (QT_MAJOR_VERSION STREQUAL "6")
find_package(Qt6Core5Compat)
endif()
# Kdepimlibs packages
find_package(KPim${KF_MAJOR_VERSION}Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KPim${KF_MAJOR_VERSION}Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED)
find_package(KPim${KF_MAJOR_VERSION}IdentityManagement ${KIDENTITYMANAGEMENT_VERSION} CONFIG)
find_package(KPim${KF_MAJOR_VERSION}MailTransport ${KMAILTRANSPORT_VERSION} CONFIG)
find_package(KPim${KF_MAJOR_VERSION}AkonadiMime ${AKONADI_MIME_VERSION} CONFIG)
find_package(Qt${QT_MAJOR_VERSION} ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport)
find_package(LibAssuan ${LIBASSUAN_REQUIRED_VERSION} REQUIRED)
set_package_properties(LibAssuan PROPERTIES
TYPE REQUIRED
PURPOSE "Needed for Kleopatra to act as the GnuPG UI Server"
)
find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED)
set_package_properties(LibGpgError PROPERTIES
TYPE REQUIRED
)
set(kleopatra_release FALSE)
if(NOT kleopatra_release)
find_package(Git)
if(GIT_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE rc
ERROR_QUIET)
if(rc EQUAL 0)
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%h ${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE Kleopatra_WC_REVISION)
string(REGEX REPLACE "\n" "" Kleopatra_WC_REVISION "${Kleopatra_WC_REVISION}")
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%cI ${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE Kleopatra_WC_LAST_CHANGED_DATE)
string(REGEX REPLACE "^([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+).*$" "\\1\\2\\3T\\4\\5\\6"
Kleopatra_WC_LAST_CHANGED_DATE "${Kleopatra_WC_LAST_CHANGED_DATE}")
set(kleopatra_version "${kleopatra_version}+git${Kleopatra_WC_LAST_CHANGED_DATE}~${Kleopatra_WC_REVISION}")
endif()
endif()
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version-kleopatra.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kleopatra.h)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
if (WIN32)
# On Windows, we need to use stuff deprecated since Qt 5.11, e.g. from QDesktopWidget
ecm_set_disabled_deprecation_versions(QT 5.10.0 KF 5.103.0)
else ()
ecm_set_disabled_deprecation_versions(QT 6.4 KF 5.103.0)
endif ()
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers")
endif()
if(MINGW)
# we do not care about different signedness of passed pointer arguments
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>)
endif()
add_definitions(-DQT_NO_EMIT)
remove_definitions(-DQT_NO_FOREACH)
# Disable the use of QStringBuilder for operator+ to prevent crashes when
# returning the result of concatenating string temporaries in lambdas. We do
# this for example in some std::transform expressions.
# This is a known issue: https://bugreports.qt.io/browse/QTBUG-47066
# Alternatively, one would always have to remember to force the lambdas to
# return a QString instead of QStringBuilder, but that's just too easy to
# forget and, unfortunately, the compiler doesn't issue a warning if one forgets
# this. So, it's just too dangerous.
# One can still use QStringBuilder explicitly with the operator% if necessary.
remove_definitions(-DQT_USE_FAST_OPERATOR_PLUS)
remove_definitions(-DQT_USE_QSTRINGBUILDER)
kde_enable_exceptions()
option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF)
if (USE_UNITY_CMAKE_SUPPORT)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON)
endif()
add_subdirectory(pics)
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(tests)
add_subdirectory(autotests)
endif()
ecm_qt_install_logging_categories(
EXPORT KLEOPATRA
FILE kleopatra.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
ki18n_install(po)
if(KF${KF_MAJOR_VERSION}DocTools_FOUND)
kdoctools_install(po)
add_subdirectory(doc)
endif()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
# add clang-format target for all our real source files
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
diff --git a/src/commands/adduseridcommand.cpp b/src/commands/adduseridcommand.cpp
index 68086a3bf..6ecd77a5c 100644
--- a/src/commands/adduseridcommand.cpp
+++ b/src/commands/adduseridcommand.cpp
@@ -1,219 +1,219 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/adduseridcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "adduseridcommand.h"
#include "command_p.h"
#include "dialogs/adduseriddialog.h"
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QGpgME/QuickJob>
#include <QObjectCleanupHandler>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
class AddUserIDCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::AddUserIDCommand;
AddUserIDCommand *q_func() const
{
return static_cast<AddUserIDCommand *>(q);
}
public:
explicit Private(AddUserIDCommand *qq, KeyListController *c);
~Private() override;
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void ensureDialogCreated();
void createJob();
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::Key key;
QObjectCleanupHandler cleaner;
QPointer<AddUserIDDialog> dialog;
QPointer<QGpgME::QuickJob> job;
};
AddUserIDCommand::Private *AddUserIDCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const AddUserIDCommand::Private *AddUserIDCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
AddUserIDCommand::Private::Private(AddUserIDCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
AddUserIDCommand::Private::~Private() = default;
AddUserIDCommand::AddUserIDCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{this, c}}
{
}
AddUserIDCommand::AddUserIDCommand(const GpgME::Key &key)
: Command{key, new Private{this, nullptr}}
{
}
AddUserIDCommand::~AddUserIDCommand()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
}
void AddUserIDCommand::doStart()
{
const std::vector<Key> keys = d->keys();
if (keys.size() != 1) {
d->finished();
return;
}
d->key = keys.front();
if (d->key.protocol() != GpgME::OpenPGP
|| !d->key.hasSecret()) {
d->finished();
return;
}
d->ensureDialogCreated();
const UserID uid = d->key.userID(0);
d->dialog->setName(QString::fromUtf8(uid.name()));
d->dialog->setEmail(Formatting::prettyEMail(uid.email(), uid.id()));
d->dialog->show();
}
void AddUserIDCommand::Private::slotDialogAccepted()
{
Q_ASSERT(dialog);
createJob();
if (!job) {
finished();
return;
}
job->startAddUid(key, dialog->userID());
}
void AddUserIDCommand::Private::slotDialogRejected()
{
Q_EMIT q->canceled();
finished();
}
void AddUserIDCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
} else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void AddUserIDCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
if (d->job) {
d->job->slotCancel();
}
}
void AddUserIDCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new AddUserIDDialog;
cleaner.add(dialog);
applyWindowID(dialog);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
void AddUserIDCommand::Private::createJob()
{
Q_ASSERT(!job);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
const auto j = backend->quickJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &QGpgME::QuickJob::result,
q, [this](const GpgME::Error &err) { slotResult(err); });
job = j;
}
void AddUserIDCommand::Private::showErrorDialog(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred while trying to add the user ID: "
"<message>%1</message></para>",
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
i18nc("@title:window", "Add User ID Error"));
}
void AddUserIDCommand::Private::showSuccessDialog()
{
information(i18nc("@info", "User ID successfully added."),
i18nc("@title:window", "Add User ID Succeeded"));
}
#undef d
#undef q
#include "moc_adduseridcommand.cpp"
diff --git a/src/commands/authenticatepivcardapplicationcommand.cpp b/src/commands/authenticatepivcardapplicationcommand.cpp
index 73369fd77..baeba50e6 100644
--- a/src/commands/authenticatepivcardapplicationcommand.cpp
+++ b/src/commands/authenticatepivcardapplicationcommand.cpp
@@ -1,193 +1,195 @@
/* commands/authenticatepivcardapplicationcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "authenticatepivcardapplicationcommand.h"
#include "cardcommand_p.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "dialogs/pivcardapplicationadministrationkeyinputdialog.h"
+#include <Libkleo/Formatting>
+
#include <KLocalizedString>
#include <gpgme++/error.h>
#include <gpg-error.h>
#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
# define GPG_ERROR_HAS_BAD_AUTH
#endif
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace Kleo::SmartCard;
using namespace GpgME;
class AuthenticatePIVCardApplicationCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::AuthenticatePIVCardApplicationCommand;
AuthenticatePIVCardApplicationCommand *q_func() const
{
return static_cast<AuthenticatePIVCardApplicationCommand *>(q);
}
public:
explicit Private(AuthenticatePIVCardApplicationCommand *qq, const std::string &serialNumber, QWidget *p);
~Private() override;
void init();
private:
void slotResult(const Error &err);
void slotDialogAccepted();
void slotDialogRejected();
private:
void authenticate(const QByteArray& adminKey);
void retryAskingForKey();
void ensureDialogCreated();
private:
QString prompt;
QPointer<PIVCardApplicationAdministrationKeyInputDialog> dialog;
};
AuthenticatePIVCardApplicationCommand::Private *AuthenticatePIVCardApplicationCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const AuthenticatePIVCardApplicationCommand::Private *AuthenticatePIVCardApplicationCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
AuthenticatePIVCardApplicationCommand::Private::Private(AuthenticatePIVCardApplicationCommand *qq, const std::string &serialNumber, QWidget *p)
: CardCommand::Private(qq, serialNumber, p)
, dialog()
{
}
AuthenticatePIVCardApplicationCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG) << "AuthenticatePIVCardApplicationCommand::Private::~Private()";
}
AuthenticatePIVCardApplicationCommand::AuthenticatePIVCardApplicationCommand(const std::string &serialNumber, QWidget *p)
: CardCommand(new Private(this, serialNumber, p))
{
d->init();
}
void AuthenticatePIVCardApplicationCommand::Private::init()
{
}
AuthenticatePIVCardApplicationCommand::~AuthenticatePIVCardApplicationCommand()
{
qCDebug(KLEOPATRA_LOG) << "AuthenticatePIVCardApplicationCommand::~AuthenticatePIVCardApplicationCommand()";
}
void AuthenticatePIVCardApplicationCommand::setPrompt(const QString& prompt)
{
d->prompt = prompt;
}
void AuthenticatePIVCardApplicationCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "AuthenticatePIVCardApplicationCommand::doStart()";
// at first, try to authenticate using the default application administration key
d->authenticate(QByteArray::fromHex("010203040506070801020304050607080102030405060708"));
}
void AuthenticatePIVCardApplicationCommand::doCancel()
{
}
void AuthenticatePIVCardApplicationCommand::Private::authenticate(const QByteArray& adminKey)
{
qCDebug(KLEOPATRA_LOG) << "AuthenticatePIVCardApplicationCommand::authenticate()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const QByteArray plusPercentEncodedAdminKey = adminKey.toPercentEncoding().replace(' ', '+');
const QByteArray command = QByteArray("SCD SETATTR AUTH-ADM-KEY ") + plusPercentEncodedAdminKey;
ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, command, q, [this](const GpgME::Error &err) {
slotResult(err);
});
}
void AuthenticatePIVCardApplicationCommand::Private::slotResult(const Error &err)
{
qCDebug(KLEOPATRA_LOG) << "AuthenticatePIVCardApplicationCommand::slotResult():"
- << err.asString() << "(" << err.code() << ")";
+ << Formatting::errorAsString(err) << "(" << err.code() << ")";
if (err.isCanceled()) {
canceled();
return;
}
if (err) {
#ifdef GPG_ERROR_HAS_BAD_AUTH
if (err.code() == GPG_ERR_BAD_AUTH) {
retryAskingForKey();
return;
}
#endif
- error(i18nc("@info", "Authenticating to the card failed: %1", QString::fromLatin1(err.asString())));
+ error(i18nc("@info", "Authenticating to the card failed: %1", Formatting::errorAsString(err)));
}
finished();
}
void AuthenticatePIVCardApplicationCommand::Private::retryAskingForKey()
{
ensureDialogCreated();
Q_ASSERT(dialog);
dialog->show();
}
void AuthenticatePIVCardApplicationCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new PIVCardApplicationAdministrationKeyInputDialog(parentWidgetOrView());
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setLabelText(prompt.isEmpty() ?
i18n("Please enter the PIV Card Application Administration Key in hex-encoded form.") :
prompt);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
void AuthenticatePIVCardApplicationCommand::Private::slotDialogAccepted()
{
authenticate(dialog->adminKey());
}
void AuthenticatePIVCardApplicationCommand::Private::slotDialogRejected()
{
canceled();
}
#undef d
#undef q
#include "moc_authenticatepivcardapplicationcommand.cpp"
diff --git a/src/commands/certificatetopivcardcommand.cpp b/src/commands/certificatetopivcardcommand.cpp
index 02c183dd4..da914f610 100644
--- a/src/commands/certificatetopivcardcommand.cpp
+++ b/src/commands/certificatetopivcardcommand.cpp
@@ -1,265 +1,265 @@
/* commands/certificatetopivcardcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certificatetopivcardcommand.h"
#include "cardcommand_p.h"
#include "commands/authenticatepivcardapplicationcommand.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "utils/writecertassuantransaction.h"
#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <qgpgme/dataprovider.h>
#include <gpgme++/context.h>
#include <gpg-error.h>
#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
# define GPG_ERROR_HAS_NO_AUTH
#endif
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
class CertificateToPIVCardCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::CertificateToPIVCardCommand;
CertificateToPIVCardCommand *q_func() const
{
return static_cast<CertificateToPIVCardCommand *>(q);
}
public:
explicit Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno);
~Private() override;
private:
void start();
void startCertificateToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
private:
std::string cardSlot;
Key certificate;
bool hasBeenCanceled = false;
};
CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define q q_func()
#define d d_func()
CertificateToPIVCardCommand::Private::Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno)
: CardCommand::Private(qq, serialno, nullptr)
, cardSlot(slot)
{
}
CertificateToPIVCardCommand::Private::~Private()
{
}
namespace {
static Key getCertificateToWriteToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &card)
{
if (!cardSlot.empty()) {
const std::string cardKeygrip = card->keyInfo(cardSlot).grip;
const auto certificate = KeyCache::instance()->findSubkeyByKeyGrip(cardKeygrip).parent();
if (certificate.isNull() || certificate.protocol() != GpgME::CMS) {
return Key();
}
if ((cardSlot == PIVCard::pivAuthenticationKeyRef() && certificate.canSign()) ||
(cardSlot == PIVCard::cardAuthenticationKeyRef() && certificate.canSign()) ||
(cardSlot == PIVCard::digitalSignatureKeyRef() && certificate.canSign()) ||
(cardSlot == PIVCard::keyManagementKeyRef() && certificate.canEncrypt())) {
return certificate;
}
}
return Key();
}
}
void CertificateToPIVCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::start()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
certificate = getCertificateToWriteToPIVCard(cardSlot, pivCard);
if (certificate.isNull()) {
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
finished();
return;
}
const QString certificateInfo = i18nc("X.509 certificate DN (validity, created: date)", "%1 (%2, created: %3)",
DN(certificate.userID(0).id()).prettyDN(),
Formatting::complianceStringShort(certificate),
Formatting::creationDateString(certificate));
const QString message = i18nc(
"@info %1 name of card slot, %2 serial number of card",
"<p>Please confirm that you want to write the following certificate to the %1 slot of card %2:</p>"
"<center>%3</center>",
PIVCard::keyDisplayName(cardSlot), QString::fromStdString(serialNumber()), certificateInfo);
auto confirmButton = KStandardGuiItem::ok();
confirmButton.setText(i18nc("@action:button", "Write certificate"));
confirmButton.setToolTip(QString());
const auto choice = KMessageBox::questionTwoActions(
parentWidgetOrView(),
message,
i18nc("@title:window", "Write certificate to card"),
confirmButton,
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Notify | KMessageBox::WindowModal);
if (choice != KMessageBox::ButtonCode::PrimaryAction) {
finished();
return;
}
startCertificateToPIVCard();
}
void CertificateToPIVCardCommand::Private::startCertificateToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::startCertificateToPIVCard()";
auto ctx = Context::createForProtocol(GpgME::CMS);
QGpgME::QByteArrayDataProvider dp;
Data data(&dp);
const Error err = ctx->exportPublicKeys(certificate.primaryFingerprint(), data);
if (err) {
- error(i18nc("@info", "Exporting the certificate failed: %1", QString::fromUtf8(err.asString())));
+ error(i18nc("@info", "Exporting the certificate failed: %1", Formatting::errorAsString(err)));
finished();
return;
}
const QByteArray certificateData = dp.data();
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const QByteArray command = QByteArrayLiteral("SCD WRITECERT ") + QByteArray::fromStdString(cardSlot);
auto transaction = std::unique_ptr<AssuanTransaction>(new WriteCertAssuanTransaction(certificateData));
ReaderStatus::mutableInstance()->startTransaction(
pivCard,
command,
q_func(),
[this](const GpgME::Error &err) {
q->certificateToPIVCardDone(err);
},
std::move(transaction));
}
void CertificateToPIVCardCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
q, [this]() { authenticationFinished(); });
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
q, [this]() { authenticationCanceled(); });
cmd->start();
}
void CertificateToPIVCardCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationFinished()";
if (!hasBeenCanceled) {
startCertificateToPIVCard();
}
}
void CertificateToPIVCardCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
CertificateToPIVCardCommand::CertificateToPIVCardCommand(const std::string& cardSlot, const std::string &serialno)
: CardCommand(new Private(this, cardSlot, serialno))
{
}
CertificateToPIVCardCommand::~CertificateToPIVCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::~CertificateToPIVCardCommand()";
}
void CertificateToPIVCardCommand::certificateToPIVCardDone(const Error &err)
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::certificateToPIVCardDone():"
- << err.asString() << "(" << err.code() << ")";
+ << Formatting::errorAsString(err) << "(" << err.code() << ")";
if (err) {
#ifdef GPG_ERROR_HAS_NO_AUTH
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
d->authenticate();
return;
}
#endif
- d->error(i18nc("@info", "Writing the certificate to the card failed: %1", QString::fromUtf8(err.asString())));
+ d->error(i18nc("@info", "Writing the certificate to the card failed: %1", Formatting::errorAsString(err)));
} else if (!err.isCanceled()) {
d->success(i18nc("@info", "Writing the certificate to the card succeeded."));
ReaderStatus::mutableInstance()->updateStatus();
}
d->finished();
}
void CertificateToPIVCardCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::doStart()";
d->start();
}
void CertificateToPIVCardCommand::doCancel()
{
}
#undef q_func
#undef d_func
diff --git a/src/commands/certifycertificatecommand.cpp b/src/commands/certifycertificatecommand.cpp
index 0af9f0fb7..30ddb98a0 100644
--- a/src/commands/certifycertificatecommand.cpp
+++ b/src/commands/certifycertificatecommand.cpp
@@ -1,346 +1,346 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/signcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2019 g10code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certifycertificatecommand.h"
#include "newopenpgpcertificatecommand.h"
#include "command_p.h"
#include "exportopenpgpcertstoservercommand.h"
#include "dialogs/certifycertificatedialog.h"
#include "utils/keys.h"
#include "utils/tags.h"
#include <Libkleo/Algorithm>
#include <Libkleo/KeyCache>
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QGpgME/SignKeyJob>
#include <QDate>
#include <QEventLoop>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
#include <KLocalizedString>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
using namespace QGpgME;
class CertifyCertificateCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::CertifyCertificateCommand;
CertifyCertificateCommand *q_func() const
{
return static_cast<CertifyCertificateCommand *>(q);
}
public:
explicit Private(CertifyCertificateCommand *qq, KeyListController *c);
~Private() override;
void init();
private:
void slotDialogRejected();
void slotResult(const Error &err);
void slotCertificationPrepared();
private:
void ensureDialogCreated();
void createJob();
private:
GpgME::Key target;
std::vector<UserID> uids;
QPointer<CertifyCertificateDialog> dialog;
QPointer<QGpgME::SignKeyJob> job;
};
CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
CertifyCertificateCommand::Private::Private(CertifyCertificateCommand *qq, KeyListController *c)
: Command::Private(qq, c),
uids(),
dialog(),
job()
{
}
CertifyCertificateCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
if (dialog) {
delete dialog;
dialog = nullptr;
}
}
CertifyCertificateCommand::CertifyCertificateCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::Key &key)
: Command(key, new Private(this, nullptr))
{
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::UserID &uid)
: Command(uid.parent(), new Private(this, nullptr))
{
std::vector<UserID>(1, uid).swap(d->uids);
d->init();
}
CertifyCertificateCommand::CertifyCertificateCommand(const std::vector<GpgME::UserID> &uids)
: Command(uids.empty() ? Key() : uids.front().parent(), new Private(this, nullptr))
{
d->uids = uids;
d->init();
}
void CertifyCertificateCommand::Private::init()
{
}
CertifyCertificateCommand::~CertifyCertificateCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void CertifyCertificateCommand::doStart()
{
const std::vector<Key> keys = d->keys();
if (keys.size() != 1 ||
keys.front().protocol() != GpgME::OpenPGP) {
d->finished();
return;
}
// hold on to the key to certify to avoid invalidation during refreshes of the key cache
d->target = keys.front();
if (d->target.isExpired() || d->target.isRevoked()) {
const auto title = d->target.isRevoked() ? i18nc("@title:window", "Key is Revoked") : i18nc("@title:window", "Key is Expired");
const auto message = d->target.isRevoked() //
? i18nc("@info", "This key has been revoked. You cannot certify it.")
: i18nc("@info", "This key has expired. You cannot certify it.");
d->information(message, title);
d->finished();
return;
}
auto findAnyGoodKey = []() {
const std::vector<Key> secKeys = KeyCache::instance()->secretKeys();
return std::any_of(secKeys.cbegin(), secKeys.cend(), [](const Key &secKey) {
return secKey.canCertify() && secKey.protocol() == OpenPGP && !secKey.isRevoked()
&& !secKey.isExpired() && !secKey.isInvalid();
});
};
if (!findAnyGoodKey()) {
auto sel =
KMessageBox::questionTwoActions(d->parentWidgetOrView(),
xi18nc("@info", "To certify other certificates, you first need to create an OpenPGP certificate for yourself.")
+ QStringLiteral("<br><br>") + i18n("Do you wish to create one now?"),
i18n("Certification Not Possible"),
KGuiItem(i18n("Create")),
KStandardGuiItem::cancel());
if (sel == KMessageBox::ButtonCode::PrimaryAction) {
QEventLoop loop;
auto cmd = new NewOpenPGPCertificateCommand;
cmd->setParentWidget(d->parentWidgetOrView());
connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(cmd, &NewOpenPGPCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
} else {
Q_EMIT(canceled());
d->finished();
return;
}
// Check again for secret keys
if (!findAnyGoodKey()) {
qCDebug(KLEOPATRA_LOG) << "Sec Keys still empty after keygen.";
Q_EMIT(canceled());
d->finished();
return;
}
}
const char *primary = keys.front().primaryFingerprint();
const bool anyMismatch = std::any_of(d->uids.cbegin(), d->uids.cend(), [primary](const UserID &uid) {
return qstricmp(uid.parent().primaryFingerprint(), primary) != 0;
});
if (anyMismatch) {
qCWarning(KLEOPATRA_LOG) << "User ID <-> Key mismatch!";
d->finished();
return;
}
d->ensureDialogCreated();
Q_ASSERT(d->dialog);
if (!(d->target.keyListMode() & GpgME::SignatureNotations)) {
d->target.update();
}
d->dialog->setCertificateToCertify(d->target);
if (d->uids.size()) {
d->dialog->setSelectedUserIDs(d->uids);
}
d->dialog->show();
}
void CertifyCertificateCommand::Private::slotDialogRejected()
{
Q_EMIT q->canceled();
finished();
}
void CertifyCertificateCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
// do nothing
} else if (err) {
error(i18n("<p>An error occurred while trying to certify<br/><br/>"
"<b>%1</b>:</p><p>\t%2</p>",
Formatting::formatForComboBox(target),
- QString::fromUtf8(err.asString())),
+ Formatting::errorAsString(err)),
i18n("Certification Error"));
} else if (dialog && dialog->exportableCertificationSelected() && dialog->sendToServer()) {
auto const cmd = new ExportOpenPGPCertsToServerCommand(target);
cmd->start();
} else {
information(i18n("Certification successful."),
i18n("Certification Succeeded"));
}
if (!dialog->tags().isEmpty()) {
Tags::enableTags();
}
finished();
}
void CertifyCertificateCommand::Private::slotCertificationPrepared()
{
Q_ASSERT(dialog);
const auto selectedUserIds = dialog->selectedUserIDs();
std::vector<unsigned int> userIdIndexes;
userIdIndexes.reserve(selectedUserIds.size());
for (unsigned int i = 0, numUserIds = target.numUserIDs(); i < numUserIds; ++i) {
const auto userId = target.userID(i);
const bool userIdIsSelected = Kleo::any_of(selectedUserIds, [userId](const auto &uid) {
return Kleo::userIDsAreEqual(userId, uid);
});
if (userIdIsSelected) {
userIdIndexes.push_back(i);
}
}
createJob();
Q_ASSERT(job);
job->setExportable(dialog->exportableCertificationSelected());
job->setNonRevocable(dialog->nonRevocableCertificationSelected());
job->setUserIDsToSign(userIdIndexes);
job->setSigningKey(dialog->selectedSecretKey());
if (!dialog->tags().isEmpty()) {
// do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142)
job->setRemark(dialog->tags());
}
job->setDupeOk(true);
if (dialog->trustSignatureSelected() && !dialog->trustSignatureDomain().isEmpty()) {
// always create level 1 trust signatures with complete trust
job->setTrustSignature(TrustSignatureTrust::Complete, 1, dialog->trustSignatureDomain());
}
if (!dialog->expirationDate().isNull()) {
job->setExpirationDate(dialog->expirationDate());
}
if (const Error err = job->start(target)) {
slotResult(err);
}
}
void CertifyCertificateCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG);
if (d->job) {
d->job->slotCancel();
}
}
void CertifyCertificateCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new CertifyCertificateDialog;
applyWindowID(dialog);
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
connect(dialog, &QDialog::accepted, q, [this]() { slotCertificationPrepared(); });
}
void CertifyCertificateCommand::Private::createJob()
{
Q_ASSERT(!job);
Q_ASSERT(target.protocol() == OpenPGP);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
SignKeyJob *const j = backend->signKeyJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &SignKeyJob::result, q, [this](const GpgME::Error &result) { slotResult(result); });
job = j;
}
#undef d
#undef q
#include "moc_certifycertificatecommand.cpp"
diff --git a/src/commands/changeexpirycommand.cpp b/src/commands/changeexpirycommand.cpp
index c9f47c499..00d24a31f 100644
--- a/src/commands/changeexpirycommand.cpp
+++ b/src/commands/changeexpirycommand.cpp
@@ -1,292 +1,292 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/changeexpirycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "changeexpirycommand.h"
#include "command_p.h"
#include "dialogs/expirydialog.h"
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <QGpgME/Protocol>
#include <QGpgME/ChangeExpiryJob>
#include <QDateTime>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
using namespace QGpgME;
namespace
{
#if QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY
bool allNotRevokedSubkeysHaveSameExpirationAsPrimaryKey(const Key &key)
{
Q_ASSERT(!key.isNull() && key.numSubkeys() > 0);
const auto subkeys = key.subkeys();
const auto primaryKey = subkeys[0];
if (primaryKey.neverExpires()) {
return std::all_of(std::begin(subkeys), std::end(subkeys), [] (const auto &subkey) {
// revoked subkeys are ignored by gpg --quick-set-expire when updating the expiration of all subkeys
return subkey.isRevoked() || subkey.neverExpires();
});
}
const auto primaryExpiration = quint32(primaryKey.expirationTime());
const auto range = std::make_pair(primaryExpiration > 10 ? primaryExpiration - 10 : 0,
primaryExpiration < std::numeric_limits<quint32>::max() - 10 ? primaryExpiration + 10 : std::numeric_limits<quint32>::max());
return std::all_of(std::begin(subkeys), std::end(subkeys), [range](const auto &subkey) {
// revoked subkeys are ignored by gpg --quick-set-expire when updating the expiration of all subkeys;
// check if expiration of subkey is (more or less) the same as the expiration of the primary key
return subkey.isRevoked() ||
(range.first <= quint32(subkey.expirationTime()) && quint32(subkey.expirationTime()) <= range.second);
});
}
#endif
}
class ChangeExpiryCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::ChangeExpiryCommand;
ChangeExpiryCommand *q_func() const
{
return static_cast<ChangeExpiryCommand *>(q);
}
public:
explicit Private(ChangeExpiryCommand *qq, KeyListController *c);
~Private() override;
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void ensureDialogCreated(ExpiryDialog::Mode mode);
void createJob();
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::Key key;
GpgME::Subkey subkey;
QPointer<ExpiryDialog> dialog;
QPointer<ChangeExpiryJob> job;
};
ChangeExpiryCommand::Private *ChangeExpiryCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ChangeExpiryCommand::Private *ChangeExpiryCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ChangeExpiryCommand::Private::Private(ChangeExpiryCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
ChangeExpiryCommand::Private::~Private() = default;
void ChangeExpiryCommand::Private::slotDialogAccepted()
{
Q_ASSERT(dialog);
static const QTime END_OF_DAY{23, 59, 59};
const QDateTime expiry{dialog->dateOfExpiry(), END_OF_DAY};
qCDebug(KLEOPATRA_LOG) << "expiry" << expiry;
createJob();
Q_ASSERT(job);
#if QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY
if (subkey.isNull() && dialog->updateExpirationOfAllSubkeys()) {
job->setOptions(ChangeExpiryJob::UpdateAllSubkeys);
}
#endif
std::vector<Subkey> subkeys;
if (!subkey.isNull() && subkey.keyID() != key.keyID()) { // ignore the primary subkey
subkeys.push_back(subkey);
}
if (const Error err = job->start(key, expiry, subkeys)) {
showErrorDialog(err);
finished();
}
}
void ChangeExpiryCommand::Private::slotDialogRejected()
{
Q_EMIT q->canceled();
finished();
}
void ChangeExpiryCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled())
;
else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void ChangeExpiryCommand::Private::ensureDialogCreated(ExpiryDialog::Mode mode)
{
if (dialog) {
return;
}
dialog = new ExpiryDialog{mode};
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
void ChangeExpiryCommand::Private::createJob()
{
Q_ASSERT(!job);
const auto backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
if (!backend) {
return;
}
ChangeExpiryJob *const j = backend->changeExpiryJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &ChangeExpiryJob::result,
q, [this] (const auto &err) { slotResult(err); });
job = j;
}
void ChangeExpiryCommand::Private::showErrorDialog(const Error &err)
{
error(i18n("<p>An error occurred while trying to change "
"the end of the validity period for <b>%1</b>:</p><p>%2</p>",
Formatting::formatForComboBox(key),
- QString::fromLocal8Bit(err.asString())));
+ Formatting::errorAsString(err)));
}
void ChangeExpiryCommand::Private::showSuccessDialog()
{
success(i18n("End of validity period changed successfully."));
}
ChangeExpiryCommand::ChangeExpiryCommand(KeyListController *c)
: Command{new Private{this, c}}
{
}
ChangeExpiryCommand::ChangeExpiryCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{this, c}}
{
}
ChangeExpiryCommand::ChangeExpiryCommand(const GpgME::Key &key)
: Command{key, new Private{this, nullptr}}
{
}
ChangeExpiryCommand::~ChangeExpiryCommand() = default;
void ChangeExpiryCommand::setSubkey(const GpgME::Subkey &subkey)
{
d->subkey = subkey;
}
void ChangeExpiryCommand::doStart()
{
const std::vector<Key> keys = d->keys();
if (keys.size() != 1 ||
keys.front().protocol() != GpgME::OpenPGP ||
!keys.front().hasSecret() ||
keys.front().subkey(0).isNull()) {
d->finished();
return;
}
d->key = keys.front();
if (!d->subkey.isNull() &&
d->subkey.parent().primaryFingerprint() != d->key.primaryFingerprint()) {
qDebug() << "Invalid subkey" << d->subkey.fingerprint()
<< ": Not a subkey of key" << d->key.primaryFingerprint();
d->finished();
return;
}
ExpiryDialog::Mode mode;
if (!d->subkey.isNull()) {
mode = ExpiryDialog::Mode::UpdateIndividualSubkey;
} else if (d->key.numSubkeys() == 1) {
mode = ExpiryDialog::Mode::UpdateCertificateWithoutSubkeys;
} else {
mode = ExpiryDialog::Mode::UpdateCertificateWithSubkeys;
}
d->ensureDialogCreated(mode);
Q_ASSERT(d->dialog);
const Subkey subkey = !d->subkey.isNull() ? d->subkey : d->key.subkey(0);
d->dialog->setDateOfExpiry(subkey.neverExpires() ? QDate() :
QDateTime::fromSecsSinceEpoch(quint32(subkey.expirationTime())).date());
#if QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY
if (mode == ExpiryDialog::Mode::UpdateCertificateWithSubkeys) {
d->dialog->setUpdateExpirationOfAllSubkeys(allNotRevokedSubkeysHaveSameExpirationAsPrimaryKey(d->key));
}
#endif
d->dialog->show();
}
void ChangeExpiryCommand::doCancel()
{
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
#include "moc_changeexpirycommand.cpp"
diff --git a/src/commands/changeownertrustcommand.cpp b/src/commands/changeownertrustcommand.cpp
index 96b8763a5..677570155 100644
--- a/src/commands/changeownertrustcommand.cpp
+++ b/src/commands/changeownertrustcommand.cpp
@@ -1,281 +1,281 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/changeownertrustcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "changeownertrustcommand.h"
#include "command_p.h"
#include <Libkleo/Compliance>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <QGpgME/Protocol>
#include <QGpgME/ChangeOwnerTrustJob>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
using namespace QGpgME;
class ChangeOwnerTrustCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::ChangeOwnerTrustCommand;
ChangeOwnerTrustCommand *q_func() const
{
return static_cast<ChangeOwnerTrustCommand *>(q);
}
public:
Private(ChangeOwnerTrustCommand *qq, KeyListController *c);
private:
void startJob(Key::OwnerTrust trust);
void createJob();
void slotResult(const Error &err);
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
QPointer<ChangeOwnerTrustJob> job;
Key::OwnerTrust trustToSet = Key::OwnerTrust::Unknown;
};
ChangeOwnerTrustCommand::Private *ChangeOwnerTrustCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ChangeOwnerTrustCommand::Private *ChangeOwnerTrustCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ChangeOwnerTrustCommand::Private::Private(ChangeOwnerTrustCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
ChangeOwnerTrustCommand::ChangeOwnerTrustCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{this, c}}
{
}
ChangeOwnerTrustCommand::~ChangeOwnerTrustCommand()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
}
void ChangeOwnerTrustCommand::doStart()
{
if (d->keys().size() != 1) {
d->finished();
return;
}
const Key key = d->key();
if (key.protocol() != GpgME::OpenPGP) {
d->finished();
return;
}
const auto keyInfo = Formatting::formatForComboBox(key);
if (key.hasSecret()) {
const auto answer = KMessageBox::questionTwoActionsCancel(d->parentWidgetOrView(),
xi18nc("@info", "Is '%1' your own certificate?", keyInfo),
i18nc("@title:window", "Mark Own Certificate"),
KGuiItem(i18nc("@action:button", "Yes, it's mine")),
KGuiItem(i18nc("@action:button", "No, it's not mine")),
KStandardGuiItem::cancel());
switch (answer) {
case KMessageBox::ButtonCode::PrimaryAction: {
if (key.ownerTrust() < Key::Ultimate) {
d->startJob(Key::OwnerTrust::Ultimate);
}
return;
}
case KMessageBox::ButtonCode::SecondaryAction: {
if (key.ownerTrust() == Key::Ultimate) {
d->startJob(Key::OwnerTrust::Unknown);
return;
}
// else ask next question
break;
}
case KMessageBox::Cancel: {
d->canceled();
return;
}
default:; // cannot happen
}
}
if (key.ownerTrust() < Key::OwnerTrust::Full) {
const auto text = (DeVSCompliance::isCompliant() && DeVSCompliance::allSubkeysAreCompliant(key))
? xi18nc("@info %1: a certificate, %2: name of a compliance mode",
"<para>Do you want to grant '%1' the power to mark certificates as %2 for you?</para>"
"<para><emphasis>This means that the owner of this certificate properly checks fingerprints "
"and confirms the identities of others.</emphasis></para>",
keyInfo,
DeVSCompliance::name())
: xi18nc("@info %1: a certificate",
"<para>Do you want to grant '%1' the power to mark certificates as valid for you?</para>"
"<para><emphasis>This means that the owner of this certificate properly checks fingerprints "
"and confirms the identities of others.</emphasis></para>",
keyInfo);
const auto answer = KMessageBox::questionTwoActions(d->parentWidgetOrView(),
text,
i18nc("@title:window", "Grant Certification Power"),
KGuiItem(i18nc("@action:button", "Grant Power")),
KStandardGuiItem::cancel());
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
d->startJob(Key::OwnerTrust::Full);
} else {
d->canceled();
}
} else {
const auto text = (DeVSCompliance::isCompliant() && DeVSCompliance::allSubkeysAreCompliant(key))
? xi18nc("@info %1: a certificate, %2: name of a compliance mode",
"<para>The certificate '%1' is empowered to mark other certificates as %2 for you.</para>"
"<para>Do you want to revoke this power?</para>",
keyInfo,
DeVSCompliance::name())
: xi18nc("@info %1: a certificate",
"<para>The certificate '%1' is empowered to mark other certificates as valid for you.</para>"
"<para>Do you want to revoke this power?</para>",
keyInfo);
const auto answer = KMessageBox::questionTwoActions(d->parentWidgetOrView(),
text,
i18nc("@title:window", "Revoke Certification Power"),
KGuiItem(i18nc("@action:button", "Revoke Power")),
KStandardGuiItem::cancel());
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
d->startJob(Key::OwnerTrust::Unknown);
} else {
d->canceled();
}
}
}
void ChangeOwnerTrustCommand::Private::startJob(Key::OwnerTrust trust)
{
trustToSet = trust;
createJob();
Q_ASSERT(job);
if (const Error err = job->start(key(), trust)) {
showErrorDialog(err);
finished();
}
}
void ChangeOwnerTrustCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled())
;
else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void ChangeOwnerTrustCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
}
void ChangeOwnerTrustCommand::Private::createJob()
{
Q_ASSERT(!job);
ChangeOwnerTrustJob *const j = QGpgME::openpgp()->changeOwnerTrustJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &ChangeOwnerTrustJob::result, q, [this](const GpgME::Error &result) { slotResult(result); });
job = j;
}
void ChangeOwnerTrustCommand::Private::showErrorDialog(const Error &err)
{
const auto keyInfo = Formatting::formatForComboBox(key());
switch (trustToSet) {
case Key::OwnerTrust::Ultimate:
error(xi18nc("@info",
"<para>An error occurred while marking certificate '%1' as your certificate.</para>"
"<para><message>%2</message></para>",
keyInfo,
- QString::fromUtf8(err.asString())));
+ Formatting::errorAsString(err)));
break;
case Key::OwnerTrust::Full:
error(xi18nc("@info",
"<para>An error occurred while granting certification power to '%1'.</para>"
"<para><message>%2</message></para>",
keyInfo,
- QString::fromUtf8(err.asString())));
+ Formatting::errorAsString(err)));
break;
default:
error(xi18nc("@info",
"<para>An error occurred while revoking the certification power of '%1'.</para>"
"<para><message>%2</message></para>",
keyInfo,
- QString::fromUtf8(err.asString())));
+ Formatting::errorAsString(err)));
}
}
void ChangeOwnerTrustCommand::Private::showSuccessDialog()
{
auto updatedKey = key();
updatedKey.update();
KeyCache::mutableInstance()->insert(updatedKey);
const auto keyInfo = Formatting::formatForComboBox(updatedKey);
switch (updatedKey.ownerTrust()) {
case Key::OwnerTrust::Ultimate:
success(i18nc("@info", "Certificate '%1' was marked as your certificate.", keyInfo));
break;
case Key::OwnerTrust::Full:
success(i18nc("@info", "Certification power was granted to '%1'.", keyInfo));
break;
default:
success(i18nc("@info", "The certification power of '%1' was revoked.", keyInfo));
}
}
#undef d
#undef q
#include "moc_changeownertrustcommand.cpp"
diff --git a/src/commands/changepassphrasecommand.cpp b/src/commands/changepassphrasecommand.cpp
index e58fef554..97f0c6eb3 100644
--- a/src/commands/changepassphrasecommand.cpp
+++ b/src/commands/changepassphrasecommand.cpp
@@ -1,206 +1,206 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/changepassphrasecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "changepassphrasecommand.h"
#include "command_p.h"
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QGpgME/ChangePasswdJob>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <gpg-error.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
using namespace QGpgME;
class ChangePassphraseCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::ChangePassphraseCommand;
ChangePassphraseCommand *q_func() const
{
return static_cast<ChangePassphraseCommand *>(q);
}
public:
explicit Private(ChangePassphraseCommand *qq, KeyListController *c);
~Private() override;
void init();
private:
void slotResult(const Error &err);
private:
void createJob();
void startJob();
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::Key key;
QPointer<ChangePasswdJob> job;
};
ChangePassphraseCommand::Private *ChangePassphraseCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ChangePassphraseCommand::Private *ChangePassphraseCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ChangePassphraseCommand::Private::Private(ChangePassphraseCommand *qq, KeyListController *c)
: Command::Private(qq, c),
key(),
job()
{
}
ChangePassphraseCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
}
ChangePassphraseCommand::ChangePassphraseCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
ChangePassphraseCommand::ChangePassphraseCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
ChangePassphraseCommand::ChangePassphraseCommand(const GpgME::Key &key)
: Command(key, new Private(this, nullptr))
{
d->init();
}
void ChangePassphraseCommand::Private::init()
{
}
ChangePassphraseCommand::~ChangePassphraseCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void ChangePassphraseCommand::doStart()
{
const std::vector<Key> keys = d->keys();
if (keys.size() != 1 || !keys.front().hasSecret()) {
d->finished();
return;
}
d->key = keys.front();
d->createJob();
d->startJob();
}
void ChangePassphraseCommand::Private::startJob()
{
const Error err = job
? job->start(key)
: Error::fromCode(GPG_ERR_NOT_SUPPORTED)
;
if (err) {
showErrorDialog(err);
finished();
}
}
void ChangePassphraseCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled())
;
else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void ChangePassphraseCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG);
if (d->job) {
d->job->slotCancel();
}
}
void ChangePassphraseCommand::Private::createJob()
{
Q_ASSERT(!job);
const auto backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
if (!backend) {
return;
}
ChangePasswdJob *const j = backend->changePasswdJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &ChangePasswdJob::result, q, [this](const GpgME::Error &result) { slotResult(result); });
job = j;
}
void ChangePassphraseCommand::Private::showErrorDialog(const Error &err)
{
error(i18n("<p>An error occurred while trying to change "
"the passphrase for <b>%1</b>:</p><p>%2</p>",
Formatting::formatForComboBox(key),
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
i18n("Passphrase Change Error"));
}
void ChangePassphraseCommand::Private::showSuccessDialog()
{
information(i18n("Passphrase changed successfully."),
i18n("Passphrase Change Succeeded"));
}
#undef d
#undef q
#include "moc_changepassphrasecommand.cpp"
diff --git a/src/commands/changepincommand.cpp b/src/commands/changepincommand.cpp
index d93e1a788..2f16dcbea 100644
--- a/src/commands/changepincommand.cpp
+++ b/src/commands/changepincommand.cpp
@@ -1,221 +1,223 @@
/* commands/changepincommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "changepincommand.h"
#include "cardcommand_p.h"
#include "smartcard/netkeycard.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
+#include <Libkleo/Formatting>
+
#include <KLocalizedString>
#include <gpgme++/error.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
class ChangePinCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::ChangePinCommand;
ChangePinCommand *q_func() const
{
return static_cast<ChangePinCommand *>(q);
}
public:
explicit Private(ChangePinCommand *qq, const std::string &serialNumber, const std::string &appName, QWidget *p);
~Private() override;
void init();
private:
void slotResult(const Error &err);
private:
void changePin();
private:
std::string appName;
std::string keyRef;
ChangePinMode mode = NormalMode;
};
ChangePinCommand::Private *ChangePinCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ChangePinCommand::Private *ChangePinCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ChangePinCommand::Private::Private(ChangePinCommand *qq, const std::string &serialNumber, const std::string &appName_, QWidget *p)
: CardCommand::Private(qq, serialNumber, p)
, appName(appName_)
{
}
ChangePinCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::Private::~Private()";
}
ChangePinCommand::ChangePinCommand(const std::string &serialNumber, const std::string &appName, QWidget *p)
: CardCommand(new Private(this, serialNumber, appName, p))
{
d->init();
}
void ChangePinCommand::Private::init()
{
}
ChangePinCommand::~ChangePinCommand()
{
qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::~ChangePinCommand()";
}
void ChangePinCommand::setKeyRef(const std::string &keyRef)
{
d->keyRef = keyRef;
}
void ChangePinCommand::setMode(ChangePinMode mode)
{
d->mode = mode;
}
void ChangePinCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::doStart()";
d->changePin();
}
void ChangePinCommand::doCancel()
{
}
void ChangePinCommand::Private::changePin()
{
qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::changePin()";
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
QByteArrayList command;
command << "SCD PASSWD";
if (mode == ResetMode) {
command << "--reset";
} else if (mode == NullPinMode) {
command << "--nullpin";
}
command << QByteArray::fromStdString(keyRef);
ReaderStatus::mutableInstance()->startSimpleTransaction(card, command.join(' '), q, [this](const GpgME::Error &err) {
slotResult(err);
});
}
namespace {
static QString errorMessage(const std::string &keyRef, ChangePinCommand::ChangePinMode mode, const QString &errorText)
{
// see cmd_passwd() in gpg-card.c
if (keyRef == PIVCard::pukKeyRef()) {
return i18nc("@info", "Changing the PUK failed: %1", errorText);
}
if (keyRef == OpenPGPCard::resetCodeKeyRef()) {
return i18nc("@info", "Unblocking the PIN failed: %1", errorText);
}
if (keyRef == OpenPGPCard::adminPinKeyRef()) {
return i18nc("@info", "Changing the Admin PIN failed: %1", errorText);
}
if (keyRef == OpenPGPCard::resetCodeKeyRef() && mode == ChangePinCommand::ResetMode) {
return i18nc("@info", "Changing the Reset Code failed: %1", errorText);
}
if (keyRef == NetKeyCard::nksPinKeyRef()) {
if (mode == ChangePinCommand::NullPinMode) {
return i18nc("@info", "Setting the NKS PIN failed: %1", errorText);
} else {
return i18nc("@info", "Changing the NKS PIN failed: %1", errorText);
}
}
if (keyRef == NetKeyCard::sigGPinKeyRef()) {
if (mode == ChangePinCommand::NullPinMode) {
return i18nc("@info", "Setting the SigG PIN failed: %1", errorText);
} else {
return i18nc("@info", "Changing the SigG PIN failed: %1", errorText);
}
}
return i18nc("@info", "Changing the PIN failed: %1", errorText);
}
static QString successMessage(const std::string &keyRef, ChangePinCommand::ChangePinMode mode)
{
// see cmd_passwd() in gpg-card.c
if (keyRef == PIVCard::pukKeyRef()) {
return i18nc("@info", "PUK successfully changed.");
}
if (keyRef == OpenPGPCard::resetCodeKeyRef()) {
return i18nc("@info", "Unblocked and set a new PIN successfully.");
}
if (keyRef == OpenPGPCard::adminPinKeyRef()) {
return i18nc("@info", "Admin PIN changed successfully.");
}
if (keyRef == OpenPGPCard::resetCodeKeyRef() && mode == ChangePinCommand::ResetMode) {
return i18nc("@info", "Reset Code changed successfully.");
}
if (keyRef == NetKeyCard::nksPinKeyRef()) {
if (mode == ChangePinCommand::NullPinMode) {
return i18nc("@info", "NKS PIN set successfully.");
} else {
return i18nc("@info", "NKS PIN changed successfully.");
}
}
if (keyRef == NetKeyCard::sigGPinKeyRef()) {
if (mode == ChangePinCommand::NullPinMode) {
return i18nc("@info", "SigG PIN set successfully.");
} else {
return i18nc("@info", "SigG PIN changed successfully.");
}
}
return i18nc("@info", "PIN changed successfully.");
}
}
void ChangePinCommand::Private::slotResult(const GpgME::Error& err)
{
qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::slotResult():"
- << err.asString() << "(" << err.code() << ")";
+ << Formatting::errorAsString(err) << "(" << err.code() << ")";
if (err) {
- error(errorMessage(keyRef, mode, QString::fromLatin1(err.asString())));
+ error(errorMessage(keyRef, mode, Formatting::errorAsString(err)));
} else if (!err.isCanceled()) {
success(successMessage(keyRef, mode));
ReaderStatus::mutableInstance()->updateStatus();
}
finished();
}
#undef d
#undef q
#include "moc_changepincommand.cpp"
diff --git a/src/commands/createcsrforcardkeycommand.cpp b/src/commands/createcsrforcardkeycommand.cpp
index e2891bceb..7ad6a1810 100644
--- a/src/commands/createcsrforcardkeycommand.cpp
+++ b/src/commands/createcsrforcardkeycommand.cpp
@@ -1,296 +1,296 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/createcsrforcardkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "createcsrforcardkeycommand.h"
#include "cardcommand_p.h"
#include "dialogs/createcsrforcardkeydialog.h"
#include "smartcard/netkeycard.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "utils/filedialog.h"
#include "utils/keyparameters.h"
#include "utils/keyusage.h"
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <QDateTime>
#include <QFile>
#include <KLocalizedString>
#include <QUrl>
#include <QGpgME/Protocol>
#include <QGpgME/KeyGenerationJob>
#include <gpgme++/context.h>
#include <gpgme++/engineinfo.h>
#include <gpgme++/keygenerationresult.h>
#include <gpgme.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace Kleo::SmartCard;
using namespace GpgME;
using namespace QGpgME;
class CreateCSRForCardKeyCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::CreateCSRForCardKeyCommand;
CreateCSRForCardKeyCommand *q_func() const
{
return static_cast<CreateCSRForCardKeyCommand *>(q);
}
public:
explicit Private(CreateCSRForCardKeyCommand *qq,
const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent);
~Private() override;
private:
void start();
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const KeyGenerationResult &result, const QByteArray &request);
QUrl saveRequest(const QByteArray &request);
void ensureDialogCreated();
private:
std::string appName;
std::string keyRef;
KeyUsage keyUsage;
QPointer<CreateCSRForCardKeyDialog> dialog;
};
CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
CreateCSRForCardKeyCommand::Private::Private(CreateCSRForCardKeyCommand *qq,
const std::string &keyRef_, const std::string &serialNumber, const std::string &appName_, QWidget *parent)
: CardCommand::Private(qq, serialNumber, parent)
, appName(appName_)
, keyRef(keyRef_)
{
}
CreateCSRForCardKeyCommand::Private::~Private()
{
}
namespace
{
KeyUsage getKeyUsage(const KeyPairInfo &keyInfo)
{
// note: gpgsm does not support creating CSRs for authentication certificates
KeyUsage usage;
if (keyInfo.canCertify()) {
usage.setCanCertify(true);
}
if (keyInfo.canSign()) {
usage.setCanSign(true);
}
if (keyInfo.canEncrypt()) {
usage.setCanEncrypt(true);
}
return usage;
}
}
void CreateCSRForCardKeyCommand::Private::start()
{
if (appName != NetKeyCard::AppName && appName != OpenPGPCard::AppName && appName != PIVCard::AppName) {
qCWarning(KLEOPATRA_LOG) << "CreateCSRForCardKeyCommand does not support card application" << QString::fromStdString(appName);
finished();
return;
}
const auto card = ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const KeyPairInfo &keyInfo = card->keyInfo(keyRef);
keyUsage = getKeyUsage(keyInfo);
ensureDialogCreated();
dialog->setWindowTitle(i18n("Certificate Details"));
if (!card->cardHolder().isEmpty()) {
dialog->setName(card->cardHolder());
}
dialog->show();
}
void CreateCSRForCardKeyCommand::Private::slotDialogAccepted()
{
const Error err = ReaderStatus::switchCardAndApp(serialNumber(), appName);
if (err) {
finished();
return;
}
const auto backend = smime();
if (!backend) {
finished();
return;
}
KeyGenerationJob *const job = backend->keyGenerationJob();
if (!job) {
finished();
return;
}
Job::context(job)->setArmor(true);
connect(job, &KeyGenerationJob::result, q, [this](const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData) {
slotResult(result, pubKeyData);
});
KeyParameters keyParameters(KeyParameters::CMS);
keyParameters.setCardKeyRef(QString::fromStdString(keyRef));
keyParameters.setKeyUsage(keyUsage);
keyParameters.setDN(dialog->dn());
keyParameters.setEmail(dialog->email());
if (const Error err = job->start(keyParameters.toString())) {
- error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", QString::fromUtf8(err.asString())));
+ error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", Formatting::errorAsString(err)));
finished();
}
}
void CreateCSRForCardKeyCommand::Private::slotDialogRejected()
{
canceled();
}
void CreateCSRForCardKeyCommand::Private::slotResult(const KeyGenerationResult &result, const QByteArray &request)
{
if (result.error().isCanceled()) {
// do nothing
} else if (result.error()) {
- error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", QString::fromUtf8(result.error().asString())));
+ error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", Formatting::errorAsString(result.error())));
} else {
const QUrl url = saveRequest(request);
if (!url.isEmpty()) {
information(xi18nc("@info", "<para>Successfully wrote request to <filename>%1</filename>.</para>"
"<para>You should now send the request to the Certification Authority (CA).</para>",
url.toLocalFile()),
i18nc("@title", "Request Saved"));
}
}
finished();
}
namespace
{
struct SaveToFileResult {
QUrl url;
QString errorMessage;
};
SaveToFileResult saveRequestToFile(const QString &filename, const QByteArray &request, QIODevice::OpenMode mode)
{
QFile file(filename);
if (file.open(mode)) {
const auto bytesWritten = file.write(request);
if (bytesWritten < request.size()) {
return { QUrl(), file.errorString() };
}
return { QUrl::fromLocalFile(file.fileName()), QString() };
}
return { QUrl(), file.errorString() };
}
}
QUrl CreateCSRForCardKeyCommand::Private::saveRequest(const QByteArray &request)
{
const QString proposedFilename = QLatin1String("request_%1.p10").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_HHmmss")));
while (true) {
const QString filePath = FileDialog::getSaveFileNameEx(
parentWidgetOrView(), i18nc("@title", "Save Request"), QStringLiteral("save_csr"), proposedFilename, i18n("PKCS#10 Requests (*.p10)"));
if (filePath.isEmpty()) {
// user canceled the dialog
return QUrl();
}
const auto result = saveRequestToFile(filePath, request, QIODevice::NewOnly);
if (result.url.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "Writing request to file" << filePath << "failed:" << result.errorMessage;
error(xi18nc("@info", "<para>Saving the request failed.</para><para><message>%1</message></para>", result.errorMessage),
i18nc("@title", "Error Saving Request"));
} else {
return result.url;
}
}
}
void CreateCSRForCardKeyCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new CreateCSRForCardKeyDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
CreateCSRForCardKeyCommand::CreateCSRForCardKeyCommand(const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent)
: CardCommand(new Private(this, keyRef, serialNumber, appName, parent))
{
}
CreateCSRForCardKeyCommand::~CreateCSRForCardKeyCommand()
{
}
void CreateCSRForCardKeyCommand::doStart()
{
d->start();
}
void CreateCSRForCardKeyCommand::doCancel()
{
}
#undef d
#undef q
#include "moc_createcsrforcardkeycommand.cpp"
diff --git a/src/commands/createopenpgpkeyfromcardkeyscommand.cpp b/src/commands/createopenpgpkeyfromcardkeyscommand.cpp
index 0bcab5b7e..dfe299038 100644
--- a/src/commands/createopenpgpkeyfromcardkeyscommand.cpp
+++ b/src/commands/createopenpgpkeyfromcardkeyscommand.cpp
@@ -1,218 +1,218 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/createopenpgpkeyfromcardkeyscommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "createopenpgpkeyfromcardkeyscommand.h"
#include "cardcommand_p.h"
#include "dialogs/adduseriddialog.h"
#include "smartcard/netkeycard.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <QGpgME/Protocol>
#include <QGpgME/QuickJob>
#include <gpgme++/context.h>
#include <gpgme++/engineinfo.h>
#include <gpgme.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
using namespace QGpgME;
class CreateOpenPGPKeyFromCardKeysCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::CreateOpenPGPKeyFromCardKeysCommand;
CreateOpenPGPKeyFromCardKeysCommand *q_func() const
{
return static_cast<CreateOpenPGPKeyFromCardKeysCommand *>(q);
}
public:
explicit Private(CreateOpenPGPKeyFromCardKeysCommand *qq, const std::string &serialNumber, const std::string &appName, QWidget *parent);
~Private() override;
private:
void start();
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
void ensureDialogCreated();
private:
std::string appName;
QPointer<AddUserIDDialog> dialog;
};
CreateOpenPGPKeyFromCardKeysCommand::Private *CreateOpenPGPKeyFromCardKeysCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CreateOpenPGPKeyFromCardKeysCommand::Private *CreateOpenPGPKeyFromCardKeysCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
CreateOpenPGPKeyFromCardKeysCommand::Private::Private(CreateOpenPGPKeyFromCardKeysCommand *qq, const std::string &serialNumber, const std::string &appName_, QWidget *parent)
: CardCommand::Private(qq, serialNumber, parent)
, appName(appName_)
{
}
CreateOpenPGPKeyFromCardKeysCommand::Private::~Private()
{
}
void CreateOpenPGPKeyFromCardKeysCommand::Private::start()
{
if (appName != NetKeyCard::AppName && appName != OpenPGPCard::AppName && appName != PIVCard::AppName) {
qCWarning(KLEOPATRA_LOG) << "CreateOpenPGPKeyFromCardKeysCommand does not support card application" << QString::fromStdString(appName);
finished();
return;
}
const auto card = ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const auto signingKeyGrip = card->keyInfo(card->signingKeyRef()).grip;
const Key signingKey = KeyCache::instance()->findSubkeyByKeyGrip(signingKeyGrip, OpenPGP).parent();
if (!signingKey.isNull()) {
const QString message = i18nc("@info",
"<p>There is already an OpenPGP key corresponding to the signing key on this card:</p><p>%1</p>"
"<p>Do you still want to create an OpenPGP key for the card keys?</p>",
Formatting::summaryLine(signingKey));
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message,
i18nc("@title:window", "Create OpenPGP Key"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify);
if (choice != KMessageBox::Continue) {
finished();
return;
}
}
ensureDialogCreated();
dialog->setWindowTitle(i18n("Enter User ID"));
dialog->setName(card->cardHolder());
dialog->show();
}
void CreateOpenPGPKeyFromCardKeysCommand::Private::slotDialogAccepted()
{
const Error err = ReaderStatus::switchCardAndApp(serialNumber(), appName);
if (err) {
finished();
return;
}
const auto backend = openpgp();
if (!backend) {
finished();
return;
}
QuickJob *const job = backend->quickJob();
if (!job) {
finished();
return;
}
connect(job, &QGpgME::QuickJob::result, q, [this](const GpgME::Error &error) { slotResult(error); });
const QString userID = Formatting::prettyNameAndEMail(OpenPGP, QString(), dialog->name(), dialog->email());
const QDateTime expires = QDateTime();
const unsigned int flags = GPGME_CREATE_FORCE;
job->startCreate(userID, "card", expires, Key(), flags);
}
void CreateOpenPGPKeyFromCardKeysCommand::Private::slotDialogRejected()
{
canceled();
}
void CreateOpenPGPKeyFromCardKeysCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
// do nothing
} else if (err) {
- error(i18nc("@info", "Creating an OpenPGP key from the card keys failed: %1", QString::fromUtf8(err.asString())));
+ error(i18nc("@info", "Creating an OpenPGP key from the card keys failed: %1", Formatting::errorAsString(err)));
} else {
success(i18nc("@info", "Successfully generated an OpenPGP key from the card keys."));
}
finished();
}
void CreateOpenPGPKeyFromCardKeysCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new AddUserIDDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
CreateOpenPGPKeyFromCardKeysCommand::CreateOpenPGPKeyFromCardKeysCommand(const std::string &serialNumber, const std::string &appName, QWidget *parent)
: CardCommand(new Private(this, serialNumber, appName, parent))
{
}
CreateOpenPGPKeyFromCardKeysCommand::~CreateOpenPGPKeyFromCardKeysCommand()
{
}
// static
bool CreateOpenPGPKeyFromCardKeysCommand::isSupported()
{
return !(engineInfo(GpgEngine).engineVersion() < "2.3.0");
}
void CreateOpenPGPKeyFromCardKeysCommand::doStart()
{
d->start();
}
void CreateOpenPGPKeyFromCardKeysCommand::doCancel()
{
}
#undef d
#undef q
#include "moc_createopenpgpkeyfromcardkeyscommand.cpp"
diff --git a/src/commands/deletecertificatescommand.cpp b/src/commands/deletecertificatescommand.cpp
index 9a346bae7..56cdb7f14 100644
--- a/src/commands/deletecertificatescommand.cpp
+++ b/src/commands/deletecertificatescommand.cpp
@@ -1,395 +1,396 @@
/* -*- mode: c++; c-basic-offset:4 -*-
deleteCertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "deletecertificatescommand.h"
#include "command_p.h"
#include <dialogs/deletecertificatesdialog.h>
+#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/Predicates>
#include <Libkleo/Stl_Util>
#include <QGpgME/Protocol>
#include <QGpgME/MultiDeleteJob>
#include <QGpgME/DeleteJob>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QPointer>
#include <QAbstractItemView>
#include <algorithm>
#include <vector>
#include <KLazyLocalizedString>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace QGpgME;
class DeleteCertificatesCommand::Private : public Command::Private
{
friend class ::Kleo::DeleteCertificatesCommand;
DeleteCertificatesCommand *q_func() const
{
return static_cast<DeleteCertificatesCommand *>(q);
}
public:
explicit Private(DeleteCertificatesCommand *qq, KeyListController *c);
~Private() override;
void startDeleteJob(GpgME::Protocol protocol);
void cancelJobs();
void pgpDeleteResult(const GpgME::Error &);
void cmsDeleteResult(const GpgME::Error &);
void showErrorsAndFinish();
bool canDelete(GpgME::Protocol proto) const
{
if (const auto cbp = (proto == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime()))
if (DeleteJob *const job = cbp->deleteJob()) {
job->slotCancel();
return true;
}
return false;
}
void ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new DeleteCertificatesDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(i18nc("@title:window", "Delete Certificates"));
connect(dialog, &QDialog::accepted, q_func(), [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q_func(), [this]() { slotDialogRejected(); });
}
void ensureDialogShown()
{
if (dialog) {
dialog->show();
}
}
void slotDialogAccepted();
void slotDialogRejected()
{
canceled();
}
private:
QPointer<DeleteCertificatesDialog> dialog;
QPointer<MultiDeleteJob> cmsJob, pgpJob;
GpgME::Error cmsError, pgpError;
std::vector<Key> cmsKeys, pgpKeys;
};
DeleteCertificatesCommand::Private *DeleteCertificatesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const DeleteCertificatesCommand::Private *DeleteCertificatesCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
DeleteCertificatesCommand::Private::Private(DeleteCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c)
{
}
DeleteCertificatesCommand::Private::~Private() {}
DeleteCertificatesCommand::DeleteCertificatesCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
DeleteCertificatesCommand::DeleteCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
DeleteCertificatesCommand::~DeleteCertificatesCommand() {}
namespace
{
enum Action { Nothing = 0, Failure = 1, ClearCMS = 2, ClearPGP = 4 };
// const unsigned int errorCase =
// openpgp.empty() << 3U | d->canDelete( OpenPGP ) << 2U |
// cms.empty() << 1U | d->canDelete( CMS ) << 0U ;
static const struct {
const KLazyLocalizedString text;
Action actions;
} deletionErrorCases[16] = {
// if havePGP
// if cantPGP
// if haveCMS
{kli18n("Neither the OpenPGP nor the CMS "
"backends support certificate deletion.\n"
"Check your installation."),
Failure}, // cantCMS
{kli18n("The OpenPGP backend does not support "
"certificate deletion.\n"
"Check your installation.\n"
"Only the selected CMS certificates "
"will be deleted."),
ClearPGP}, // canCMS
// if !haveCMS
{kli18n("The OpenPGP backend does not support "
"certificate deletion.\n"
"Check your installation."),
Failure},
{kli18n("The OpenPGP backend does not support "
"certificate deletion.\n"
"Check your installation."),
Failure},
// if canPGP
// if haveCMS
{kli18n("The CMS backend does not support "
"certificate deletion.\n"
"Check your installation.\n"
"Only the selected OpenPGP certificates "
"will be deleted."),
ClearCMS}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !haveCMS
{KLazyLocalizedString(), Nothing}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !havePGP
// if cantPGP
// if haveCMS
{kli18n("The CMS backend does not support "
"certificate deletion.\n"
"Check your installation."),
Failure}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !haveCMS
{KLazyLocalizedString(), Nothing}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if canPGP
// if haveCMS
{kli18n("The CMS backend does not support "
"certificate deletion.\n"
"Check your installation."),
Failure}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !haveCMS
{KLazyLocalizedString(), Nothing}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
};
} // anon namespace
void DeleteCertificatesCommand::doStart()
{
std::vector<Key> selected = d->keys();
if (selected.empty()) {
d->finished();
return;
}
std::sort(selected.begin(), selected.end(), _detail::ByFingerprint<std::less>());
// Calculate the closure of the selected keys (those that need to
// be deleted with them, though not selected themselves):
std::vector<Key> toBeDeleted = KeyCache::instance()->findSubjects(selected);
std::sort(toBeDeleted.begin(), toBeDeleted.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> unselected;
unselected.reserve(toBeDeleted.size());
std::set_difference(toBeDeleted.begin(), toBeDeleted.end(),
selected.begin(), selected.end(),
std::back_inserter(unselected),
_detail::ByFingerprint<std::less>());
d->ensureDialogCreated();
d->dialog->setSelectedKeys(selected);
d->dialog->setUnselectedKeys(unselected);
d->ensureDialogShown();
}
void DeleteCertificatesCommand::Private::slotDialogAccepted()
{
std::vector<Key> keys = dialog->keys();
Q_ASSERT(!keys.empty());
auto pgpBegin = keys.begin();
auto pgpEnd = std::stable_partition(pgpBegin, keys.end(),
[](const GpgME::Key &key) {
return key.protocol() != GpgME::CMS;
});
auto cmsBegin = pgpEnd;
auto cmsEnd = keys.end();
std::vector<Key> openpgp(pgpBegin, pgpEnd);
std::vector<Key> cms(cmsBegin, cmsEnd);
const unsigned int errorCase =
openpgp.empty() << 3U | canDelete(OpenPGP) << 2U |
cms.empty() << 1U | canDelete(CMS) << 0U;
if (const unsigned int actions = deletionErrorCases[errorCase].actions) {
information(KLocalizedString(deletionErrorCases[errorCase].text).toString(),
(actions & Failure)
? i18n("Certificate Deletion Failed")
: i18n("Certificate Deletion Problem"));
if (actions & ClearCMS) {
cms.clear();
}
if (actions & ClearPGP) {
openpgp.clear();
}
if (actions & Failure) {
canceled();
return;
}
}
Q_ASSERT(!openpgp.empty() || !cms.empty());
pgpKeys.swap(openpgp);
cmsKeys.swap(cms);
if (!pgpKeys.empty()) {
startDeleteJob(GpgME::OpenPGP);
}
if (!cmsKeys.empty()) {
startDeleteJob(GpgME::CMS);
}
if ((pgpKeys.empty() || pgpError.code()) &&
(cmsKeys.empty() || cmsError.code())) {
showErrorsAndFinish();
}
}
void DeleteCertificatesCommand::Private::startDeleteJob(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != GpgME::UnknownProtocol);
const std::vector<Key> &keys = protocol == CMS ? cmsKeys : pgpKeys;
const auto backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr<MultiDeleteJob> job(new MultiDeleteJob(backend));
connect(job.get(), &QGpgME::MultiDeleteJob::result, q_func(), [this, protocol](const GpgME::Error &result) {
if (protocol == CMS) {
cmsDeleteResult(result);
} else {
pgpDeleteResult(result);
}
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(job.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
if (const Error err = job->start(keys, true /*allowSecretKeyDeletion*/)) {
(protocol == CMS ? cmsError : pgpError) = err;
} else {
(protocol == CMS ? cmsJob : pgpJob) = job.release();
}
}
void DeleteCertificatesCommand::Private::showErrorsAndFinish()
{
Q_ASSERT(!pgpJob); Q_ASSERT(!cmsJob);
if (pgpError || cmsError) {
QString pgpErrorString;
if (pgpError) {
- pgpErrorString = i18n("OpenPGP backend: %1", QString::fromLocal8Bit(pgpError.asString()));
+ pgpErrorString = i18n("OpenPGP backend: %1", Formatting::errorAsString(pgpError));
}
QString cmsErrorString;
if (cmsError) {
- cmsErrorString = i18n("CMS backend: %1", QString::fromLocal8Bit(cmsError.asString()));
+ cmsErrorString = i18n("CMS backend: %1", Formatting::errorAsString(cmsError));
}
const QString msg = i18n("<qt><p>An error occurred while trying to delete "
"the certificate:</p>"
"<p><b>%1</b></p></qt>",
pgpError ? cmsError ? pgpErrorString + QLatin1String("</br>") + cmsErrorString : pgpErrorString : cmsErrorString);
error(msg, i18n("Certificate Deletion Failed"));
} else if (!pgpError.isCanceled() && !cmsError.isCanceled()) {
std::vector<Key> keys = pgpKeys;
keys.insert(keys.end(), cmsKeys.begin(), cmsKeys.end());
KeyCache::mutableInstance()->remove(keys);
}
finished();
}
void DeleteCertificatesCommand::doCancel()
{
d->cancelJobs();
}
void DeleteCertificatesCommand::Private::pgpDeleteResult(const Error &err)
{
pgpError = err;
pgpJob = nullptr;
if (!cmsJob) {
showErrorsAndFinish();
}
}
void DeleteCertificatesCommand::Private::cmsDeleteResult(const Error &err)
{
cmsError = err;
cmsJob = nullptr;
if (!pgpJob) {
showErrorsAndFinish();
}
}
void DeleteCertificatesCommand::Private::cancelJobs()
{
if (cmsJob) {
cmsJob->slotCancel();
}
if (pgpJob) {
pgpJob->slotCancel();
}
}
#undef d
#undef q
#include "moc_deletecertificatescommand.cpp"
diff --git a/src/commands/exportcertificatecommand.cpp b/src/commands/exportcertificatecommand.cpp
index c8365ab09..ca6af841d 100644
--- a/src/commands/exportcertificatecommand.cpp
+++ b/src/commands/exportcertificatecommand.cpp
@@ -1,369 +1,369 @@
/* -*- mode: c++; c-basic-offset:4 -*-
exportcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportcertificatecommand.h"
#include "fileoperationspreferences.h"
#include "command_p.h"
#include <utils/applicationstate.h>
#include <utils/filedialog.h>
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QGpgME/ExportJob>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QSaveFile>
#include <QMap>
#include <QPointer>
#include <QFileInfo>
#include <algorithm>
#include <vector>
using namespace Kleo;
using namespace GpgME;
using namespace QGpgME;
class ExportCertificateCommand::Private : public Command::Private
{
friend class ::ExportCertificateCommand;
ExportCertificateCommand *q_func() const
{
return static_cast<ExportCertificateCommand *>(q);
}
public:
explicit Private(ExportCertificateCommand *qq, KeyListController *c);
~Private() override;
void startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys);
void cancelJobs();
void exportResult(const GpgME::Error &, const QByteArray &);
void showError(const GpgME::Error &error);
bool requestFileNames(GpgME::Protocol prot);
void finishedIfLastJob();
private:
QMap<GpgME::Protocol, QString> fileNames;
uint jobsPending = 0;
QMap<QObject *, QString> outFileForSender;
QPointer<ExportJob> cmsJob;
QPointer<ExportJob> pgpJob;
};
ExportCertificateCommand::Private *ExportCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportCertificateCommand::Private *ExportCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportCertificateCommand::Private::Private(ExportCertificateCommand *qq, KeyListController *c)
: Command::Private(qq, c)
{
}
ExportCertificateCommand::Private::~Private() {}
ExportCertificateCommand::ExportCertificateCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(const Key &key)
: Command(key, new Private(this, nullptr))
{
}
ExportCertificateCommand::~ExportCertificateCommand() {}
void ExportCertificateCommand::setOpenPGPFileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[OpenPGP] = fileName;
}
}
QString ExportCertificateCommand::openPGPFileName() const
{
return d->fileNames[OpenPGP];
}
void ExportCertificateCommand::setX509FileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[CMS] = fileName;
}
}
QString ExportCertificateCommand::x509FileName() const
{
return d->fileNames[CMS];
}
void ExportCertificateCommand::doStart()
{
std::vector<Key> keys = d->keys();
if (keys.empty()) {
return;
}
const auto firstCms = std::partition(keys.begin(), keys.end(),
[](const GpgME::Key &key) {
return key.protocol() != GpgME::CMS;
});
std::vector<Key> openpgp, cms;
std::copy(keys.begin(), firstCms, std::back_inserter(openpgp));
std::copy(firstCms, keys.end(), std::back_inserter(cms));
Q_ASSERT(!openpgp.empty() || !cms.empty());
const bool haveBoth = !cms.empty() && !openpgp.empty();
const GpgME::Protocol prot = haveBoth ? UnknownProtocol : (!cms.empty() ? CMS : OpenPGP);
if (!d->requestFileNames(prot)) {
Q_EMIT canceled();
d->finished();
} else {
if (!openpgp.empty()) {
d->startExportJob(GpgME::OpenPGP, openpgp);
}
if (!cms.empty()) {
d->startExportJob(GpgME::CMS, cms);
}
}
}
bool ExportCertificateCommand::Private::requestFileNames(GpgME::Protocol protocol)
{
if (protocol == UnknownProtocol) {
if (!fileNames[GpgME::OpenPGP].isEmpty() && !fileNames[GpgME::CMS].isEmpty()) {
return true;
}
/* Unknown protocol ask for first PGP Export file name */
if (fileNames[GpgME::OpenPGP].isEmpty() && !requestFileNames(GpgME::OpenPGP)) {
return false;
}
/* And then for CMS */
return requestFileNames(GpgME::CMS);
}
if (!fileNames[protocol].isEmpty()) {
return true;
}
const auto lastDir = ApplicationState::lastUsedExportDirectory();
QString proposedFileName = lastDir + QLatin1Char('/');
if (keys().size() == 1) {
const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt();
const auto key = keys().front();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
/* Not translated so it's better to use in tutorials etc. */
proposedFileName += QStringLiteral("%1_%2_public.%3").arg(name).arg(
Formatting::prettyKeyID(key.shortKeyID())).arg(
QString::fromLatin1(outputFileExtension(protocol == OpenPGP
? Class::OpenPGP | Class::Ascii | Class::Certificate
: Class::CMS | Class::Ascii | Class::Certificate, usePGPFileExt)));
}
if (protocol == GpgME::CMS) {
if (!fileNames[GpgME::OpenPGP].isEmpty()) {
/* If the user has already selected a PGP file name then use that as basis
* for a proposal for the S/MIME file. */
proposedFileName = fileNames[GpgME::OpenPGP];
const int idx = proposedFileName.size() - 4;
if (proposedFileName.endsWith(QLatin1String(".asc"))) {
proposedFileName.replace(idx, 4, QLatin1String(".pem"));
}
if (proposedFileName.endsWith(QLatin1String(".gpg")) || proposedFileName.endsWith(QLatin1String(".pgp"))) {
proposedFileName.replace(idx, 4, QLatin1String(".der"));
}
}
}
if (proposedFileName.isEmpty()) {
proposedFileName = lastDir;
proposedFileName += i18nc("A generic filename for exported certificates", "certificates");
proposedFileName += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
auto fname = FileDialog::getSaveFileNameEx(parentWidgetOrView(),
i18nc("1 is protocol", "Export %1 Certificates", Formatting::displayName(protocol)),
QStringLiteral("imp"),
proposedFileName,
protocol == GpgME::OpenPGP
? i18n("OpenPGP Certificates") + QLatin1String(" (*.asc *.gpg *.pgp)")
: i18n("S/MIME Certificates") + QLatin1String(" (*.pem *.der)"));
if (!fname.isEmpty() && protocol == GpgME::CMS && fileNames[GpgME::OpenPGP] == fname) {
KMessageBox::error(parentWidgetOrView(),
i18n("You have to select different filenames for different protocols."),
i18n("Export Error"));
return false;
}
const QFileInfo fi(fname);
if (fi.suffix().isEmpty()) {
fname += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
fileNames[protocol] = fname;
ApplicationState::setLastUsedExportDirectory(fi.absolutePath());
return !fname.isEmpty();
}
void ExportCertificateCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys)
{
Q_ASSERT(protocol != GpgME::UnknownProtocol);
const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
const QString fileName = fileNames[protocol];
const bool binary = protocol == GpgME::OpenPGP
? fileName.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive)
: fileName.endsWith(QLatin1String(".der"), Qt::CaseInsensitive);
std::unique_ptr<ExportJob> job(backend->publicKeyExportJob(!binary));
Q_ASSERT(job.get());
connect(job.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &result, const QByteArray &keyData) {
exportResult(result, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(job.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
QStringList fingerprints;
fingerprints.reserve(keys.size());
for (const Key &i : keys) {
fingerprints << QLatin1String(i.primaryFingerprint());
}
const GpgME::Error err = job->start(fingerprints);
if (err) {
showError(err);
finished();
return;
}
Q_EMIT q->info(i18n("Exporting certificates..."));
++jobsPending;
const QPointer<ExportJob> exportJob(job.release());
outFileForSender[exportJob.data()] = fileName;
(protocol == CMS ? cmsJob : pgpJob) = exportJob;
}
void ExportCertificateCommand::Private::showError(const GpgME::Error &err)
{
Q_ASSERT(err);
const QString msg = i18n("<qt><p>An error occurred while trying to export "
"the certificate:</p>"
"<p><b>%1</b></p></qt>",
- QString::fromLocal8Bit(err.asString()));
+ Formatting::errorAsString(err));
error(msg, i18n("Certificate Export Failed"));
}
void ExportCertificateCommand::doCancel()
{
d->cancelJobs();
}
void ExportCertificateCommand::Private::finishedIfLastJob()
{
if (jobsPending <= 0) {
finished();
}
}
static bool write_complete(QIODevice &iod, const QByteArray &data)
{
qint64 total = 0;
qint64 toWrite = data.size();
while (total < toWrite) {
const qint64 written = iod.write(data.data() + total, toWrite);
if (written < 0) {
return false;
}
total += written;
toWrite -= written;
}
return true;
}
void ExportCertificateCommand::Private::exportResult(const GpgME::Error &err, const QByteArray &data)
{
Q_ASSERT(jobsPending > 0);
--jobsPending;
Q_ASSERT(outFileForSender.contains(q->sender()));
const QString outFile = outFileForSender[q->sender()];
if (err) {
showError(err);
finishedIfLastJob();
return;
}
QSaveFile savefile(outFile);
//TODO: use KIO
const QString writeErrorMsg = i18n("Could not write to file %1.", outFile);
const QString errorCaption = i18n("Certificate Export Failed");
if (!savefile.open(QIODevice::WriteOnly)) {
error(writeErrorMsg, errorCaption);
finishedIfLastJob();
return;
}
if (!write_complete(savefile, data) ||
!savefile.commit()) {
error(writeErrorMsg, errorCaption);
}
finishedIfLastJob();
}
void ExportCertificateCommand::Private::cancelJobs()
{
if (cmsJob) {
cmsJob->slotCancel();
}
if (pgpJob) {
pgpJob->slotCancel();
}
}
#undef d
#undef q
#include "moc_exportcertificatecommand.cpp"
diff --git a/src/commands/exportgroupscommand.cpp b/src/commands/exportgroupscommand.cpp
index c63fdcc8b..6aa94a37f 100644
--- a/src/commands/exportgroupscommand.cpp
+++ b/src/commands/exportgroupscommand.cpp
@@ -1,302 +1,303 @@
/* -*- mode: c++; c-basic-offset:4 -*-
exportgroupscommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportgroupscommand.h"
#include "command_p.h"
#include <utils/applicationstate.h>
#include "utils/filedialog.h"
#include <Libkleo/Algorithm>
+#include <Libkleo/Formatting>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyGroupImportExport>
#include <Libkleo/KeyHelpers>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/Protocol>
#include <QGpgME/ExportJob>
#include <QFileInfo>
#include <QStandardPaths>
#include <memory>
#include <vector>
using namespace Kleo;
using namespace GpgME;
using namespace QGpgME;
namespace
{
static const QString certificateGroupFileExtension{QLatin1String{".kgrp"}};
QString proposeFilename(const std::vector<KeyGroup> &groups)
{
QString filename;
filename = ApplicationState::lastUsedExportDirectory() + QLatin1Char{'/'};
if (groups.size() == 1) {
filename += groups.front().name().replace(QLatin1Char{'/'}, QLatin1Char{'_'});
} else {
filename += i18nc("A generic filename for exported certificate groups", "certificate groups");
}
return filename + certificateGroupFileExtension;
}
QString requestFilename(QWidget *parent, const std::vector<KeyGroup> &groups)
{
const QString proposedFilename = proposeFilename(groups);
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18ncp("@title:window", "Export Certificate Group", "Export Certificate Groups", groups.size()),
QStringLiteral("imp"),
proposedFilename,
i18nc("filename filter like Certificate Groups (*.foo)", "Certificate Groups (*%1)", certificateGroupFileExtension));
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += certificateGroupFileExtension;
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
}
class ExportGroupsCommand::Private : public Command::Private
{
friend class ::ExportGroupsCommand;
ExportGroupsCommand *q_func() const
{
return static_cast<ExportGroupsCommand *>(q);
}
public:
explicit Private(ExportGroupsCommand *qq);
~Private() override;
void start();
bool exportGroups();
bool startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys);
void onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData);
void cancelJobs();
void showError(const GpgME::Error &err);
void finishedIfLastJob(const QGpgME::Job *job);
private:
std::vector<KeyGroup> groups;
QString filename;
std::vector<QPointer<QGpgME::Job>> exportJobs;
};
ExportGroupsCommand::Private *ExportGroupsCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportGroupsCommand::Private *ExportGroupsCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportGroupsCommand::Private::Private(ExportGroupsCommand *qq)
: Command::Private(qq)
{
}
ExportGroupsCommand::Private::~Private() = default;
void ExportGroupsCommand::Private::start()
{
if (groups.empty()) {
finished();
return;
}
filename = requestFilename(parentWidgetOrView(), groups);
if (filename.isEmpty()) {
canceled();
return;
}
const auto groupKeys = std::accumulate(std::begin(groups), std::end(groups),
KeyGroup::Keys{},
[](auto allKeys, const auto &group) {
const auto keys = group.keys();
allKeys.insert(std::begin(keys), std::end(keys));
return allKeys;
});
std::vector<Key> openpgpKeys;
std::vector<Key> cmsKeys;
std::partition_copy(std::begin(groupKeys), std::end(groupKeys),
std::back_inserter(openpgpKeys),
std::back_inserter(cmsKeys),
[](const GpgME::Key &key) {
return key.protocol() == GpgME::OpenPGP;
});
// remove/overwrite existing file
if (QFile::exists(filename) && !QFile::remove(filename)) {
error(xi18n("Cannot overwrite existing <filename>%1</filename>.", filename),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
if (!exportGroups()) {
finished();
return;
}
if (!openpgpKeys.empty()) {
if (!startExportJob(GpgME::OpenPGP, openpgpKeys)) {
finished();
return;
}
}
if (!cmsKeys.empty()) {
if (!startExportJob(GpgME::CMS, cmsKeys)) {
finishedIfLastJob(nullptr);
}
}
}
bool ExportGroupsCommand::Private::exportGroups()
{
const auto result = writeKeyGroups(filename, groups);
if (result != WriteKeyGroups::Success) {
error(xi18n("Writing groups to file <filename>%1</filename> failed.", filename),
i18nc("@title:window", "Export Failed"));
}
return result == WriteKeyGroups::Success;
}
bool ExportGroupsCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys)
{
const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr<ExportJob> jobOwner(backend->publicKeyExportJob(/*armor=*/ true));
auto job = jobOwner.get();
Q_ASSERT(job);
connect(job, &ExportJob::result,
q, [this, job](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(job, err, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(job, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
const GpgME::Error err = job->start(Kleo::getFingerprints(keys));
if (err) {
showError(err);
return false;
}
Q_EMIT q->info(i18n("Exporting certificate groups..."));
exportJobs.push_back(jobOwner.release());
return true;
}
void ExportGroupsCommand::Private::onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData)
{
Q_ASSERT(Kleo::contains(exportJobs, job));
if (err) {
showError(err);
finishedIfLastJob(job);
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly | QIODevice::Append)) {
error(xi18n("Cannot open file <filename>%1</filename> for writing.", filename),
i18nc("@title:window", "Export Failed"));
finishedIfLastJob(job);
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18n("Writing certificates to file <filename>%1</filename> failed.", filename),
i18nc("@title:window", "Export Failed"));
}
finishedIfLastJob(job);
}
void ExportGroupsCommand::Private::showError(const GpgME::Error &err)
{
error(xi18n("<para>An error occurred during the export:</para>"
"<para><message>%1</message></para>",
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
i18nc("@title:window", "Export Failed"));
}
void ExportGroupsCommand::Private::finishedIfLastJob(const QGpgME::Job *job)
{
if (job) {
exportJobs.erase(std::remove(exportJobs.begin(), exportJobs.end(), job), exportJobs.end());
}
if (exportJobs.size() == 0) {
finished();
}
}
void ExportGroupsCommand::Private::cancelJobs()
{
std::for_each(std::cbegin(exportJobs), std::cend(exportJobs),
[](const auto &job) {
if (job) {
job->slotCancel();
}
});
exportJobs.clear();
}
ExportGroupsCommand::ExportGroupsCommand(const std::vector<KeyGroup> &groups)
: Command{new Private{this}}
{
d->groups = groups;
}
ExportGroupsCommand::~ExportGroupsCommand() = default;
void ExportGroupsCommand::doStart()
{
d->start();
}
void ExportGroupsCommand::doCancel()
{
d->cancelJobs();
}
#undef d
#undef q
#include "moc_exportgroupscommand.cpp"
diff --git a/src/commands/exportsecretkeycommand.cpp b/src/commands/exportsecretkeycommand.cpp
index cf8cf9ee9..15cfdc0aa 100644
--- a/src/commands/exportsecretkeycommand.cpp
+++ b/src/commands/exportsecretkeycommand.cpp
@@ -1,316 +1,316 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportsecretkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportsecretkeycommand.h"
#include "command_p.h"
#include "fileoperationspreferences.h"
#include <utils/applicationstate.h>
#include "utils/filedialog.h"
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/Protocol>
#include <QGpgME/ExportJob>
#include <QFileInfo>
#include <QStandardPaths>
#include <gpgme++/context.h>
#include <algorithm>
#include <memory>
#include <vector>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
namespace
{
QString openPGPCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate,
FileOperationsPreferences().usePGPFileExt())};
}
QString cmsCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::CMS | Class::Binary | Class::ExportedPSM,
/*usePGPFileExt=*/false)};
}
QString certificateFileExtension(GpgME::Protocol protocol)
{
switch (protocol) {
case GpgME::OpenPGP:
return openPGPCertificateFileExtension();
case GpgME::CMS:
return cmsCertificateFileExtension();
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol;
return QStringLiteral("txt");
}
}
QString proposeFilename(const Key &key)
{
QString filename;
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID());
/* Not translated so it's better to use in tutorials etc. */
filename = QStringView{u"%1_%2_SECRET"}.arg(name, shortKeyID);
filename.replace(u'/', u'_');
return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + certificateFileExtension(key.protocol());
}
QString secretKeyFileFilters(GpgME::Protocol protocol)
{
switch (protocol) {
case GpgME::OpenPGP:
return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"};
case GpgME::CMS:
return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.p12)"};
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol;
return i18nc("description of filename filter", "All Files") + QLatin1String{" (*)"};
}
}
QString requestFilename(const Key &key, const QString &proposedFilename, QWidget *parent)
{
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18nc("@title:window", "Secret Key Backup"),
QStringLiteral("imp"),
proposedFilename,
secretKeyFileFilters(key.protocol()));
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += u'.' + certificateFileExtension(key.protocol());
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
QString errorCaption()
{
return i18nc("@title:window", "Secret Key Backup Error");
}
}
class ExportSecretKeyCommand::Private : public Command::Private
{
friend class ::ExportSecretKeyCommand;
ExportSecretKeyCommand *q_func() const
{
return static_cast<ExportSecretKeyCommand *>(q);
}
public:
explicit Private(ExportSecretKeyCommand *qq, KeyListController *c = nullptr);
~Private() override;
void start();
void cancel();
private:
std::unique_ptr<QGpgME::ExportJob> startExportJob(const Key &key);
void onExportJobResult(const Error &err, const QByteArray &keyData);
void showError(const Error &err);
private:
QString filename;
QPointer<QGpgME::ExportJob> job;
};
ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportSecretKeyCommand::Private::Private(ExportSecretKeyCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
ExportSecretKeyCommand::Private::~Private() = default;
void ExportSecretKeyCommand::Private::start()
{
const Key key = this->key();
if (key.isNull()) {
finished();
return;
}
filename = requestFilename(key, proposeFilename(key), parentWidgetOrView());
if (filename.isEmpty()) {
canceled();
return;
}
auto exportJob = startExportJob(key);
if (!exportJob) {
finished();
return;
}
job = exportJob.release();
}
void ExportSecretKeyCommand::Private::cancel()
{
if (job) {
job->slotCancel();
}
job.clear();
}
std::unique_ptr<QGpgME::ExportJob> ExportSecretKeyCommand::Private::startExportJob(const Key &key)
{
#if QGPGME_SUPPORTS_SECRET_KEY_EXPORT
const bool armor = key.protocol() == GpgME::OpenPGP && filename.endsWith(u".asc", Qt::CaseInsensitive);
const QGpgME::Protocol *const backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr<QGpgME::ExportJob> exportJob{backend->secretKeyExportJob(armor)};
Q_ASSERT(exportJob);
if (key.protocol() == GpgME::CMS) {
exportJob->setExportFlags(GpgME::Context::ExportPKCS12);
}
connect(exportJob.get(), &QGpgME::ExportJob::result,
q, [this](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(err, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(exportJob.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(exportJob.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
const GpgME::Error err = exportJob->start({QLatin1String{key.primaryFingerprint()}});
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Backing up secret key..."));
return exportJob;
#else
Q_UNUSED(key)
return {};
#endif
}
void ExportSecretKeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData)
{
if (err.isCanceled()) {
finished();
return;
}
if (err) {
showError(err);
finished();
return;
}
if (keyData.isEmpty()) {
error(i18nc("@info", "The result of the backup is empty. Maybe you entered an empty or a wrong passphrase."),
errorCaption());
finished();
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly)) {
error(xi18nc("@info", "Cannot open file <filename>%1</filename> for writing.", filename),
errorCaption());
finished();
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18nc("@info", "Writing key to file <filename>%1</filename> failed.", filename),
errorCaption());
finished();
return;
}
information(i18nc("@info", "The backup of the secret key was created successfully."),
i18nc("@title:window", "Secret Key Backup"));
finished();
}
void ExportSecretKeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred during the backup of the secret key:</para>"
"<para><message>%1</message></para>",
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
errorCaption());
}
ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *view, KeyListController *controller)
: Command{view, new Private{this, controller}}
{
}
ExportSecretKeyCommand::ExportSecretKeyCommand(const GpgME::Key &key)
: Command{key, new Private{this}}
{
}
ExportSecretKeyCommand::~ExportSecretKeyCommand() = default;
void ExportSecretKeyCommand::doStart()
{
d->start();
}
void ExportSecretKeyCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_exportsecretkeycommand.cpp"
diff --git a/src/commands/exportsecretsubkeycommand.cpp b/src/commands/exportsecretsubkeycommand.cpp
index 88db09164..86a11d80e 100644
--- a/src/commands/exportsecretsubkeycommand.cpp
+++ b/src/commands/exportsecretsubkeycommand.cpp
@@ -1,298 +1,298 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportsecretsubkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportsecretsubkeycommand.h"
#include "command_p.h"
#include "fileoperationspreferences.h"
#include <utils/applicationstate.h>
#if QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
#include "utils/filedialog.h"
#endif
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/ExportJob>
#include <QGpgME/Protocol>
#include <QFileInfo>
#include <QStandardPaths>
#include <algorithm>
#include <memory>
#include <vector>
using namespace Kleo;
using namespace GpgME;
namespace
{
#if QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
QString openPGPCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate,
FileOperationsPreferences().usePGPFileExt())};
}
QString proposeFilename(const std::vector<Subkey> &subkeys)
{
QString filename;
if (subkeys.size() == 1) {
const auto subkey = subkeys.front();
const auto key = subkey.parent();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID());
const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData());
const auto usage = Formatting::usageString(subkey).replace(QLatin1String{", "}, QLatin1String{"_"});
/* Not translated so it's better to use in tutorials etc. */
filename = QStringView{u"%1_%2_SECRET_SUBKEY_%3_%4"}.arg(
name, shortKeyID, shortSubkeyID, usage);
} else {
filename = i18nc("Generic filename for exported subkeys", "subkeys");
}
filename.replace(u'/', u'_');
return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + openPGPCertificateFileExtension();
}
QString requestFilename(const std::vector<Subkey> &subkeys, const QString &proposedFilename, QWidget *parent)
{
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18ncp("@title:window", "Export Subkey", "Export Subkeys", subkeys.size()),
QStringLiteral("imp"),
proposedFilename,
i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"});
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += u'.' + openPGPCertificateFileExtension();
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
template<typename SubkeyContainer>
QStringList getSubkeyFingerprints(const SubkeyContainer &subkeys)
{
QStringList fingerprints;
fingerprints.reserve(subkeys.size());
std::transform(std::begin(subkeys), std::end(subkeys),
std::back_inserter(fingerprints),
[](const auto &subkey) {
return QLatin1String{subkey.fingerprint()} + u'!';
});
return fingerprints;
}
#endif
}
class ExportSecretSubkeyCommand::Private : public Command::Private
{
friend class ::ExportSecretSubkeyCommand;
ExportSecretSubkeyCommand *q_func() const
{
return static_cast<ExportSecretSubkeyCommand *>(q);
}
public:
explicit Private(ExportSecretSubkeyCommand *qq);
~Private() override;
void start();
void cancel();
private:
std::unique_ptr<QGpgME::ExportJob> startExportJob(const std::vector<Subkey> &subkeys);
void onExportJobResult(const Error &err, const QByteArray &keyData);
void showError(const Error &err);
private:
std::vector<Subkey> subkeys;
QString filename;
QPointer<QGpgME::ExportJob> job;
};
ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportSecretSubkeyCommand::Private::Private(ExportSecretSubkeyCommand *qq)
: Command::Private{qq}
{
}
ExportSecretSubkeyCommand::Private::~Private() = default;
void ExportSecretSubkeyCommand::Private::start()
{
#if QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
if (subkeys.empty()) {
finished();
return;
}
filename = requestFilename(subkeys, proposeFilename(subkeys), parentWidgetOrView());
if (filename.isEmpty()) {
canceled();
return;
}
auto exportJob = startExportJob(subkeys);
if (!exportJob) {
finished();
return;
}
job = exportJob.release();
#else
Q_ASSERT(!"This command is not supported by the backend it was compiled against");
finished();
return;
#endif
}
void ExportSecretSubkeyCommand::Private::cancel()
{
if (job) {
job->slotCancel();
}
job.clear();
}
std::unique_ptr<QGpgME::ExportJob> ExportSecretSubkeyCommand::Private::startExportJob(const std::vector<Subkey> &subkeys)
{
#if QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
const bool armor = filename.endsWith(u".asc", Qt::CaseInsensitive);
std::unique_ptr<QGpgME::ExportJob> exportJob{QGpgME::openpgp()->secretSubkeyExportJob(armor)};
Q_ASSERT(exportJob);
connect(exportJob.get(), &QGpgME::ExportJob::result,
q, [this](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(err, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(exportJob.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(exportJob.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
const GpgME::Error err = exportJob->start(getSubkeyFingerprints(subkeys));
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Exporting subkeys..."));
return exportJob;
#else
Q_UNUSED(subkeys)
return {};
#endif
}
void ExportSecretSubkeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData)
{
if (err) {
showError(err);
finished();
return;
}
if (keyData.isEmpty()) {
error(i18nc("@info", "The result of the export is empty."),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly)) {
error(xi18nc("@info", "Cannot open file <filename>%1</filename> for writing.", filename),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18ncp("@info",
"Writing subkey to file <filename>%2</filename> failed.",
"Writing subkeys to file <filename>%2</filename> failed.",
subkeys.size(), filename),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
information(i18ncp("@info",
"The subkey was exported successfully.",
"%1 subkeys were exported successfully.",
subkeys.size()),
i18nc("@title:window", "Secret Key Backup"));
finished();
}
void ExportSecretSubkeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred during the export:</para>"
"<para><message>%1</message></para>",
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
i18nc("@title:window", "Export Failed"));
}
ExportSecretSubkeyCommand::ExportSecretSubkeyCommand(const std::vector<GpgME::Subkey> &subkeys)
: Command{new Private{this}}
{
d->subkeys = subkeys;
}
ExportSecretSubkeyCommand::~ExportSecretSubkeyCommand() = default;
void ExportSecretSubkeyCommand::doStart()
{
d->start();
}
void ExportSecretSubkeyCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_exportsecretsubkeycommand.cpp"
diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp
index d74a2b4ef..eaabb35a6 100644
--- a/src/commands/importcertificatescommand.cpp
+++ b/src/commands/importcertificatescommand.cpp
@@ -1,1139 +1,1139 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/importcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
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-kleopatra.h>
#include "importcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "certifycertificatecommand.h"
#include <utils/keys.h>
#include <utils/memory-helpers.h>
#include <settings.h>
#include "kleopatra_debug.h"
#include <Libkleo/Algorithm>
#include <Libkleo/KeyList>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyGroupImportExport>
#include <Libkleo/KeyHelpers>
#include <Libkleo/MessageBox>
#include <Libkleo/Predicates>
#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
#include <QGpgME/Protocol>
#include <QGpgME/ImportJob>
#include <QGpgME/ImportFromKeyserverJob>
#include <QGpgME/ChangeOwnerTrustJob>
#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
#include <QGpgME/ReceiveKeysJob>
#endif
#include <gpgme++/global.h>
#include <gpgme++/importresult.h>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QByteArray>
#include <QEventLoop>
#include <QProgressDialog>
#include <QString>
#include <QWidget>
#include <QTreeView>
#include <QTextDocument> // for Qt::escape
#include <memory>
#include <algorithm>
#include <map>
#include <set>
using namespace GpgME;
using namespace Kleo;
using namespace QGpgME;
static void disconnectConnection(const QMetaObject::Connection &connection)
{
// trivial function for disconnecting a signal-slot connection
QObject::disconnect(connection);
}
bool operator==(const ImportJobData &lhs, const ImportJobData &rhs)
{
return lhs.job == rhs.job;
}
namespace
{
make_comparator_str(ByImportFingerprint, .fingerprint());
class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
ImportResultProxyModel(const std::vector<ImportResultData> &results, QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
{
updateFindCache(results);
}
~ImportResultProxyModel() override {}
ImportResultProxyModel *clone() const override
{
// compiler-generated copy ctor is fine!
return new ImportResultProxyModel(*this);
}
void setImportResults(const std::vector<ImportResultData> &results)
{
updateFindCache(results);
invalidateFilter();
}
protected:
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid() || role != Qt::ToolTipRole) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
}
const QString fpr = index.data(KeyList::FingerprintRole).toString();
// find information:
const std::vector<Import>::const_iterator it
= Kleo::binary_find(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint<std::less>());
if (it == m_importsByFingerprint.end()) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
} else {
QStringList rv;
const auto ids = m_idsByFingerprint[it->fingerprint()];
rv.reserve(ids.size());
std::copy(ids.cbegin(), ids.cend(), std::back_inserter(rv));
return Formatting::importMetaData(*it, rv);
}
}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
//
// 0. Keep parents of matching children:
//
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
Q_ASSERT(index.isValid());
for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i)
if (filterAcceptsRow(i, index)) {
return true;
}
//
// 1. Check that this is an imported key:
//
const QString fpr = index.data(KeyList::FingerprintRole).toString();
return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint<std::less>());
}
private:
void updateFindCache(const std::vector<ImportResultData> &results)
{
m_importsByFingerprint.clear();
m_idsByFingerprint.clear();
m_results = results;
for (const auto &r : results) {
const std::vector<Import> imports = r.result.imports();
m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end());
for (std::vector<Import>::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) {
m_idsByFingerprint[it->fingerprint()].insert(r.id);
}
}
std::sort(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
ByImportFingerprint<std::less>());
}
private:
mutable std::vector<Import> m_importsByFingerprint;
mutable std::map< const char *, std::set<QString>, ByImportFingerprint<std::less> > m_idsByFingerprint;
std::vector<ImportResultData> m_results;
};
bool importFailed(const ImportResultData &r)
{
// ignore GPG_ERR_EOF error to handle the "failed" import of files
// without X.509 certificates by gpgsm gracefully
return r.result.error() && r.result.error().code() != GPG_ERR_EOF;
}
bool importWasCanceled(const ImportResultData &r)
{
return r.result.error().isCanceled();
}
}
ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, progressWindowTitle{i18nc("@title:window", "Importing Certificates")}
, progressLabelText{i18n("Importing certificates... (this can take a while)")}
{
}
ImportCertificatesCommand::Private::~Private()
{
if (progressDialog) {
delete progressDialog;
}
}
#define d d_func()
#define q q_func()
ImportCertificatesCommand::ImportCertificatesCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ImportCertificatesCommand::ImportCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ImportCertificatesCommand::~ImportCertificatesCommand() = default;
static QString format_ids(const std::vector<QString> &ids)
{
QStringList escapedIds;
for (const QString &id : ids) {
if (!id.isEmpty()) {
escapedIds << id.toHtmlEscaped();
}
}
return escapedIds.join(QLatin1String("<br>"));
}
static QString make_tooltip(const std::vector<ImportResultData> &results)
{
if (results.empty()) {
return {};
}
std::vector<QString> ids;
ids.reserve(results.size());
std::transform(std::begin(results), std::end(results),
std::back_inserter(ids),
[](const auto &r) { return r.id; });
std::sort(std::begin(ids), std::end(ids));
ids.erase(std::unique(std::begin(ids), std::end(ids)), std::end(ids));
if (ids.size() == 1)
if (ids.front().isEmpty()) {
return {};
} else
return i18nc("@info:tooltip",
"Imported Certificates from %1",
ids.front().toHtmlEscaped());
else
return i18nc("@info:tooltip",
"Imported certificates from these sources:<br/>%1",
format_ids(ids));
}
void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector<ImportResultData> &results)
{
if (std::none_of(std::begin(results), std::end(results),
[](const auto &r) { return r.result.numConsidered() > 0; })) {
return;
}
q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"),
new ImportResultProxyModel(results),
make_tooltip(results));
if (QTreeView *const tv = qobject_cast<QTreeView *>(parentWidgetOrView())) {
tv->expandAll();
}
}
int sum(const std::vector<ImportResult> &res, int (ImportResult::*fun)() const)
{
return kdtools::accumulate_transform(res.begin(), res.end(), std::mem_fn(fun), 0);
}
static QString make_report(const std::vector<ImportResultData> &results,
const std::vector<ImportedGroup> &groups)
{
const KLocalizedString normalLine = ki18n("<tr><td align=\"right\">%1</td><td>%2</td></tr>");
const KLocalizedString boldLine = ki18n("<tr><td align=\"right\"><b>%1</b></td><td>%2</td></tr>");
const KLocalizedString headerLine = ki18n("<tr><th colspan=\"2\" align=\"center\">%1</th></tr>");
std::vector<ImportResult> res;
res.reserve(results.size());
std::transform(std::begin(results), std::end(results),
std::back_inserter(res),
[](const auto &r) { return r.result; });
const auto numProcessedCertificates = sum(res, &ImportResult::numConsidered);
QStringList lines;
if (numProcessedCertificates > 0 || groups.size() == 0) {
lines.push_back(headerLine.subs(i18n("Certificates")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:"))
.subs(numProcessedCertificates).toString());
lines.push_back(normalLine.subs(i18n("Imported:"))
.subs(sum(res, &ImportResult::numImported)).toString());
if (const int n = sum(res, &ImportResult::newSignatures))
lines.push_back(normalLine.subs(i18n("New signatures:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newUserIDs))
lines.push_back(normalLine.subs(i18n("New user IDs:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numKeysWithoutUserID))
lines.push_back(normalLine.subs(i18n("Certificates without user IDs:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newSubkeys))
lines.push_back(normalLine.subs(i18n("New subkeys:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newRevocations))
lines.push_back(boldLine.subs(i18n("Newly revoked:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::notImported))
lines.push_back(boldLine.subs(i18n("Not imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numUnchanged))
lines.push_back(normalLine.subs(i18n("Unchanged:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered))
lines.push_back(normalLine.subs(i18n("Secret keys processed:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysImported))
lines.push_back(normalLine.subs(i18n("Secret keys imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered) - sum(res, &ImportResult::numSecretKeysImported) - sum(res, &ImportResult::numSecretKeysUnchanged))
if (n > 0)
lines.push_back(boldLine.subs(i18n("Secret keys <em>not</em> imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysUnchanged))
lines.push_back(normalLine.subs(i18n("Secret keys unchanged:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numV3KeysSkipped))
lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:"))
.subs(n).toString());
}
if (!lines.empty()) {
lines.push_back(headerLine.subs(QLatin1String{"&nbsp;"}).toString());
}
if (groups.size() > 0) {
const auto newGroups = std::count_if(std::begin(groups), std::end(groups),
[](const auto &g) {
return g.status == ImportedGroup::Status::New;
});
const auto updatedGroups = groups.size() - newGroups;
lines.push_back(headerLine.subs(i18n("Certificate Groups")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:"))
.subs(groups.size()).toString());
lines.push_back(normalLine.subs(i18n("New groups:"))
.subs(newGroups).toString());
lines.push_back(normalLine.subs(i18n("Updated groups:"))
.subs(updatedGroups).toString());
}
return lines.join(QLatin1String{});
}
static bool isImportFromSingleSource(const std::vector<ImportResultData> &res)
{
return (res.size() == 1) || (res.size() == 2 && res[0].id == res[1].id);
}
static QString make_message_report(const std::vector<ImportResultData> &res,
const std::vector<ImportedGroup> &groups)
{
QString report{QLatin1String{"<html>"}};
if (res.empty()) {
report += i18n("No imports (should not happen, please report a bug).");
} else {
const QString title = isImportFromSingleSource(res) && !res.front().id.isEmpty() ?
i18n("Detailed results of importing %1:", res.front().id) :
i18n("Detailed results of import:");
report += QLatin1String{"<p>"} + title + QLatin1String{"</p>"};
report += QLatin1String{"<p><table width=\"100%\">"};
report += make_report(res, groups);
report += QLatin1String{"</table></p>"};
}
report += QLatin1String{"</html>"};
return report;
}
// Returns false on error, true if please certify was shown.
bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp)
{
if (!Kleo::userHasCertificationKey()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "No certification key available";
return false;
}
const char *fpr = imp.fingerprint();
if (!fpr) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Import without fingerprint";
return false;
}
// Exactly one public key imported. Let's see if it is openpgp. We are async here so
// we can just fetch it.
auto ctx = wrap_unique(GpgME::Context::createForProtocol(GpgME::OpenPGP));
if (!ctx) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto";
return false;
}
ctx->addKeyListMode(KeyListMode::WithSecret);
GpgME::Error err;
const auto key = ctx->key(fpr, err, false);
if (key.isNull() || err) {
// No such key most likely not OpenPGP
return false;
}
if (!Kleo::canBeCertified(key)) {
// key is expired or revoked
return false;
}
if (key.hasSecret()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "Secret key is available -> skipping certification";
return false;
}
for (const auto &uid: key.userIDs()) {
if (uid.validity() >= GpgME::UserID::Marginal) {
// Already marginal so don't bug the user
return false;
}
}
const QStringList suggestions = QStringList() << i18n("A phone call to the person.")
<< i18n("Using a business card.")
<< i18n("Confirming it on a trusted website.");
auto sel = KMessageBox::questionTwoActions(parentWidgetOrView(),
i18n("In order to mark the certificate as valid it needs to be certified.") + QStringLiteral("<br>")
+ i18n("Certifying means that you check the Fingerprint.") + QStringLiteral("<br>")
+ i18n("Some suggestions to do this are:")
+ QStringLiteral("<li><ul>%1").arg(suggestions.join(QStringLiteral("</ul><ul>")))
+ QStringLiteral("</ul></li>") + i18n("Do you wish to start this process now?"),
i18nc("@title", "You have imported a new certificate (public key)"),
KGuiItem(i18nc("@action:button", "Certify")),
KStandardGuiItem::cancel(),
QStringLiteral("CertifyQuestion"));
if (sel == KMessageBox::ButtonCode::PrimaryAction) {
QEventLoop loop;
auto cmd = new Commands::CertifyCertificateCommand(key);
cmd->setParentWidget(parentWidgetOrView());
connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
}
return true;
}
namespace
{
/**
* Returns the Import of an OpenPGP key, if a single certificate was imported and this was an OpenPGP key.
* Otherwise, returns a null Import.
*/
auto getSingleOpenPGPImport(const std::vector<ImportResultData> &res)
{
static const Import nullImport;
if (!isImportFromSingleSource(res)) {
return nullImport;
}
const auto numImported = std::accumulate(res.cbegin(), res.cend(), 0, [](auto s, const auto &r) {
return s + r.result.numImported();
});
if (numImported > 1) {
return nullImport;
}
if ((res.size() >= 1) && (res[0].protocol == GpgME::OpenPGP) && (res[0].result.numImported() == 1) && (res[0].result.imports().size() == 1)) {
return res[0].result.imports()[0];
} else if ((res.size() == 2) && (res[1].protocol == GpgME::OpenPGP) && (res[1].result.numImported() == 1) && (res[1].result.imports().size() == 1)) {
return res[1].result.imports()[0];
}
return nullImport;
}
auto consolidatedAuditLogEntries(const std::vector<ImportResultData> &res)
{
static const QString gpg = QStringLiteral("gpg");
static const QString gpgsm = QStringLiteral("gpgsm");
if (res.size() == 1) {
return res.front().auditLog;
}
QStringList auditLogs;
auto extractAndAnnotateAuditLog = [](const ImportResultData &r) {
QString s;
if (!r.id.isEmpty()) {
const auto program = r.protocol == GpgME::OpenPGP ? gpg : gpgsm;
const auto headerLine = i18nc("file name (imported with gpg/gpgsm)", "%1 (imported with %2)").arg(r.id, program);
s += QStringLiteral("<div><b>%1</b></div>").arg(headerLine);
}
if (r.auditLog.error().code() == GPG_ERR_NO_DATA) {
s += QStringLiteral("<em>") + i18nc("@info", "Audit log is empty.") + QStringLiteral("</em>");
} else if (r.result.error().isCanceled()) {
s += QStringLiteral("<em>") + i18nc("@info", "Import was canceled.") + QStringLiteral("</em>");
} else {
s += r.auditLog.text();
}
return s;
};
std::transform(res.cbegin(), res.cend(), std::back_inserter(auditLogs), extractAndAnnotateAuditLog);
return AuditLogEntry{auditLogs.join(QLatin1String{"<hr>"}), Error{}};
}
}
void ImportCertificatesCommand::Private::showDetails(const std::vector<ImportResultData> &res,
const std::vector<ImportedGroup> &groups)
{
const auto singleOpenPGPImport = getSingleOpenPGPImport(res);
if (!singleOpenPGPImport.isNull()) {
if (showPleaseCertify(singleOpenPGPImport)) {
return;
}
}
setImportResultProxyModel(res);
MessageBox::information(parentWidgetOrView(), make_message_report(res, groups), consolidatedAuditLogEntries(res), i18n("Certificate Import Result"));
}
static QString make_error_message(const Error &err, const QString &id)
{
Q_ASSERT(err);
Q_ASSERT(!err.isCanceled());
return id.isEmpty()
? i18n("<qt><p>An error occurred while trying "
"to import the certificate:</p>"
"<p><b>%1</b></p></qt>",
- QString::fromLocal8Bit(err.asString()))
+ Formatting::errorAsString(err))
: i18n("<qt><p>An error occurred while trying "
"to import the certificate %1:</p>"
"<p><b>%2</b></p></qt>",
- id, QString::fromLocal8Bit(err.asString()));
+ id, Formatting::errorAsString(err));
}
void ImportCertificatesCommand::Private::showError(const ImportResultData &result)
{
MessageBox::error(parentWidgetOrView(), make_error_message(result.result.error(), result.id), result.auditLog);
}
void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait)
{
if (wait == waitForMoreJobs) {
return;
}
waitForMoreJobs = wait;
if (!waitForMoreJobs) {
tryToFinish();
}
}
void ImportCertificatesCommand::Private::onImportResult(const ImportResult &result, QGpgME::Job *finishedJob)
{
if (!finishedJob) {
finishedJob = qobject_cast<QGpgME::Job *>(q->sender());
}
Q_ASSERT(finishedJob);
qCDebug(KLEOPATRA_LOG) << q << __func__ << finishedJob;
auto it = std::find_if(std::begin(runningJobs), std::end(runningJobs),
[finishedJob](const auto &job) { return job.job == finishedJob; });
Q_ASSERT(it != std::end(runningJobs));
if (it == std::end(runningJobs)) {
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Finished job not found";
return;
}
Kleo::for_each(it->connections, &disconnectConnection);
it->connections.clear();
increaseProgressValue();
const auto job = *it;
addImportResult({job.id, job.protocol, job.type, result, AuditLogEntry::fromJob(finishedJob)}, job);
}
void ImportCertificatesCommand::Private::addImportResult(const ImportResultData &result, const ImportJobData &job)
{
- qCDebug(KLEOPATRA_LOG) << q << __func__ << result.id << "Result:" << result.result.error().asString();
+ qCDebug(KLEOPATRA_LOG) << q << __func__ << result.id << "Result:" << Formatting::errorAsString(result.result.error());
results.push_back(result);
if (importFailed(result)) {
showError(result);
}
if (job.job) {
const auto count = std::erase(runningJobs, job);
Q_ASSERT(count == 1);
}
tryToFinish();
}
static void handleOwnerTrust(const std::vector<ImportResultData> &results, QWidget *dialog)
{
for (const auto &r: results) {
if (r.protocol != GpgME::Protocol::OpenPGP) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping non-OpenPGP import";
continue;
}
const auto imports = r.result.imports();
for (const auto &import : imports) {
if (!(import.status() & (Import::Status::NewKey | Import::Status::ContainedSecretKey))) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping already known imported public key";
continue;
}
const char *fpr = import.fingerprint();
if (!fpr) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import without fingerprint";
continue;
}
GpgME::Error err;
auto ctx = wrap_unique(Context::createForProtocol(GpgME::Protocol::OpenPGP));
if (!ctx){
qCWarning(KLEOPATRA_LOG) << "Failed to get context";
continue;
}
ctx->addKeyListMode(KeyListMode::WithSecret);
const Key toTrustOwner = ctx->key(fpr, err, false);
if (toTrustOwner.isNull() || !toTrustOwner.hasSecret()) {
continue;
}
if (toTrustOwner.ownerTrust() == Key::OwnerTrust::Ultimate) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping key with ultimate ownertrust";
continue;
}
const auto toTrustOwnerUserIDs{toTrustOwner.userIDs()};
// ki18n(" ") as initializer because initializing with empty string leads to
// (I18N_EMPTY_MESSAGE)
const KLocalizedString uids = std::accumulate(toTrustOwnerUserIDs.cbegin(), toTrustOwnerUserIDs.cend(), KLocalizedString{ki18n(" ")}, [](KLocalizedString temp, const auto &uid) {
return kxi18nc("@info", "%1<item>%2</item>").subs(temp).subs(Formatting::prettyNameAndEMail(uid));
});
const QString str = xi18nc("@info",
"<para>You have imported a certificate with fingerprint</para>"
"<para><numid>%1</numid></para>"
"<para>"
"and user IDs"
"<list>%2</list>"
"</para>"
"<para>Is this your own certificate?</para>",
Formatting::prettyID(fpr),
uids);
int k = KMessageBox::questionTwoActionsCancel(dialog,
str,
i18nc("@title:window", "Mark Own Certificate"),
KGuiItem{i18nc("@action:button", "Yes, It's Mine")},
KGuiItem{i18nc("@action:button", "No, It's Not Mine")});
if (k == KMessageBox::ButtonCode::PrimaryAction) {
//To use the ChangeOwnerTrustJob over
//the CryptoBackendFactory
const QGpgME::Protocol *const backend = QGpgME::openpgp();
if (!backend){
qCWarning(KLEOPATRA_LOG) << "Failed to get CryptoBackend";
return;
}
ChangeOwnerTrustJob *const j = backend->changeOwnerTrustJob();
j->start(toTrustOwner, Key::Ultimate);
} else if (k == KMessageBox::ButtonCode::Cancel) {
// do not bother the user with further "Is this yours?" questions
return;
}
}
}
}
static void validateImportedCertificate(const GpgME::Import &import)
{
if (const auto fpr = import.fingerprint()) {
auto key = KeyCache::instance()->findByFingerprint(fpr);
if (!key.isNull()) {
// this triggers a keylisting with validation for this certificate
key.update();
} else {
qCWarning(KLEOPATRA_LOG) << __func__ << "Certificate with fingerprint" << fpr << "not found";
}
}
}
static void handleExternalCMSImports(const std::vector<ImportResultData> &results)
{
// For external CMS Imports we have to manually do a keylist
// with validation to get the intermediate and root ca imported
// automatically if trusted-certs and extra-certs are used.
for (const auto &r : results) {
if (r.protocol == GpgME::CMS && r.type == ImportType::External
&& !importFailed(r) && !importWasCanceled(r)) {
const auto imports = r.result.imports();
std::for_each(std::begin(imports), std::end(imports), &validateImportedCertificate);
}
}
}
void ImportCertificatesCommand::Private::processResults()
{
importGroups();
#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
if (Settings{}.retrieveSignerKeysAfterImport() && !importingSignerKeys) {
importingSignerKeys = true;
const auto missingSignerKeys = getMissingSignerKeyIds(results);
if (!missingSignerKeys.empty()) {
importSignerKeys(missingSignerKeys);
return;
}
}
#endif
handleExternalCMSImports(results);
// ensure that the progress dialog is closed before we show any other dialogs
setProgressToMaximum();
handleOwnerTrust(results, parentWidgetOrView());
showDetails(results, importedGroups);
auto tv = dynamic_cast<QTreeView *> (view());
if (!tv) {
qCDebug(KLEOPATRA_LOG) << "Failed to find treeview";
} else {
tv->expandAll();
}
finished();
}
void ImportCertificatesCommand::Private::tryToFinish()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
if (waitForMoreJobs) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "Waiting for more jobs -> keep going";
return;
}
if (!runningJobs.empty()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are unfinished jobs -> keep going";
return;
}
#if QGPGME_SUPPORTS_DEFERRED_IMPORT_JOB
if (!pendingJobs.empty()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are pending jobs -> start the next one";
auto job = pendingJobs.front();
pendingJobs.pop();
job.job->startNow();
runningJobs.push_back(job);
return;
}
#endif
if (keyListConnection) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "There is already a valid keyListConnection!";
} else {
auto keyCache = KeyCache::mutableInstance();
keyListConnection = connect(keyCache.get(), &KeyCache::keyListingDone,
q, [this]() { keyCacheUpdated(); });
keyCache->startKeyListing();
}
}
void ImportCertificatesCommand::Private::keyCacheUpdated()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
if (!disconnect(keyListConnection)) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "Failed to disconnect keyListConnection";
}
keyCacheAutoRefreshSuspension.reset();
const auto allIds = std::accumulate(std::cbegin(results), std::cend(results),
std::set<QString>{},
[](auto allIds, const auto &r) {
allIds.insert(r.id);
return allIds;
});
const auto canceledIds = std::accumulate(std::cbegin(results), std::cend(results),
std::set<QString>{},
[](auto canceledIds, const auto &r) {
if (importWasCanceled(r)) {
canceledIds.insert(r.id);
}
return canceledIds;
});
const auto totalConsidered = std::accumulate(std::cbegin(results), std::cend(results),
0,
[](auto totalConsidered, const auto &r) {
return totalConsidered + r.result.numConsidered();
});
if (totalConsidered == 0 && canceledIds.size() == allIds.size()) {
// nothing was considered for import and at least one import per id was
// canceled => treat the command as canceled
canceled();
return;
}
processResults();
}
static ImportedGroup storeGroup(const KeyGroup &group, const QString &id)
{
const auto status = KeyCache::instance()->group(group.id()).isNull() ?
ImportedGroup::Status::New :
ImportedGroup::Status::Updated;
if (status == ImportedGroup::Status::New) {
KeyCache::mutableInstance()->insert(group);
} else {
KeyCache::mutableInstance()->update(group);
}
return {id, group, status};
}
void ImportCertificatesCommand::Private::importGroups()
{
for (const auto &path : filesToImportGroupsFrom) {
const bool certificateImportSucceeded =
std::any_of(std::cbegin(results), std::cend(results),
[path](const auto &r) {
return r.id == path && !importFailed(r) && !importWasCanceled(r);
});
if (certificateImportSucceeded) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Importing groups from file" << path;
const auto groups = readKeyGroups(path);
std::transform(std::begin(groups), std::end(groups),
std::back_inserter(importedGroups),
[path](const auto &group) {
return storeGroup(group, path);
});
}
increaseProgressValue();
}
filesToImportGroupsFrom.clear();
}
static auto accumulateNewKeys(std::vector<std::string> &fingerprints, const std::vector<GpgME::Import> &imports)
{
return std::accumulate(std::begin(imports), std::end(imports),
fingerprints,
[](auto fingerprints, const auto &import) {
if (import.status() == Import::NewKey) {
fingerprints.push_back(import.fingerprint());
}
return fingerprints;
});
}
static auto accumulateNewOpenPGPKeys(const std::vector<ImportResultData> &results)
{
return std::accumulate(std::begin(results), std::end(results),
std::vector<std::string>{},
[](auto fingerprints, const auto &r) {
if (r.protocol == GpgME::OpenPGP) {
fingerprints = accumulateNewKeys(fingerprints, r.result.imports());
}
return fingerprints;
});
}
std::set<QString> ImportCertificatesCommand::Private::getMissingSignerKeyIds(const std::vector<ImportResultData> &results)
{
auto newOpenPGPKeys = KeyCache::instance()->findByFingerprint(accumulateNewOpenPGPKeys(results));
// update all new OpenPGP keys to get information about certifications
std::for_each(std::begin(newOpenPGPKeys), std::end(newOpenPGPKeys), std::mem_fn(&Key::update));
auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(newOpenPGPKeys);
return missingSignerKeyIds;
}
void ImportCertificatesCommand::Private::importSignerKeys(const std::set<QString> &keyIds)
{
Q_ASSERT(!keyIds.empty());
setProgressLabelText(i18np("Fetching 1 signer key... (this can take a while)",
"Fetching %1 signer keys... (this can take a while)",
keyIds.size()));
setWaitForMoreJobs(true);
// start one import per key id to allow canceling the key retrieval without
// losing already retrieved keys
for (const auto &keyId : keyIds) {
startImport(GpgME::OpenPGP, {keyId}, QStringLiteral("Retrieve Signer Keys"));
}
setWaitForMoreJobs(false);
}
static std::unique_ptr<ImportJob> get_import_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr<ImportJob>(backend->importJob());
} else {
return std::unique_ptr<ImportJob>();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const QByteArray &data, const QString &id,
[[maybe_unused]] const ImportOptions &options)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr<ImportJob> job = get_import_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
addImportResult({id, protocol, ImportType::Local, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector<QMetaObject::Connection> connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }),
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress,
q, &Command::progress),
#else
connect(job.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); }),
#endif
};
#if QGPGME_SUPPORTS_IMPORT_WITH_FILTER
job->setImportFilter(options.importFilter);
#endif
#if QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN
job->setKeyOrigin(options.keyOrigin, options.keyOriginUrl);
#endif
#if QGPGME_SUPPORTS_DEFERRED_IMPORT_JOB
const GpgME::Error err = job->startLater(data);
#else
const GpgME::Error err = job->start(data);
#endif
if (err.code()) {
addImportResult({id, protocol, ImportType::Local, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
#if QGPGME_SUPPORTS_DEFERRED_IMPORT_JOB
pendingJobs.push({id, protocol, ImportType::Local, job.release(), connections});
#else
runningJobs.push_back({id, protocol, ImportType::Local, job.release(), connections});
#endif
}
}
static std::unique_ptr<ImportFromKeyserverJob> get_import_from_keyserver_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr<ImportFromKeyserverJob>(backend->importFromKeyserverJob());
} else {
return std::unique_ptr<ImportFromKeyserverJob>();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector<Key> &keys, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr<ImportFromKeyserverJob> job = get_import_from_keyserver_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector<QMetaObject::Connection> connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }),
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress,
q, &Command::progress),
#else
connect(job.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); }),
#endif
};
const GpgME::Error err = job->start(keys);
if (err.code()) {
addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
}
static auto get_receive_keys_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
std::unique_ptr<ReceiveKeysJob> job{};
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
job.reset(backend->receiveKeysJob());
}
return job;
#else
return std::unique_ptr<Job>{};
#endif
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, [[maybe_unused]] const QStringList &keyIds, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
auto job = get_receive_keys_job(protocol);
if (!job.get()) {
qCWarning(KLEOPATRA_LOG) << "Failed to get ReceiveKeysJob for protocol" << Formatting::displayName(protocol);
addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}});
return;
}
#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector<QMetaObject::Connection> connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) {
onImportResult(result);
}),
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress,
q, &Command::progress),
#else
connect(job.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); }),
#endif
};
const GpgME::Error err = job->start(keyIds);
if (err.code()) {
addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
#endif
}
void ImportCertificatesCommand::Private::importGroupsFromFile(const QString &filename)
{
increaseProgressMaximum();
filesToImportGroupsFrom.push_back(filename);
}
void ImportCertificatesCommand::Private::setUpProgressDialog()
{
if (progressDialog) {
return;
}
progressDialog = new QProgressDialog{parentWidgetOrView()};
// use a non-modal progress dialog to avoid reentrancy problems (and crashes) if multiple jobs finish in the same event loop cycle
// (cf. the warning for QProgressDialog::setValue() in the API documentation)
progressDialog->setModal(false);
progressDialog->setWindowTitle(progressWindowTitle);
progressDialog->setLabelText(progressLabelText);
progressDialog->setMinimumDuration(1000);
progressDialog->setMaximum(1);
progressDialog->setValue(0);
connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel);
connect(q, &Command::finished, progressDialog, [this]() { progressDialog->accept(); });
}
void ImportCertificatesCommand::Private::setProgressWindowTitle(const QString &title)
{
if (progressDialog) {
progressDialog->setWindowTitle(title);
} else {
progressWindowTitle = title;
}
}
void ImportCertificatesCommand::Private::setProgressLabelText(const QString &text)
{
if (progressDialog) {
progressDialog->setLabelText(text);
} else {
progressLabelText = text;
}
}
void ImportCertificatesCommand::Private::increaseProgressMaximum()
{
setUpProgressDialog();
progressDialog->setMaximum(progressDialog->maximum() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::increaseProgressValue()
{
progressDialog->setValue(progressDialog->value() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::setProgressToMaximum()
{
qCDebug(KLEOPATRA_LOG) << __func__;
progressDialog->setValue(progressDialog->maximum());
}
void ImportCertificatesCommand::doCancel()
{
const auto jobsToCancel = d->runningJobs;
std::for_each(std::begin(jobsToCancel), std::end(jobsToCancel), [this](const auto &job) {
if (!job.connections.empty()) {
// ignore jobs without connections; they are already completed
qCDebug(KLEOPATRA_LOG) << "Canceling job" << job.job;
job.job->slotCancel();
d->onImportResult(ImportResult{Error::fromCode(GPG_ERR_CANCELED)}, job.job);
}
});
}
#undef d
#undef q
#include "importcertificatescommand.moc"
#include "moc_importcertificatescommand.cpp"
diff --git a/src/commands/importpaperkeycommand.cpp b/src/commands/importpaperkeycommand.cpp
index 18fba732b..6d5e29259 100644
--- a/src/commands/importpaperkeycommand.cpp
+++ b/src/commands/importpaperkeycommand.cpp
@@ -1,220 +1,221 @@
/* commands/importperkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "importpaperkeycommand.h"
+#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <gpgme++/key.h>
#include <gpgme++/importresult.h>
#include <QGpgME/Protocol>
#include <QGpgME/ImportJob>
#include <QGpgME/ExportJob>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <KMessageBox>
#include <QFileDialog>
#include <QTextStream>
#include "kleopatra_debug.h"
#include "command_p.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
ImportPaperKeyCommand::ImportPaperKeyCommand(const GpgME::Key &k) :
GnuPGProcessCommand(k)
{
}
QStringList ImportPaperKeyCommand::arguments() const
{
const Key key = d->key();
QStringList result;
result << paperKeyInstallPath() << QStringLiteral("--pubring")
<< mTmpDir.path() + QStringLiteral("/pubkey.gpg")
<< QStringLiteral("--secrets")
<< mTmpDir.path() + QStringLiteral("/secrets.txt")
<< QStringLiteral("--output")
<< mTmpDir.path() + QStringLiteral("/seckey.gpg");
return result;
}
void ImportPaperKeyCommand::exportResult(const GpgME::Error &err, const QByteArray &data)
{
if (err) {
- d->error(QString::fromUtf8(err.asString()), errorCaption());
+ d->error(Formatting::errorAsString(err), errorCaption());
d->finished();
return;
}
if (!mTmpDir.isValid()) {
// Should not happen so no i18n
d->error(QStringLiteral("Failed to get temporary directory"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to get temporary dir";
d->finished();
return;
}
const QString fileName = mTmpDir.path() + QStringLiteral("/pubkey.gpg");
QFile f(fileName);
if (!f.open(QIODevice::WriteOnly)) {
d->error(QStringLiteral("Failed to create temporary file"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file";
d->finished();
return;
}
f.write(data);
f.close();
// Copy and sanitize input a bit
QFile input(mFileName);
if (!input.open(QIODevice::ReadOnly)) {
d->error(xi18n("Cannot open <filename>%1</filename> for reading.", mFileName), errorCaption());
d->finished();
return;
}
const QString outName = mTmpDir.path() + QStringLiteral("/secrets.txt");
QFile out(outName);
if (!out.open(QIODevice::WriteOnly)) {
// Should not happen
d->error(QStringLiteral("Failed to create temporary file"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file for writing";
d->finished();
return;
}
QTextStream in(&input);
while (!in.atEnd()) {
// Paperkey is picky, tabs may not be part. Neither may be empty lines.
const QString line = in.readLine().trimmed().replace(QLatin1Char('\t'), QStringLiteral(" ")) +
QLatin1Char('\n');
out.write(line.toUtf8());
}
input.close();
out.close();
GnuPGProcessCommand::doStart();
}
void ImportPaperKeyCommand::postSuccessHook(QWidget *)
{
qCDebug(KLEOPATRA_LOG) << "Paperkey secrets restore finished successfully.";
QFile secKey(mTmpDir.path() + QStringLiteral("/seckey.gpg"));
if (!secKey.open(QIODevice::ReadOnly)) {
d->error(QStringLiteral("Failed to open temporary secret"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file";
Q_EMIT finished();
return;
}
auto data = secKey.readAll();
secKey.close();
auto importjob = QGpgME::openpgp()->importJob();
auto result = importjob->exec(data);
delete importjob;
if (result.error()) {
- d->error(QString::fromUtf8(result.error().asString()), errorCaption());
+ d->error(Formatting::errorAsString(result.error()), errorCaption());
Q_EMIT finished();
return;
}
if (!result.numSecretKeysImported() ||
(result.numSecretKeysUnchanged() == result.numSecretKeysImported())) {
d->error(i18n("Failed to restore any secret keys."), errorCaption());
Q_EMIT finished();
return;
}
// Refresh the key after success
KeyCache::mutableInstance()->reload(OpenPGP);
Q_EMIT finished();
d->information(xi18nc("@info", "Successfully restored the secret key parts from <filename>%1</filename>",
mFileName));
return;
}
void ImportPaperKeyCommand::doStart()
{
if (paperKeyInstallPath().isNull()) {
KMessageBox::error(d->parentWidgetOrView(),
xi18nc("@info", "<para><application>Kleopatra</application> uses "
"<application>PaperKey</application> to import your "
"text backup.</para>"
"<para>Please make sure it is installed.</para>"),
i18nc("@title", "Failed to find PaperKey executable."));
return;
}
mFileName = QFileDialog::getOpenFileName(d->parentWidgetOrView(), i18n("Select input file"),
QString(),
QStringLiteral("%1 (*.txt)").arg(i18n("Paper backup"))
#ifdef Q_OS_WIN
/* For whatever reason at least with Qt 5.6.1 the native file dialog crashes in
* my (aheinecke) Windows 10 environment when invoked here.
* In other places it works, with the same arguments as in other places (e.g. import)
* it works. But not here. Maybe it's our (gpg4win) build? But why did it only
* crash here?
*
* It does not crash immediately, the program flow continues for a while before it
* crashes so this is hard to debug.
*
* There are some reports about this
* QTBUG-33119 QTBUG-41416 where different people describe "bugs" but they
* describe them differently also not really reproducible.
* Anyway this works for now and for such an exotic feature its good enough for now.
*/
, 0, QFileDialog::DontUseNativeDialog
#endif
);
if (mFileName.isEmpty()) {
d->finished();
return;
}
auto exportJob = QGpgME::openpgp()->publicKeyExportJob();
connect(exportJob, &QGpgME::ExportJob::result, this, &ImportPaperKeyCommand::exportResult);
exportJob->start(QStringList() << QLatin1String(d->key().primaryFingerprint()));
}
QString ImportPaperKeyCommand::errorCaption() const
{
return i18nc("@title:window", "Error importing secret key");
}
QString ImportPaperKeyCommand::crashExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>The GPG process that tried to restore the secret key "
"ended prematurely because of an unexpected error.</para>"
"<para>Please check the output of <icode>%1</icode> for details.</para>",
args.join(QLatin1Char(' ')));
}
QString ImportPaperKeyCommand::errorExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>An error occurred while trying to restore the secret key.</para> "
"<para>The output from <command>%1</command> was:</para>"
"<para><message>%2</message></para>",
args[0], errorString());
}
QString ImportPaperKeyCommand::successMessage(const QStringList &) const
{
return QString();
}
diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp
index e71cbd207..7c323b9be 100644
--- a/src/commands/keytocardcommand.cpp
+++ b/src/commands/keytocardcommand.cpp
@@ -1,736 +1,736 @@
/* commands/keytocardcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020,2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "keytocardcommand.h"
#include "cardcommand_p.h"
#include "authenticatepivcardapplicationcommand.h"
#include "smartcard/algorithminfo.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "smartcard/utils.h"
#include <utils/applicationstate.h>
#include <utils/filedialog.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/KeySelectionDialog>
#include <KLocalizedString>
#include <QDateTime>
#include <QDir>
#include <QInputDialog>
#include <QSaveFile>
#include <QStringList>
#include <gpg-error.h>
#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
# define GPG_ERROR_HAS_NO_AUTH
#endif
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
namespace
{
QString cardDisplayName(const std::shared_ptr<const Card> &card)
{
return i18nc("smartcard application - serial number of smartcard", "%1 - %2",
displayAppName(card->appName()), card->displaySerialNumber());
}
}
class KeyToCardCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::KeyToCardCommand;
KeyToCardCommand *q_func() const
{
return static_cast<KeyToCardCommand *>(q);
}
public:
explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey);
explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName);
private:
void start();
void startKeyToOpenPGPCard();
Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &card);
void startKeyToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
void keyToCardDone(const GpgME::Error &err);
void keyToPIVCardDone(const GpgME::Error &err);
void updateDone();
void keyHasBeenCopiedToCard();
bool backupKey();
std::vector<QByteArray> readSecretKeyFile();
bool writeSecretKeyBackup(const QString &filename, const std::vector<QByteArray> &keydata);
void startDeleteSecretKeyLocally();
void deleteSecretKeyLocallyFinished(const GpgME::Error &err);
private:
std::string appName;
GpgME::Subkey subkey;
std::string cardSlot;
bool overwriteExistingAlreadyApproved = false;
bool hasBeenCanceled = false;
QMetaObject::Connection updateConnection;
};
KeyToCardCommand::Private *KeyToCardCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const KeyToCardCommand::Private *KeyToCardCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define q q_func()
#define d d_func()
KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey_)
: CardCommand::Private(qq, "", nullptr)
, subkey(subkey_)
{
}
KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName_)
: CardCommand::Private(qq, serialNumber, nullptr)
, appName(appName_)
, cardSlot(slot)
{
}
namespace {
static std::shared_ptr<Card> getCardToTransferSubkeyTo(const Subkey &subkey, QWidget *parent)
{
const std::vector<std::shared_ptr<Card> > suitableCards = KeyToCardCommand::getSuitableCards(subkey);
if (suitableCards.empty()) {
return std::shared_ptr<Card>();
} else if (suitableCards.size() == 1) {
return suitableCards[0];
}
QStringList options;
for (const auto &card: suitableCards) {
options.push_back(cardDisplayName(card));
}
bool ok;
const QString choice = QInputDialog::getItem(parent, i18n("Select Card"),
i18n("Please select the card the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok);
if (!ok) {
return std::shared_ptr<Card>();
}
const int index = options.indexOf(choice);
return suitableCards[index];
}
}
void KeyToCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()";
if (!subkey.isNull() && serialNumber().empty()) {
const auto card = getCardToTransferSubkeyTo(subkey, parentWidgetOrView());
if (!card) {
finished();
return;
}
setSerialNumber(card->serialNumber());
appName = card->appName();
}
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (card->appName() == SmartCard::OpenPGPCard::AppName) {
startKeyToOpenPGPCard();
} else if (card->appName() == SmartCard::PIVCard::AppName) {
startKeyToPIVCard();
} else {
error(xi18nc("@info", "Sorry! Writing keys to the card <emphasis>%1</emphasis> is not supported.", cardDisplayName(card)));
finished();
return;
}
}
namespace {
static std::string getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent)
{
// Check if we need to ask the user for the slot
if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) {
// Signing only
return OpenPGPCard::pgpSigKeyRef();
}
if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) {
// Encrypt only
return OpenPGPCard::pgpEncKeyRef();
}
if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) {
// Auth only
return OpenPGPCard::pgpAuthKeyRef();
}
// Multiple uses, ask user.
QStringList options;
std::vector<std::string> cardSlots;
if (subKey.canSign() || subKey.canCertify()) {
options.push_back(i18nc("@item:inlistbox", "Signature"));
cardSlots.push_back(OpenPGPCard::pgpSigKeyRef());
}
if (subKey.canEncrypt()) {
options.push_back(i18nc("@item:inlistbox", "Encryption"));
cardSlots.push_back(OpenPGPCard::pgpEncKeyRef());
}
if (subKey.canAuthenticate()) {
options.push_back(i18nc("@item:inlistbox", "Authentication"));
cardSlots.push_back(OpenPGPCard::pgpAuthKeyRef());
}
bool ok;
const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"),
i18n("Please select the card slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok);
const int choiceIndex = options.indexOf(choice);
if (ok && choiceIndex >= 0) {
return cardSlots[choiceIndex];
} else {
return {};
}
}
}
void KeyToCardCommand::Private::startKeyToOpenPGPCard() {
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()";
const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard<OpenPGPCard>(serialNumber());
if (!pgpCard) {
error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (subkey.isNull()) {
finished();
return;
}
if (subkey.parent().protocol() != GpgME::OpenPGP) {
error(i18n("Sorry! This key cannot be transferred to an OpenPGP card."));
finished();
return;
}
cardSlot = getOpenPGPCardSlotForKey(subkey, parentWidgetOrView());
if (cardSlot.empty()) {
finished();
return;
}
// Check if we need to do the overwrite warning.
const std::string existingKey = pgpCard->keyFingerprint(cardSlot);
if (!existingKey.empty()) {
const auto encKeyWarning = (cardSlot == OpenPGPCard::pgpEncKeyRef()) ?
i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") :
QString{};
const QString message = i18nc(
"@info",
"<p>The card <em>%1</em> already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
"<p>If there is no backup the existing key will be irrecoverably lost.</p>",
cardDisplayName(pgpCard)) +
i18n("The existing key has the fingerprint:") +
QStringLiteral("<pre>%1</pre>").arg(Formatting::prettyID(existingKey.c_str())) +
encKeyWarning;
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message,
i18nc("@title:window", "Overwrite existing key"),
KGuiItem{i18nc("@action:button", "Overwrite Existing Key")}, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
finished();
return;
}
}
// Now do the deed
const auto time = QDateTime::fromSecsSinceEpoch(quint32(subkey.creationTime()), Qt::UTC);
const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss"));
const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3 %4")
.arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()), QString::fromStdString(cardSlot), timestamp);
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
keyToCardDone(err);
});
}
namespace {
static std::vector<Key> getSigningCertificates()
{
std::vector<Key> signingCertificates = KeyCache::instance()->secretKeys();
const auto it = std::remove_if(signingCertificates.begin(), signingCertificates.end(),
[](const Key &key) {
return ! (key.protocol() == GpgME::CMS &&
!key.subkey(0).isNull() &&
key.subkey(0).canSign() &&
!key.subkey(0).canEncrypt() &&
key.subkey(0).isSecret() &&
!key.subkey(0).isCardKey());
});
signingCertificates.erase(it, signingCertificates.end());
return signingCertificates;
}
static std::vector<Key> getEncryptionCertificates()
{
std::vector<Key> encryptionCertificates = KeyCache::instance()->secretKeys();
const auto it = std::remove_if(encryptionCertificates.begin(), encryptionCertificates.end(),
[](const Key &key) {
return ! (key.protocol() == GpgME::CMS &&
!key.subkey(0).isNull() &&
key.subkey(0).canEncrypt() &&
key.subkey(0).isSecret() &&
!key.subkey(0).isCardKey());
});
encryptionCertificates.erase(it, encryptionCertificates.end());
return encryptionCertificates;
}
}
Subkey KeyToCardCommand::Private::getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &/*card*/)
{
if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
return Subkey();
}
const std::vector<Key> certificates = cardSlot == PIVCard::cardAuthenticationKeyRef() ? getSigningCertificates() : getEncryptionCertificates();
if (certificates.empty()) {
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
return Subkey();
}
auto dialog = new KeySelectionDialog(parentWidgetOrView());
dialog->setWindowTitle(i18nc("@title:window", "Select Certificate"));
dialog->setText(i18n("Please select the certificate whose key pair you want to write to the card:"));
dialog->setKeys(certificates);
if (dialog->exec() == QDialog::Rejected) {
return Subkey();
}
return dialog->selectedKey().subkey(0);
}
void KeyToCardCommand::Private::startKeyToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
// key to card is only supported for the Card Authentication key and the Key Management key
finished();
return;
}
if (subkey.isNull()) {
subkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard);
}
if (subkey.isNull()) {
finished();
return;
}
if (subkey.parent().protocol() != GpgME::CMS) {
error(i18n("Sorry! This key cannot be transferred to a PIV card."));
finished();
return;
}
if (!subkey.canEncrypt() && !subkey.canSign()) {
error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card."));
finished();
return;
}
// Check if we need to do the overwrite warning.
if (!overwriteExistingAlreadyApproved) {
const std::string existingKey = pivCard->keyInfo(cardSlot).grip;
if (!existingKey.empty() && (existingKey != subkey.keyGrip())) {
const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef()) ?
i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") :
QString();
const QString message = i18nc(
"@info",
"<p>The card <em>%1</em> already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
"<p>If there is no backup the existing key will be irrecoverably lost.</p>",
cardDisplayName(pivCard)) +
i18n("The existing key has the key grip:") +
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(existingKey)) +
decryptionWarning;
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message,
i18nc("@title:window", "Overwrite existing key"),
KGuiItem{i18nc("@action:button", "Overwrite Existing Key")}, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
finished();
return;
}
overwriteExistingAlreadyApproved = true;
}
}
const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3")
.arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()))
.arg(QString::fromStdString(cardSlot));
ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
keyToPIVCardDone(err);
});
}
void KeyToCardCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
q, [this]() { authenticationFinished(); });
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
q, [this]() { authenticationCanceled(); });
cmd->start();
}
void KeyToCardCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()";
if (!hasBeenCanceled) {
startKeyToPIVCard();
}
}
void KeyToCardCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
void KeyToCardCommand::Private::updateDone()
{
disconnect(updateConnection);
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const std::string keyGripOnCard = card->keyInfo(cardSlot).grip;
if (keyGripOnCard != subkey.keyGrip()) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "KEYTOCARD succeeded, but key on card doesn't match copied key";
error(i18nc("@info", "Copying the key to the card failed."));
finished();
return;
}
keyHasBeenCopiedToCard();
}
void KeyToCardCommand::Private::keyHasBeenCopiedToCard()
{
const auto answer = KMessageBox::questionTwoActionsCancel(
parentWidgetOrView(),
xi18nc("@info",
"<para>The key has been copied to the card.</para>"
"<para>Do you want to delete the copy of the key stored on this computer?</para>"),
i18nc("@title:window", "Success"),
KGuiItem{i18nc("@action:button", "Create Backup and Delete Key")},
KGuiItem{i18nc("@action:button", "Delete Key")},
KGuiItem{i18nc("@action:button", "Keep Key")});
if (answer == KMessageBox::ButtonCode::Cancel) {
finished();
return;
}
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
if (!backupKey()) {
finished();
return;
}
}
startDeleteSecretKeyLocally();
}
namespace
{
QString gnupgPrivateKeyBackupExtension()
{
return QStringLiteral(".gpgsk");
}
QString proposeFilename(const Subkey &subkey)
{
QString filename;
const auto key = subkey.parent();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID());
const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData());
const auto usage = Formatting::usageString(subkey).replace(QLatin1String{", "}, QLatin1String{"_"});
/* Not translated so it's better to use in tutorials etc. */
filename = ((shortKeyID == shortSubkeyID) //
? QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3"}.arg(name, shortKeyID, usage)
: QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3_%4"}.arg(name, shortKeyID, shortSubkeyID, usage));
filename.replace(u'/', u'_');
return QDir{ApplicationState::lastUsedExportDirectory()}.filePath(filename + gnupgPrivateKeyBackupExtension());
}
QString requestPrivateKeyBackupFilename(const QString &proposedFilename, QWidget *parent)
{
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18nc("@title:window", "Backup Secret Key"),
QStringLiteral("imp"),
proposedFilename,
i18nc("description of filename filter", "Secret Key Backup Files") + QLatin1String{" (*.gpgsk)"});
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += gnupgPrivateKeyBackupExtension();
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
}
bool KeyToCardCommand::Private::backupKey()
{
static const QByteArray backupInfoName = "Backup-info:";
auto keydata = readSecretKeyFile();
if (keydata.empty()) {
return false;
}
const auto filename = requestPrivateKeyBackupFilename(proposeFilename(subkey), parentWidgetOrView());
if (filename.isEmpty()) {
return false;
}
// remove old backup info
Kleo::erase_if(keydata, [](const auto &line) {
return line.startsWith(backupInfoName);
});
// prepend new backup info
const QByteArrayList backupInfo = {
backupInfoName,
subkey.keyGrip(),
QDateTime::currentDateTimeUtc().toString(Qt::ISODate).toUtf8(),
"Kleopatra",
Formatting::prettyNameAndEMail(subkey.parent()).toUtf8(),
};
keydata.insert(keydata.begin(), backupInfo.join(' ') + '\n');
return writeSecretKeyBackup(filename, keydata);
}
std::vector<QByteArray> KeyToCardCommand::Private::readSecretKeyFile()
{
const auto filename = QString::fromLatin1(subkey.keyGrip()) + QLatin1String{".key"};
const auto path = QDir{Kleo::gnupgPrivateKeysDirectory()}.filePath(filename);
QFile file{path};
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
error(xi18n("Cannot open the private key file <filename>%1</filename> for reading.", path));
return {};
}
std::vector<QByteArray> lines;
while (!file.atEnd()) {
lines.push_back(file.readLine());
}
if (lines.empty()) {
error(xi18n("The private key file <filename>%1</filename> is empty.", path));
}
return lines;
}
bool KeyToCardCommand::Private::writeSecretKeyBackup(const QString &filename, const std::vector<QByteArray> &keydata)
{
QSaveFile file{filename};
// open the file in binary format because we want to write Unix line endings
if (!file.open(QIODevice::WriteOnly)) {
error(xi18n("Cannot open the file <filename>%1</filename> for writing.", filename));
return false;
}
for (const auto &line : keydata) {
file.write(line);
}
if (!file.commit()) {
error(xi18n("Writing the backup of the secret key to <filename>%1</filename> failed.", filename));
return false;
};
return true;
}
void KeyToCardCommand::Private::startDeleteSecretKeyLocally()
{
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const auto answer = KMessageBox::questionTwoActions(
parentWidgetOrView(),
xi18n("Do you really want to delete the local copy of the secret key?"),
i18nc("@title:window", "Confirm Deletion"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
if (answer != KMessageBox::ButtonCode::PrimaryAction) {
finished();
return;
}
const auto cmd = QByteArray{"DELETE_KEY --force "} + subkey.keyGrip();
ReaderStatus::mutableInstance()->startSimpleTransaction(card, cmd, q, [this](const GpgME::Error &err) {
deleteSecretKeyLocallyFinished(err);
});
}
void KeyToCardCommand::Private::deleteSecretKeyLocallyFinished(const GpgME::Error &err)
{
if (err) {
- error(xi18nc("@info", "<para>Failed to delete the key:</para><para><message>%1</message></para>", QString::fromUtf8(err.asString())));
+ error(xi18nc("@info", "<para>Failed to delete the key:</para><para><message>%1</message></para>", Formatting::errorAsString(err)));
}
ReaderStatus::mutableInstance()->updateStatus();
success(i18nc("@info", "Successfully copied the key to the card."));
finished();
}
KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &subkey)
: CardCommand(new Private(this, subkey))
{
}
KeyToCardCommand::KeyToCardCommand(const std::string& cardSlot, const std::string &serialNumber, const std::string &appName)
: CardCommand(new Private(this, cardSlot, serialNumber, appName))
{
}
KeyToCardCommand::~KeyToCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()";
}
namespace
{
bool cardSupportsKeyAlgorithm(const std::shared_ptr<const Card> &card, const std::string &keyAlgo)
{
if (card->appName() == OpenPGPCard::AppName) {
const auto pgpCard = static_cast<const OpenPGPCard *>(card.get());
const auto cardAlgos = pgpCard->supportedAlgorithms();
return Kleo::any_of(cardAlgos, [keyAlgo](const auto &algo) {
return (keyAlgo == algo.id) //
|| (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpEncKeyRef()))
|| (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpSigKeyRef()));
});
}
return false;
}
}
// static
std::vector<std::shared_ptr<Card>> KeyToCardCommand::getSuitableCards(const GpgME::Subkey &subkey)
{
std::vector<std::shared_ptr<Card>> suitableCards;
if (subkey.isNull() || subkey.parent().protocol() != GpgME::OpenPGP) {
return suitableCards;
}
const auto keyAlgo = subkey.algoName();
Kleo::copy_if(ReaderStatus::instance()->getCards(), std::back_inserter(suitableCards), [keyAlgo](const auto &card) {
return cardSupportsKeyAlgorithm(card, keyAlgo);
});
return suitableCards;
}
void KeyToCardCommand::Private::keyToCardDone(const GpgME::Error &err)
{
if (!err && !err.isCanceled()) {
updateConnection = connect(ReaderStatus::instance(), &ReaderStatus::updateFinished, q, [this]() {
updateDone();
});
ReaderStatus::mutableInstance()->updateCard(serialNumber(), appName);
return;
}
if (err) {
error(xi18nc("@info",
- "<para>Copying the key to the card failed:</para><para><message>%1</message></para>", QString::fromUtf8(err.asString())));
+ "<para>Copying the key to the card failed:</para><para><message>%1</message></para>", Formatting::errorAsString(err)));
}
finished();
}
void KeyToCardCommand::Private::keyToPIVCardDone(const GpgME::Error &err)
{
- qCDebug(KLEOPATRA_LOG) << q << __func__ << err.asString() << "(" << err.code() << ")";
+ qCDebug(KLEOPATRA_LOG) << q << __func__ << Formatting::errorAsString(err) << "(" << err.code() << ")";
#ifdef GPG_ERROR_HAS_NO_AUTH
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
authenticate();
return;
}
#endif
keyToCardDone(err);
}
void KeyToCardCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()";
d->start();
}
void KeyToCardCommand::doCancel()
{
}
#undef q_func
#undef d_func
diff --git a/src/commands/lookupcertificatescommand.cpp b/src/commands/lookupcertificatescommand.cpp
index dbe73e000..07674fe92 100644
--- a/src/commands/lookupcertificatescommand.cpp
+++ b/src/commands/lookupcertificatescommand.cpp
@@ -1,611 +1,611 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/lookupcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008, 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "lookupcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "detailscommand.h"
#include <settings.h>
#include "view/tabwidget.h"
#include <Libkleo/Compat>
#include <Libkleo/Debug>
#include <Libkleo/GnuPG>
#include <dialogs/lookupcertificatesdialog.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
#include <QGpgME/Debug>
#include <QGpgME/Protocol>
#include <QGpgME/KeyListJob>
#include <QGpgME/ImportFromKeyserverJob>
#if QGPGME_SUPPORTS_WKDLOOKUP
# include <QGpgME/WKDLookupJob>
# include <QGpgME/WKDLookupResult>
#endif
#include <gpgme++/data.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme++/importresult.h>
#include <KLocalizedString>
#include <KMessageBox>
#include "kleopatra_debug.h"
#include <QRegularExpression>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
using namespace QGpgME;
class LookupCertificatesCommand::Private : public ImportCertificatesCommand::Private
{
friend class ::Kleo::Commands::LookupCertificatesCommand;
LookupCertificatesCommand *q_func() const
{
return static_cast<LookupCertificatesCommand *>(q);
}
public:
explicit Private(LookupCertificatesCommand *qq, KeyListController *c);
~Private() override;
void init();
private:
void slotSearchTextChanged(const QString &str);
void slotNextKey(const Key &key);
void slotKeyListResult(const KeyListResult &result);
#if QGPGME_SUPPORTS_WKDLOOKUP
void slotWKDLookupResult(const WKDLookupResult &result);
#endif
void tryToFinishKeyLookup();
void slotImportRequested(const std::vector<Key> &keys);
void slotDetailsRequested(const Key &key);
void slotSaveAsRequested(const std::vector<Key> &keys);
void slotDialogRejected()
{
canceled();
}
private:
using ImportCertificatesCommand::Private::showError;
void showError(QWidget *parent, const KeyListResult &result);
void showResult(QWidget *parent, const KeyListResult &result);
void createDialog();
KeyListJob *createKeyListJob(GpgME::Protocol proto) const
{
const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
return cbp ? cbp->keyListJob(true) : nullptr;
}
#if QGPGME_SUPPORTS_WKDLOOKUP
WKDLookupJob *createWKDLookupJob() const
{
const auto cbp = QGpgME::openpgp();
return cbp ? cbp->wkdLookupJob() : nullptr;
}
#endif
ImportFromKeyserverJob *createImportJob(GpgME::Protocol proto) const
{
const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
return cbp ? cbp->importFromKeyserverJob() : nullptr;
}
void startKeyListJob(GpgME::Protocol proto, const QString &str);
#if QGPGME_SUPPORTS_WKDLOOKUP
void startWKDLookupJob(const QString &str);
#endif
bool checkConfig() const;
QWidget *dialogOrParentWidgetOrView() const
{
if (dialog) {
return dialog;
} else {
return parentWidgetOrView();
}
}
private:
GpgME::Protocol protocol = GpgME::UnknownProtocol;
QString query;
bool autoStartLookup = false;
QPointer<LookupCertificatesDialog> dialog;
struct KeyListingVariables {
QPointer<KeyListJob> cms, openpgp;
#if QGPGME_SUPPORTS_WKDLOOKUP
QPointer<WKDLookupJob> wkdJob;
#endif
QString pattern;
KeyListResult result;
std::vector<Key> keys;
int numKeysWithoutUserId = 0;
std::set<std::string> wkdKeyFingerprints;
QByteArray wkdKeyData;
QString wkdSource;
bool cmsKeysHaveNoFingerprints = false;
bool openPgpKeysHaveNoFingerprints = false;
void reset()
{
*this = KeyListingVariables();
}
} keyListing;
};
LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
LookupCertificatesCommand::Private::Private(LookupCertificatesCommand *qq, KeyListController *c)
: ImportCertificatesCommand::Private(qq, c),
dialog()
{
if (!Settings{}.cmsEnabled()) {
protocol = GpgME::OpenPGP;
}
}
LookupCertificatesCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
delete dialog;
}
LookupCertificatesCommand::LookupCertificatesCommand(KeyListController *c)
: ImportCertificatesCommand(new Private(this, c))
{
d->init();
}
LookupCertificatesCommand::LookupCertificatesCommand(const QString &query, KeyListController *c)
: ImportCertificatesCommand(new Private(this, c))
{
d->init();
d->query = query;
d->autoStartLookup = true;
}
LookupCertificatesCommand::LookupCertificatesCommand(QAbstractItemView *v, KeyListController *c)
: ImportCertificatesCommand(v, new Private(this, c))
{
d->init();
if (c->tabWidget()) {
d->query = c->tabWidget()->stringFilter();
// do not start the lookup automatically to prevent unwanted leaking
// of information
}
}
void LookupCertificatesCommand::Private::init()
{
}
LookupCertificatesCommand::~LookupCertificatesCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void LookupCertificatesCommand::setProtocol(GpgME::Protocol protocol)
{
d->protocol = protocol;
}
GpgME::Protocol LookupCertificatesCommand::protocol() const
{
return d->protocol;
}
void LookupCertificatesCommand::doStart()
{
if (!d->checkConfig()) {
d->finished();
return;
}
d->createDialog();
Q_ASSERT(d->dialog);
// if we have a prespecified query, load it into find field
// and start the search, if auto-start is enabled
if (!d->query.isEmpty()) {
d->dialog->setSearchText(d->query);
if (d->autoStartLookup) {
d->slotSearchTextChanged(d->query);
}
} else {
d->dialog->setPassive(false);
}
d->dialog->show();
}
void LookupCertificatesCommand::Private::createDialog()
{
if (dialog) {
return;
}
dialog = new LookupCertificatesDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &LookupCertificatesDialog::searchTextChanged, q, [this](const QString &text) { slotSearchTextChanged(text); });
using CertsVec = std::vector<GpgME::Key>;
connect(dialog, &LookupCertificatesDialog::saveAsRequested, q, [this](const CertsVec &certs) { slotSaveAsRequested(certs); });
connect(dialog, &LookupCertificatesDialog::importRequested, q, [this](const CertsVec &certs) { slotImportRequested(certs); });
connect(dialog, &LookupCertificatesDialog::detailsRequested, q, [this](const GpgME::Key &gpgKey) { slotDetailsRequested(gpgKey); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
static auto searchTextToEmailAddress(const QString &s)
{
return QString::fromStdString(UserID::addrSpecFromString(s.toStdString().c_str()));
}
void LookupCertificatesCommand::Private::slotSearchTextChanged(const QString &str)
{
// pressing return might trigger both search and dialog destruction (search focused and default key set)
// On Windows, the dialog is then destroyed before this slot is called
if (dialog) { //thus test
dialog->setPassive(true);
dialog->setCertificates(std::vector<Key>());
dialog->showInformation({});
}
query = str;
keyListing.reset();
keyListing.pattern = str;
if (protocol != GpgME::OpenPGP) {
startKeyListJob(CMS, str);
}
if (protocol != GpgME::CMS) {
static const QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1String("(?:0x|0X)?[0-9a-fA-F]{6,}")));
if (rx.match(query).hasMatch() && !str.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
qCDebug(KLEOPATRA_LOG) << "Adding 0x prefix to query";
startKeyListJob(OpenPGP, QStringLiteral("0x") + str);
} else {
startKeyListJob(OpenPGP, str);
#if QGPGME_SUPPORTS_WKDLOOKUP
if (str.contains(QLatin1Char{'@'}) && !searchTextToEmailAddress(str).isEmpty()) {
startWKDLookupJob(str);
}
#endif
}
}
}
void LookupCertificatesCommand::Private::startKeyListJob(GpgME::Protocol proto, const QString &str)
{
KeyListJob *const klj = createKeyListJob(proto);
if (!klj) {
return;
}
connect(klj, &QGpgME::KeyListJob::result, q, [this](const GpgME::KeyListResult &result) { slotKeyListResult(result); });
connect(klj, &QGpgME::KeyListJob::nextKey, q, [this](const GpgME::Key &key) { slotNextKey(key); });
if (const Error err = klj->start(QStringList(str))) {
keyListing.result.mergeWith(KeyListResult(err));
} else if (proto == CMS) {
keyListing.cms = klj;
} else {
keyListing.openpgp = klj;
}
}
#if QGPGME_SUPPORTS_WKDLOOKUP
void LookupCertificatesCommand::Private::startWKDLookupJob(const QString &str)
{
const auto job = createWKDLookupJob();
if (!job) {
qCDebug(KLEOPATRA_LOG) << "Failed to create WKDLookupJob";
return;
}
connect(job, &WKDLookupJob::result,
q, [this](const WKDLookupResult &result) { slotWKDLookupResult(result); });
if (const Error err = job->start(str)) {
keyListing.result.mergeWith(KeyListResult{err});
} else {
keyListing.wkdJob = job;
}
}
#endif
void LookupCertificatesCommand::Private::slotNextKey(const Key &key)
{
if (!key.primaryFingerprint()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without fingerprint" << key;
if (q->sender() == keyListing.cms) {
keyListing.cmsKeysHaveNoFingerprints = true;
} else if (q->sender() == keyListing.openpgp) {
keyListing.openPgpKeysHaveNoFingerprints = true;
}
} else if (key.numUserIDs() == 0) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without user IDs" << key;
keyListing.numKeysWithoutUserId++;
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "got key" << key;
keyListing.keys.push_back(key);
}
}
void LookupCertificatesCommand::Private::slotKeyListResult(const KeyListResult &r)
{
if (q->sender() == keyListing.cms) {
keyListing.cms = nullptr;
} else if (q->sender() == keyListing.openpgp) {
keyListing.openpgp = nullptr;
} else {
qCDebug(KLEOPATRA_LOG) << "unknown sender()" << q->sender();
}
keyListing.result.mergeWith(r);
tryToFinishKeyLookup();
}
#if QGPGME_SUPPORTS_WKDLOOKUP
static auto removeKeysNotMatchingEmail(const std::vector<Key> &keys, const std::string &email)
{
std::vector<Key> filteredKeys;
const auto addrSpec = UserID::addrSpecFromString(email.c_str());
std::copy_if(std::begin(keys), std::end(keys),
std::back_inserter(filteredKeys),
[addrSpec](const auto &key) {
const auto uids = key.userIDs();
return std::any_of(std::begin(uids), std::end(uids),
[addrSpec](const auto &uid) {
return uid.addrSpec() == addrSpec;
});
});
return filteredKeys;
}
void LookupCertificatesCommand::Private::slotWKDLookupResult(const WKDLookupResult &result)
{
if (q->sender() == keyListing.wkdJob) {
keyListing.wkdJob = nullptr;
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "unknown sender()" << q->sender();
}
// we do not want to bother the user with errors during the WKD lookup;
// therefore, we log the result, but we do not merge it into keyListing.result
qCDebug(KLEOPATRA_LOG) << "Result of WKD lookup:" << result.error();
const auto keys = removeKeysNotMatchingEmail(result.keyData().toKeys(GpgME::OpenPGP), result.pattern());
if (!keys.empty()) {
keyListing.wkdKeyData = QByteArray::fromStdString(result.keyData().toString());
keyListing.wkdSource = QString::fromStdString(result.source());
std::copy(std::begin(keys), std::end(keys),
std::back_inserter(keyListing.keys));
// remember the keys retrieved via WKD for import
std::transform(std::begin(keys), std::end(keys),
std::inserter(keyListing.wkdKeyFingerprints, std::begin(keyListing.wkdKeyFingerprints)),
[](const auto &k) { return k.primaryFingerprint(); });
}
tryToFinishKeyLookup();
}
#endif
namespace
{
void showKeysWithoutFingerprintsNotification(QWidget *parent, GpgME::Protocol protocol)
{
if (protocol != GpgME::CMS && protocol != GpgME::OpenPGP) {
return;
}
QString message;
if (protocol == GpgME::CMS) {
message = xi18nc("@info",
"<para>One of the X.509 directory services returned certificates without "
"fingerprints. Those certificates are ignored because fingerprints "
"are required as unique identifiers for certificates.</para>"
"<para>You may want to configure a different X.509 directory service "
"in the configuration dialog.</para>");
} else {
message = xi18nc("@info",
"<para>The OpenPGP keyserver returned certificates without "
"fingerprints. Those certificates are ignored because fingerprints "
"are required as unique identifiers for certificates.</para>"
"<para>You may want to configure a different OpenPGP keyserver "
"in the configuration dialog.</para>");
}
KMessageBox::information(parent, message, i18nc("@title", "Invalid Server Reply"),
QStringLiteral("certificates-lookup-missing-fingerprints"));
}
}
void LookupCertificatesCommand::Private::tryToFinishKeyLookup()
{
if (keyListing.cms || keyListing.openpgp
#if QGPGME_SUPPORTS_WKDLOOKUP
|| keyListing.wkdJob
#endif
) {
// still waiting for jobs to complete
return;
}
if (keyListing.result.error() && !keyListing.result.error().isCanceled()) {
showError(dialog, keyListing.result);
}
if (keyListing.result.isTruncated()) {
showResult(dialog, keyListing.result);
}
if (keyListing.cmsKeysHaveNoFingerprints) {
showKeysWithoutFingerprintsNotification(dialog, GpgME::CMS);
}
if (keyListing.openPgpKeysHaveNoFingerprints) {
showKeysWithoutFingerprintsNotification(dialog, GpgME::OpenPGP);
}
if (dialog) {
dialog->setPassive(false);
dialog->setCertificates(keyListing.keys);
if (keyListing.numKeysWithoutUserId > 0) {
dialog->showInformation(i18ncp("@info",
"One certificate without name and email address was ignored.",
"%1 certificates without name and email address were ignored.",
keyListing.numKeysWithoutUserId));
}
} else {
finished();
}
}
void LookupCertificatesCommand::Private::slotImportRequested(const std::vector<Key> &keys)
{
dialog = nullptr;
Q_ASSERT(!keys.empty());
Q_ASSERT(std::none_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.isNull(); }));
std::vector<Key> wkdKeys, otherKeys;
otherKeys.reserve(keys.size());
kdtools::separate_if(std::begin(keys), std::end(keys),
std::back_inserter(wkdKeys),
std::back_inserter(otherKeys),
[this](const auto &key) {
return key.primaryFingerprint() &&
keyListing.wkdKeyFingerprints.find(key.primaryFingerprint()) != std::end(keyListing.wkdKeyFingerprints);
});
std::vector<Key> pgp, cms;
pgp.reserve(otherKeys.size());
cms.reserve(otherKeys.size());
kdtools::separate_if(otherKeys.begin(), otherKeys.end(),
std::back_inserter(pgp),
std::back_inserter(cms),
[](const Key &key) {
return key.protocol() == GpgME::OpenPGP;
});
setWaitForMoreJobs(true);
if (!wkdKeys.empty()) {
// set an import filter, so that only user IDs matching the email address used for the WKD lookup are imported
const QString importFilter = QLatin1String{"keep-uid=mbox = "} + searchTextToEmailAddress(keyListing.pattern);
startImport(OpenPGP, keyListing.wkdKeyData, keyListing.wkdSource,
{importFilter, Key::OriginWKD, keyListing.wkdSource});
}
if (!pgp.empty()) {
startImport(OpenPGP, pgp,
i18nc(R"(@title %1:"OpenPGP" or "S/MIME")",
"%1 Certificate Server",
Formatting::displayName(OpenPGP)));
}
if (!cms.empty()) {
startImport(CMS, cms,
i18nc(R"(@title %1:"OpenPGP" or "S/MIME")",
"%1 Certificate Server",
Formatting::displayName(CMS)));
}
setWaitForMoreJobs(false);
}
void LookupCertificatesCommand::Private::slotSaveAsRequested(const std::vector<Key> &keys)
{
Q_UNUSED(keys)
qCDebug(KLEOPATRA_LOG) << "not implemented";
}
void LookupCertificatesCommand::Private::slotDetailsRequested(const Key &key)
{
Command *const cmd = new DetailsCommand(key);
cmd->setParentWidget(dialogOrParentWidgetOrView());
cmd->start();
}
void LookupCertificatesCommand::doCancel()
{
ImportCertificatesCommand::doCancel();
if (QDialog *const dlg = d->dialog) {
d->dialog = nullptr;
dlg->close();
}
}
void LookupCertificatesCommand::Private::showError(QWidget *parent, const KeyListResult &result)
{
if (!result.error()) {
return;
}
KMessageBox::information(parent, i18nc("@info",
"Failed to search on certificate server. The error returned was:\n%1",
- QString::fromLocal8Bit(result.error().asString())));
+ Formatting::errorAsString(result.error())));
}
void LookupCertificatesCommand::Private::showResult(QWidget *parent, const KeyListResult &result)
{
if (result.isTruncated())
KMessageBox::information(parent,
xi18nc("@info",
"<para>The query result has been truncated.</para>"
"<para>Either the local or a remote limit on "
"the maximum number of returned hits has "
"been exceeded.</para>"
"<para>You can try to increase the local limit "
"in the configuration dialog, but if one "
"of the configured servers is the limiting "
"factor, you have to refine your search.</para>"),
i18nc("@title", "Result Truncated"),
QStringLiteral("lookup-certificates-truncated-result"));
}
bool LookupCertificatesCommand::Private::checkConfig() const
{
const bool haveOrDontNeedOpenPGPServer = haveKeyserverConfigured() || (protocol == GpgME::CMS);
const bool haveOrDontNeedCMSServer = haveX509DirectoryServerConfigured() || (protocol == GpgME::OpenPGP);
const bool ok = haveOrDontNeedOpenPGPServer || haveOrDontNeedCMSServer;
if (!ok)
information(xi18nc("@info",
"<para>You do not have any directory servers configured.</para>"
"<para>You need to configure at least one directory server to "
"search on one.</para>"
"<para>You can configure directory servers here: "
"<interface>Settings->Configure Kleopatra</interface>.</para>"),
i18nc("@title", "No Directory Servers Configured"));
return ok;
}
#undef d
#undef q
#include "moc_lookupcertificatescommand.cpp"
diff --git a/src/commands/newopenpgpcertificatecommand.cpp b/src/commands/newopenpgpcertificatecommand.cpp
index eaac4bb69..f577cfeac 100644
--- a/src/commands/newopenpgpcertificatecommand.cpp
+++ b/src/commands/newopenpgpcertificatecommand.cpp
@@ -1,263 +1,263 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/newopenpgpcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "newopenpgpcertificatecommand.h"
#include "command_p.h"
#include "dialogs/newopenpgpcertificatedetailsdialog.h"
#include "utils/emptypassphraseprovider.h"
#include "utils/keyparameters.h"
#include "utils/userinfo.h"
#include <settings.h>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include <QGpgME/KeyGenerationJob>
#include <QGpgME/Protocol>
#include <QProgressDialog>
#include <gpgme++/context.h>
#include <gpgme++/keygenerationresult.h>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace GpgME;
class NewOpenPGPCertificateCommand::Private : public Command::Private
{
friend class ::Kleo::NewOpenPGPCertificateCommand;
NewOpenPGPCertificateCommand *q_func() const
{
return static_cast<NewOpenPGPCertificateCommand *>(q);
}
public:
explicit Private(NewOpenPGPCertificateCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
void getCertificateDetails();
void createCertificate();
void showResult(const KeyGenerationResult &result);
void showErrorDialog(const KeyGenerationResult &result);
private:
KeyParameters keyParameters;
bool protectKeyWithPassword = false;
EmptyPassphraseProvider emptyPassphraseProvider;
QPointer<NewOpenPGPCertificateDetailsDialog> detailsDialog;
QPointer<QGpgME::Job> job;
QPointer<QProgressDialog> progressDialog;
};
NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
void NewOpenPGPCertificateCommand::Private::getCertificateDetails()
{
detailsDialog = new NewOpenPGPCertificateDetailsDialog;
detailsDialog->setAttribute(Qt::WA_DeleteOnClose);
applyWindowID(detailsDialog);
if (keyParameters.protocol() == KeyParameters::NoProtocol) {
const auto settings = Kleo::Settings{};
const KConfigGroup config{KSharedConfig::openConfig(), "CertificateCreationWizard"};
// prefer the last used name and email address over the values retrieved from the system
detailsDialog->setName(config.readEntry("NAME", QString{}));
if (detailsDialog->name().isEmpty() && settings.prefillName()) {
detailsDialog->setName(userFullName());
}
detailsDialog->setEmail(config.readEntry("EMAIL", QString{}));
if (detailsDialog->email().isEmpty() && settings.prefillEmail()) {
detailsDialog->setEmail(userEmailAddress());
}
} else {
detailsDialog->setKeyParameters(keyParameters);
detailsDialog->setProtectKeyWithPassword(protectKeyWithPassword);
}
connect(detailsDialog, &QDialog::accepted, q, [this]() {
keyParameters = detailsDialog->keyParameters();
protectKeyWithPassword = detailsDialog->protectKeyWithPassword();
QMetaObject::invokeMethod(
q,
[this] {
createCertificate();
},
Qt::QueuedConnection);
});
connect(detailsDialog, &QDialog::rejected, q, [this]() {
canceled();
});
detailsDialog->show();
}
void NewOpenPGPCertificateCommand::Private::createCertificate()
{
Q_ASSERT(keyParameters.protocol() == KeyParameters::OpenPGP);
auto keyGenJob = QGpgME::openpgp()->keyGenerationJob();
if (!keyGenJob) {
finished();
return;
}
if (!protectKeyWithPassword) {
auto ctx = QGpgME::Job::context(keyGenJob);
ctx->setPassphraseProvider(&emptyPassphraseProvider);
ctx->setPinentryMode(Context::PinentryLoopback);
}
connect(keyGenJob, &QGpgME::KeyGenerationJob::result, q, [this](const KeyGenerationResult &result) {
QMetaObject::invokeMethod(
q,
[this, result] {
showResult(result);
},
Qt::QueuedConnection);
});
if (const Error err = keyGenJob->start(keyParameters.toString())) {
- error(i18n("Could not start key pair creation: %1", QString::fromUtf8(err.asString())));
+ error(i18n("Could not start key pair creation: %1", Formatting::errorAsString(err)));
finished();
return;
} else {
job = keyGenJob;
}
progressDialog = new QProgressDialog;
progressDialog->setAttribute(Qt::WA_DeleteOnClose);
applyWindowID(progressDialog);
progressDialog->setModal(true);
progressDialog->setWindowTitle(i18nc("@title", "Creating Key Pair..."));
progressDialog->setLabelText(i18n("The process of creating a key requires large amounts of random numbers. This may require several minutes..."));
progressDialog->setRange(0, 0);
connect(progressDialog, &QProgressDialog::canceled, job, &QGpgME::Job::slotCancel);
connect(job, &QGpgME::Job::done, q, [this]() {
if (progressDialog) {
progressDialog->accept();
}
});
progressDialog->show();
}
void NewOpenPGPCertificateCommand::Private::showResult(const KeyGenerationResult &result)
{
if (result.error().isCanceled()) {
finished();
return;
}
// Ensure that we have the key in the cache
Key key;
if (!result.error().code() && result.fingerprint()) {
std::unique_ptr<Context> ctx{Context::createForProtocol(OpenPGP)};
if (ctx) {
Error err;
key = ctx->key(result.fingerprint(), err, /*secret=*/true);
if (!key.isNull()) {
KeyCache::mutableInstance()->insert(key);
}
}
}
if (!key.isNull()) {
success(
xi18n("<para>A new OpenPGP certificate was created successfully.</para>"
"<para>Fingerprint of the new certificate: %1</para>",
Formatting::prettyID(key.primaryFingerprint())));
finished();
} else {
showErrorDialog(result);
}
}
void NewOpenPGPCertificateCommand::Private::showErrorDialog(const KeyGenerationResult &result)
{
QString text;
if (result.error() || !result.fingerprint()) {
text = xi18n(
"<para>The creation of a new OpenPGP certificate failed.</para>"
"<para>Error: <message>%1</message></para>",
- QString::fromLocal8Bit(result.error().asString()));
+ Formatting::errorAsString(result.error()));
} else {
// no error and we have a fingerprint, but there was no corresponding key in the key ring
text = xi18n(
"<para>A new OpenPGP certificate was created successfully, but it has not been found in the key ring.</para>"
"<para>Fingerprint of the new certificate:<nl/>%1</para>",
Formatting::prettyID(result.fingerprint()));
}
auto dialog = new QDialog;
applyWindowID(dialog);
dialog->setWindowTitle(i18nc("@title:window", "Error"));
auto buttonBox = new QDialogButtonBox{QDialogButtonBox::Retry | QDialogButtonBox::Ok, dialog};
const auto buttonCode = KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Critical, text, {}, {}, nullptr, {});
if (buttonCode == QDialogButtonBox::Retry) {
QMetaObject::invokeMethod(
q,
[this]() {
getCertificateDetails();
},
Qt::QueuedConnection);
} else {
finished();
}
}
NewOpenPGPCertificateCommand::NewOpenPGPCertificateCommand()
: NewOpenPGPCertificateCommand(nullptr, nullptr)
{
}
NewOpenPGPCertificateCommand::NewOpenPGPCertificateCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
}
NewOpenPGPCertificateCommand::~NewOpenPGPCertificateCommand() = default;
void NewOpenPGPCertificateCommand::doStart()
{
d->getCertificateDetails();
}
void NewOpenPGPCertificateCommand::doCancel()
{
if (d->detailsDialog) {
d->detailsDialog->close();
}
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
diff --git a/src/commands/openpgpgeneratecardkeycommand.cpp b/src/commands/openpgpgeneratecardkeycommand.cpp
index 6bd756fbf..1c0d0e334 100644
--- a/src/commands/openpgpgeneratecardkeycommand.cpp
+++ b/src/commands/openpgpgeneratecardkeycommand.cpp
@@ -1,202 +1,204 @@
/* commands/openpgpgeneratecardkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "openpgpgeneratecardkeycommand.h"
#include "cardcommand_p.h"
#include "smartcard/algorithminfo.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/readerstatus.h"
#include "dialogs/gencardkeydialog.h"
+#include <Libkleo/Formatting>
+
#include <KLocalizedString>
#include <QGpgME/Debug>
#include <gpgme++/error.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
class OpenPGPGenerateCardKeyCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::OpenPGPGenerateCardKeyCommand;
OpenPGPGenerateCardKeyCommand *q_func() const
{
return static_cast<OpenPGPGenerateCardKeyCommand *>(q);
}
public:
explicit Private(OpenPGPGenerateCardKeyCommand *qq, const std::string &keyref, const std::string &serialNumber, QWidget *p);
void init();
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void ensureDialogCreated();
void generateKey();
private:
std::string keyRef;
bool overwriteExistingKey = false;
std::string algorithm;
QPointer<GenCardKeyDialog> dialog;
};
OpenPGPGenerateCardKeyCommand::Private *OpenPGPGenerateCardKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const OpenPGPGenerateCardKeyCommand::Private *OpenPGPGenerateCardKeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
OpenPGPGenerateCardKeyCommand::Private::Private(OpenPGPGenerateCardKeyCommand *qq, const std::string &keyRef_, const std::string &serialNumber, QWidget *p)
: CardCommand::Private(qq, serialNumber, p)
, keyRef{keyRef_}
{
}
void OpenPGPGenerateCardKeyCommand::Private::init()
{
}
void OpenPGPGenerateCardKeyCommand::Private::slotDialogAccepted()
{
algorithm = dialog->getKeyParams().algorithm;
generateKey();
}
void OpenPGPGenerateCardKeyCommand::Private::slotDialogRejected()
{
finished();
}
void OpenPGPGenerateCardKeyCommand::Private::slotResult(const GpgME::Error& err)
{
qCDebug(KLEOPATRA_LOG).nospace() << q << "::Private::" << __func__ << err;
if (err) {
- error(i18nc("@info", "Generating key failed: %1", QString::fromLatin1(err.asString())));
+ error(i18nc("@info", "Generating key failed: %1", Formatting::errorAsString(err)));
} else if (!err.isCanceled()) {
success(i18nc("@info", "Key successfully generated."));
ReaderStatus::mutableInstance()->updateStatus();
}
finished();
}
void OpenPGPGenerateCardKeyCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new GenCardKeyDialog(GenCardKeyDialog::KeyAlgorithm, parentWidgetOrView());
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
void OpenPGPGenerateCardKeyCommand::Private::generateKey()
{
qCDebug(KLEOPATRA_LOG).nospace() << q << "::Private::" << __func__;
auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(serialNumber());
if (!pgpCard) {
error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
QByteArrayList command;
command << "SCD GENKEY";
if (overwriteExistingKey) {
command << "--force";
}
if (!algorithm.empty()) {
command << "--algo=" + QByteArray::fromStdString(OpenPGPCard::getAlgorithmName(algorithm, keyRef));
}
command << "--" << QByteArray::fromStdString(keyRef);
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command.join(' '), q, [this](const GpgME::Error &err) {
slotResult(err);
});
}
OpenPGPGenerateCardKeyCommand::OpenPGPGenerateCardKeyCommand(const std::string &keyref, const std::string &serialNumber, QWidget *p)
: CardCommand(new Private(this, keyref, serialNumber, p))
{
d->init();
}
OpenPGPGenerateCardKeyCommand::~OpenPGPGenerateCardKeyCommand()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
}
void OpenPGPGenerateCardKeyCommand::doStart()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
// check if key exists
auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(d->serialNumber());
if (!pgpCard) {
d->error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(d->serialNumber())));
d->finished();
return;
}
auto existingKey = pgpCard->keyInfo(d->keyRef).grip;
if (!existingKey.empty()) {
const QString warningText = i18nc("@info",
"<p>This card already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
"<p>If there is no backup the existing key will be irrecoverably lost.</p>") +
i18n("The existing key has the ID:") + QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(existingKey)) +
(d->keyRef == OpenPGPCard::pgpEncKeyRef() ?
i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") :
QString());
const auto choice = KMessageBox::warningContinueCancel(d->parentWidgetOrView(), warningText,
i18nc("@title:window", "Overwrite Existing Key"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
d->finished();
return;
}
d->overwriteExistingKey = true;
}
d->ensureDialogCreated();
Q_ASSERT(d->dialog);
d->dialog->setSupportedAlgorithms(pgpCard->supportedAlgorithms(), "rsa2048");
d->dialog->show();
}
void OpenPGPGenerateCardKeyCommand::doCancel()
{
}
#undef d
#undef q
#include "moc_openpgpgeneratecardkeycommand.cpp"
diff --git a/src/commands/pivgeneratecardkeycommand.cpp b/src/commands/pivgeneratecardkeycommand.cpp
index a28d5a0c0..c5367bf5f 100644
--- a/src/commands/pivgeneratecardkeycommand.cpp
+++ b/src/commands/pivgeneratecardkeycommand.cpp
@@ -1,256 +1,258 @@
/* commands/pivgeneratecardkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pivgeneratecardkeycommand.h"
#include "cardcommand_p.h"
#include "smartcard/algorithminfo.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "commands/authenticatepivcardapplicationcommand.h"
#include "dialogs/gencardkeydialog.h"
+#include <Libkleo/Formatting>
+
#include <KLocalizedString>
#include <gpgme++/error.h>
#include <gpg-error.h>
#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
# define GPG_ERROR_HAS_NO_AUTH
#endif
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
class PIVGenerateCardKeyCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::PIVGenerateCardKeyCommand;
PIVGenerateCardKeyCommand *q_func() const
{
return static_cast<PIVGenerateCardKeyCommand *>(q);
}
public:
explicit Private(PIVGenerateCardKeyCommand *qq, const std::string &serialNumber, QWidget *p);
~Private() override;
void init();
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void authenticate();
void authenticationFinished();
void authenticationCanceled();
void generateKey();
void ensureDialogCreated();
private:
std::string keyRef;
bool overwriteExistingKey = false;
std::string algorithm;
QPointer<GenCardKeyDialog> dialog;
bool hasBeenCanceled = false;
};
PIVGenerateCardKeyCommand::Private *PIVGenerateCardKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const PIVGenerateCardKeyCommand::Private *PIVGenerateCardKeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
PIVGenerateCardKeyCommand::Private::Private(PIVGenerateCardKeyCommand *qq, const std::string &serialNumber, QWidget *p)
: CardCommand::Private(qq, serialNumber, p)
, dialog()
{
}
PIVGenerateCardKeyCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::Private::~Private()";
}
PIVGenerateCardKeyCommand::PIVGenerateCardKeyCommand(const std::string &serialNumber, QWidget *p)
: CardCommand(new Private(this, serialNumber, p))
{
d->init();
}
void PIVGenerateCardKeyCommand::Private::init()
{
}
PIVGenerateCardKeyCommand::~PIVGenerateCardKeyCommand()
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::~PIVGenerateCardKeyCommand()";
}
void PIVGenerateCardKeyCommand::setKeyRef(const std::string &keyRef)
{
d->keyRef = keyRef;
}
void PIVGenerateCardKeyCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::doStart()";
// check if key exists
auto pivCard = ReaderStatus::instance()->getCard<PIVCard>(d->serialNumber());
if (!pivCard) {
d->error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(d->serialNumber())));
d->finished();
return;
}
auto existingKey = pivCard->keyInfo(d->keyRef).grip;
if (!existingKey.empty()) {
const QString warningText = i18nc("@info",
"<p>This card already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
"<p>If there is no backup the existing key will be irrecoverably lost.</p>") +
i18n("The existing key has the ID:") + QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(existingKey)) +
(d->keyRef == PIVCard::keyManagementKeyRef() ?
i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") :
QString());
const auto choice = KMessageBox::warningContinueCancel(d->parentWidgetOrView(), warningText,
i18nc("@title:window", "Overwrite existing key"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
d->finished();
return;
}
d->overwriteExistingKey = true;
}
d->ensureDialogCreated();
Q_ASSERT(d->dialog);
d->dialog->show();
}
void PIVGenerateCardKeyCommand::doCancel()
{
}
void PIVGenerateCardKeyCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
q, [this]() { authenticationFinished(); });
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
q, [this]() { authenticationCanceled(); });
cmd->start();
}
void PIVGenerateCardKeyCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::authenticationFinished()";
if (!hasBeenCanceled) {
generateKey();
}
}
void PIVGenerateCardKeyCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
void PIVGenerateCardKeyCommand::Private::generateKey()
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::generateKey()";
auto pivCard = ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
QByteArrayList command;
command << "SCD GENKEY";
if (overwriteExistingKey) {
command << "--force";
}
if (!algorithm.empty()) {
command << "--algo=" + QByteArray::fromStdString(algorithm);
}
command << "--" << QByteArray::fromStdString(keyRef);
ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, command.join(' '), q, [this](const GpgME::Error &err) {
slotResult(err);
});
}
void PIVGenerateCardKeyCommand::Private::slotResult(const GpgME::Error& err)
{
qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::slotResult():"
- << err.asString() << "(" << err.code() << ")";
+ << Formatting::errorAsString(err) << "(" << err.code() << ")";
if (err) {
#ifdef GPG_ERROR_HAS_NO_AUTH
if (err.code() == GPG_ERR_NO_AUTH) {
authenticate();
return;
}
#endif
- error(i18nc("@info", "Generating key failed: %1", QString::fromLatin1(err.asString())));
+ error(i18nc("@info", "Generating key failed: %1", Formatting::errorAsString(err)));
} else if (!err.isCanceled()) {
success(i18nc("@info", "Key successfully generated."));
ReaderStatus::mutableInstance()->updateStatus();
}
finished();
}
void PIVGenerateCardKeyCommand::Private::slotDialogAccepted()
{
algorithm = dialog->getKeyParams().algorithm;
// assume that we are already authenticated to the card
generateKey();
}
void PIVGenerateCardKeyCommand::Private::slotDialogRejected()
{
finished();
}
void PIVGenerateCardKeyCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new GenCardKeyDialog(GenCardKeyDialog::KeyAlgorithm, parentWidgetOrView());
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setSupportedAlgorithms(PIVCard::supportedAlgorithms(keyRef), "rsa2048");
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
#undef d
#undef q
#include "moc_pivgeneratecardkeycommand.cpp"
diff --git a/src/commands/refreshcertificatecommand.cpp b/src/commands/refreshcertificatecommand.cpp
index 7d0a1e7d4..c4c728bae 100644
--- a/src/commands/refreshcertificatecommand.cpp
+++ b/src/commands/refreshcertificatecommand.cpp
@@ -1,303 +1,305 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/refreshcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "refreshcertificatecommand.h"
#include "command_p.h"
+#include <Libkleo/Formatting>
+
#include <KLocalizedString>
#include <KMessageBox>
#include <QGpgME/Protocol>
#if QGPGME_SUPPORTS_KEY_REFRESH
#include <QGpgME/RefreshKeysJob>
#include <QGpgME/ReceiveKeysJob>
#endif
#include <gpgme++/importresult.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace GpgME;
class RefreshCertificateCommand::Private : public Command::Private
{
friend class ::RefreshCertificateCommand;
RefreshCertificateCommand *q_func() const
{
return static_cast<RefreshCertificateCommand *>(q);
}
public:
explicit Private(RefreshCertificateCommand *qq);
~Private() override;
void start();
void cancel();
#if QGPGME_SUPPORTS_KEY_REFRESH
std::unique_ptr<QGpgME::ReceiveKeysJob> startOpenPGPJob();
std::unique_ptr<QGpgME::RefreshKeysJob> startSMIMEJob();
#endif
void onOpenPGPJobResult(const ImportResult &result);
void onSMIMEJobResult(const Error &err);
void showError(const Error &err);
private:
Key key;
#if QGPGME_SUPPORTS_KEY_REFRESH
QPointer<QGpgME::Job> job;
#endif
};
RefreshCertificateCommand::Private *RefreshCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RefreshCertificateCommand::Private *RefreshCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
RefreshCertificateCommand::Private::Private(RefreshCertificateCommand *qq)
: Command::Private{qq}
{
}
RefreshCertificateCommand::Private::~Private() = default;
namespace
{
Key getKey(const std::vector<Key> &keys)
{
if (keys.size() != 1) {
qCWarning(KLEOPATRA_LOG) << "Expected exactly one key, but got" << keys.size();
return {};
}
const Key key = keys.front();
if (key.protocol() == GpgME::UnknownProtocol) {
qCWarning(KLEOPATRA_LOG) << "Key has unknown protocol";
return {};
}
return key;
}
}
void RefreshCertificateCommand::Private::start()
{
key = getKey(keys());
if (key.isNull()) {
finished();
return;
}
#if QGPGME_SUPPORTS_KEY_REFRESH
std::unique_ptr<QGpgME::Job> refreshJob;
switch (key.protocol()) {
case GpgME::OpenPGP:
refreshJob = startOpenPGPJob();
break;
case GpgME::CMS:
refreshJob = startSMIMEJob();
break;
default:
; // cannot happen ;-)
}
if (!refreshJob) {
finished();
return;
}
job = refreshJob.release();
#else
- KMessageBox::error(parentWidgetOrView(), i18n("The backend does not support updating individual certificates."));
+ error(i18n("The backend does not support updating individual certificates."));
finished();
#endif
}
void RefreshCertificateCommand::Private::cancel()
{
#if QGPGME_SUPPORTS_KEY_REFRESH
if (job) {
job->slotCancel();
}
job.clear();
#endif
}
#if QGPGME_SUPPORTS_KEY_REFRESH
std::unique_ptr<QGpgME::ReceiveKeysJob> RefreshCertificateCommand::Private::startOpenPGPJob()
{
std::unique_ptr<QGpgME::ReceiveKeysJob> refreshJob{QGpgME::openpgp()->receiveKeysJob()};
Q_ASSERT(refreshJob);
connect(refreshJob.get(), &QGpgME::ReceiveKeysJob::result,
q, [this](const GpgME::ImportResult &result) {
onOpenPGPJobResult(result);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(refreshJob.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(refreshJob.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
const GpgME::Error err = refreshJob->start({QString::fromLatin1(key.primaryFingerprint())});
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Updating key..."));
return refreshJob;
}
std::unique_ptr<QGpgME::RefreshKeysJob> RefreshCertificateCommand::Private::startSMIMEJob()
{
std::unique_ptr<QGpgME::RefreshKeysJob> refreshJob{QGpgME::smime()->refreshKeysJob()};
Q_ASSERT(refreshJob);
connect(refreshJob.get(), &QGpgME::RefreshKeysJob::result,
q, [this](const GpgME::Error &err) {
onSMIMEJobResult(err);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(refreshJob.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(refreshJob.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
const GpgME::Error err = refreshJob->start({key});
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Updating certificate..."));
return refreshJob;
}
#endif
namespace
{
static auto informationOnChanges(const ImportResult &result)
{
QString text;
// if additional keys have been retrieved via WKD, then most of the below
// details are just a guess and may concern the additional keys instead of
// the refresh keys; this could only be clarified by a thorough comparison of
// unrefreshed and refreshed key
if (result.numUnchanged() == result.numConsidered()) {
// if numUnchanged < numConsidered, then it is not clear whether the refreshed key
// hasn't changed or whether another key retrieved via WKD hasn't changed
text = i18n("The key hasn't changed.");
} else if (result.newRevocations() > 0) {
// it is possible that a revoked key has been newly imported via WKD,
// but it is much more likely that the refreshed key was revoked
text = i18n("The key has been revoked.");
} else {
// it doesn't make much sense to list below details if the key has been revoked
text = i18n("The key has been updated.");
QStringList details;
if (result.newUserIDs() > 0) {
details.push_back(i18n("New user IDs: %1", result.newUserIDs()));
}
if (result.newSubkeys() > 0) {
details.push_back(i18n("New subkeys: %1", result.newSubkeys()));
}
if (result.newSignatures() > 0) {
details.push_back(i18n("New signatures: %1", result.newSignatures()));
}
if (!details.empty()) {
text += QLatin1String{"<br><br>"} + details.join(QLatin1String{"<br>"});
}
}
text = QLatin1String{"<p>"} + text + QLatin1String{"</p>"};
if (result.numImported() > 0) {
text += QLatin1String{"<p>"} + i18np("Additionally, one new key has been retrieved.",
"Additionally, %1 new keys have been retrieved.",
result.numImported()) + QLatin1String{"</p>"};
}
return text;
}
}
void RefreshCertificateCommand::Private::onOpenPGPJobResult(const ImportResult &result)
{
if (result.error()) {
showError(result.error());
finished();
return;
}
if (!result.error().isCanceled()) {
information(informationOnChanges(result),
i18nc("@title:window", "Key Updated"));
}
finished();
}
void RefreshCertificateCommand::Private::onSMIMEJobResult(const Error &err)
{
if (err) {
showError(err);
finished();
return;
}
if (!err.isCanceled()) {
information(i18nc("@info", "The certificate has been updated."),
i18nc("@title:window", "Certificate Updated"));
}
finished();
}
void RefreshCertificateCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred while updating the certificate:</para>"
"<para><message>%1</message></para>",
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
i18nc("@title:window", "Update Failed"));
}
RefreshCertificateCommand::RefreshCertificateCommand(const GpgME::Key &key)
: Command{key, new Private{this}}
{
}
RefreshCertificateCommand::~RefreshCertificateCommand() = default;
void RefreshCertificateCommand::doStart()
{
d->start();
}
void RefreshCertificateCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_refreshcertificatecommand.cpp"
diff --git a/src/commands/reloadkeyscommand.cpp b/src/commands/reloadkeyscommand.cpp
index 1151062a9..a7577cc80 100644
--- a/src/commands/reloadkeyscommand.cpp
+++ b/src/commands/reloadkeyscommand.cpp
@@ -1,99 +1,100 @@
/* -*- mode: c++; c-basic-offset:4 -*-
reloadkeyscommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "reloadkeyscommand.h"
#include "smartcard/readerstatus.h"
#include "command_p.h"
+#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include "kleopatra_debug.h"
#include <gpgme++/keylistresult.h>
using namespace Kleo;
using namespace GpgME;
class ReloadKeysCommand::Private : public Command::Private
{
friend class ::Kleo::ReloadKeysCommand;
public:
Private(ReloadKeysCommand *qq, KeyListController *controller);
~Private() override;
void keyListingDone(const KeyListResult &result);
};
ReloadKeysCommand::Private *ReloadKeysCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ReloadKeysCommand::Private *ReloadKeysCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
ReloadKeysCommand::ReloadKeysCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ReloadKeysCommand::ReloadKeysCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ReloadKeysCommand::~ReloadKeysCommand() {}
ReloadKeysCommand::Private::Private(ReloadKeysCommand *qq, KeyListController *controller)
: Command::Private(qq, controller)
{
}
ReloadKeysCommand::Private::~Private() {}
void ReloadKeysCommand::Private::keyListingDone(const KeyListResult &result)
{
if (result.error()) { // ### Show error message here?
- qCritical() << "Error occurred during key listing: " << result.error().asString();
+ qCritical() << "Error occurred during key listing: " << Formatting::errorAsString(result.error());
}
finished();
}
#define d d_func()
void ReloadKeysCommand::doStart()
{
const auto view = d->parentWidgetOrView();
if (view && !view->isVisible()) {
// Small hack to make redisplay also work nicely when the keylist
// is not currently the active widget.
SmartCard::ReaderStatus::mutableInstance()->updateStatus();
d->finished();
return;
}
connect(KeyCache::instance().get(), &KeyCache::keyListingDone, this, [this](const GpgME::KeyListResult &result) {
d->keyListingDone(result);
});
KeyCache::mutableInstance()->startKeyListing();
}
void ReloadKeysCommand::doCancel()
{
KeyCache::mutableInstance()->cancelKeyListing();
}
#undef d
#include "moc_reloadkeyscommand.cpp"
diff --git a/src/commands/revokecertificationcommand.cpp b/src/commands/revokecertificationcommand.cpp
index 2b7ec2698..015fb432b 100644
--- a/src/commands/revokecertificationcommand.cpp
+++ b/src/commands/revokecertificationcommand.cpp
@@ -1,366 +1,366 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/revokecertificationcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "revokecertificationcommand.h"
#include "command_p.h"
#include "exportopenpgpcertstoservercommand.h"
#include <kleopatra_debug.h>
#include <utils/keys.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KGuiItem>
#include <KLocalizedString>
#include <KMessageBox>
#include <KStandardGuiItem>
#include <QGpgME/Protocol>
#include <QGpgME/QuickJob>
#include <gpgme++/engineinfo.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
using namespace QGpgME;
namespace
{
enum class InputType {
Key,
UserIDs,
Certifications,
};
struct CertificationData {
UserID userId;
Key certificationKey;
};
static std::vector<Key> getCertificationKeys(const GpgME::UserID &userId)
{
std::vector<Key> keys;
if (userId.numSignatures() == 0) {
qCWarning(KLEOPATRA_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available";
return keys;
}
std::vector<GpgME::UserID::Signature> revokableCertifications;
Kleo::copy_if(userId.signatures(), std::back_inserter(revokableCertifications), [](const auto &certification) {
return userCanRevokeCertification(certification) == CertificationCanBeRevoked;
});
Kleo::transform(revokableCertifications, std::back_inserter(keys), [](const auto &certification) {
return KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID());
});
return keys;
}
static bool confirmRevocations(QWidget *parent, const std::vector<CertificationData> &certifications)
{
KMessageBox::ButtonCode answer;
if (certifications.size() == 1) {
const auto [userId, certificationKey] = certifications.front();
const auto message = xi18nc("@info",
"<para>You are about to revoke the certification of user ID<nl/>%1<nl/>made with the key<nl/>%2.</para>",
QString::fromUtf8(userId.id()),
Formatting::formatForComboBox(certificationKey));
answer = KMessageBox::questionTwoActions(parent,
message,
i18nc("@title:window", "Confirm Revocation"),
KGuiItem{i18n("Revoke Certification")},
KStandardGuiItem::cancel());
} else {
QStringList l;
Kleo::transform(certifications, std::back_inserter(l), [](const auto &c) {
return i18n("User ID '%1' certified with key %2", QString::fromUtf8(c.userId.id()), Formatting::formatForComboBox(c.certificationKey));
});
const auto message = i18np("You are about to revoke the following certification:", //
"You are about to revoke the following %1 certifications:",
certifications.size());
answer = KMessageBox::questionTwoActionsList(parent,
message,
l,
i18nc("@title:window", "Confirm Revocation"),
KGuiItem{i18n("Revoke Certifications")},
KStandardGuiItem::cancel());
}
return answer == KMessageBox::ButtonCode::PrimaryAction;
}
}
class RevokeCertificationCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::RevokeCertificationCommand;
RevokeCertificationCommand *q_func() const
{
return static_cast<RevokeCertificationCommand *>(q);
}
public:
Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c = nullptr);
void init();
private:
std::vector<CertificationData> getCertificationsToRevoke();
void scheduleNextRevocation();
QGpgME::QuickJob *createJob();
void slotResult(const Error &err);
private:
InputType inputType = InputType::Key;
Key certificationTarget;
std::vector<UserID> uids;
std::vector<CertificationData> certificationsToRevoke;
std::vector<CertificationData> completedRevocations;
QPointer<QGpgME::QuickJob> job;
};
RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
RevokeCertificationCommand::Private::Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c)
: Command::Private{qq, c}
, inputType{i}
{
}
void RevokeCertificationCommand::Private::init()
{
const std::vector<Key> keys_ = keys();
if (keys_.size() != 1) {
qCWarning(KLEOPATRA_LOG) << q << "Expected exactly one key, but got" << keys_.size();
return;
}
if (keys_.front().protocol() != GpgME::OpenPGP) {
qCWarning(KLEOPATRA_LOG) << q << "Expected OpenPGP key, but got" << keys_.front().protocolAsString();
return;
}
certificationTarget = keys_.front();
}
std::vector<CertificationData> RevokeCertificationCommand::Private::getCertificationsToRevoke()
{
if (inputType != InputType::Certifications) {
// ensure that the certifications of the key have been loaded
if (certificationTarget.userID(0).numSignatures() == 0) {
certificationTarget.update();
}
// build list of user IDs and revokable certifications
const auto userIDsToConsider = (inputType == InputType::Key) ? certificationTarget.userIDs() : uids;
for (const auto &userId : userIDsToConsider) {
Kleo::transform(getCertificationKeys(userId), std::back_inserter(certificationsToRevoke), [userId](const auto &k) {
return CertificationData{userId, k};
});
}
}
Kleo::erase_if(certificationsToRevoke, [](const auto &c) {
return c.certificationKey.isNull();
});
return certificationsToRevoke;
}
void RevokeCertificationCommand::Private::scheduleNextRevocation()
{
if (!certificationsToRevoke.empty()) {
const auto nextCertification = certificationsToRevoke.back();
job = createJob();
if (!job) {
qCWarning(KLEOPATRA_LOG) << q << "Failed to create job";
finished();
return;
}
job->startRevokeSignature(certificationTarget, nextCertification.certificationKey, {nextCertification.userId});
} else {
const auto message = xi18ncp("@info",
"<para>The certification has been revoked successfully.</para>"
"<para>Do you want to publish the revocation?</para>",
"<para>%1 certifications have been revoked successfully.</para>"
"<para>Do you want to publish the revocations?</para>",
completedRevocations.size());
const auto yesButton = KGuiItem{i18ncp("@action:button", "Publish Revocation", "Publish Revocations", completedRevocations.size()),
QIcon::fromTheme(QStringLiteral("view-certificate-export-server"))};
const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(),
message,
i18nc("@title:window", "Confirm Publication"),
yesButton,
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
const auto cmd = new ExportOpenPGPCertsToServerCommand{certificationTarget};
cmd->start();
}
finished();
}
}
void RevokeCertificationCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
canceled();
return;
}
if (err) {
const auto failedRevocation = certificationsToRevoke.back();
error(xi18nc("@info",
"<para>The revocation of the certification of user ID<nl/>%1<nl/>made with key<nl/>%2<nl/>failed:</para>"
"<para><message>%3</message></para>",
Formatting::prettyNameAndEMail(failedRevocation.userId),
Formatting::formatForComboBox(failedRevocation.certificationKey),
- QString::fromUtf8(err.asString())));
+ Formatting::errorAsString(err)));
finished();
return;
}
completedRevocations.push_back(certificationsToRevoke.back());
certificationsToRevoke.pop_back();
scheduleNextRevocation();
}
QGpgME::QuickJob *RevokeCertificationCommand::Private::createJob()
{
const auto j = QGpgME::openpgp()->quickJob();
if (j) {
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
connect(j, &QGpgME::QuickJob::result, q, [this](const GpgME::Error &error) {
slotResult(error);
});
}
return j;
}
RevokeCertificationCommand::RevokeCertificationCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{InputType::Key, this, c}}
{
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::Key &key)
: Command{key, new Private{InputType::Key, this}}
{
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID &uid)
: Command{uid.parent(), new Private{InputType::UserIDs, this}}
{
std::vector<UserID>(1, uid).swap(d->uids);
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const std::vector<GpgME::UserID> &uids)
: Command{uids.empty() ? Key{} : uids.front().parent(), new Private{InputType::UserIDs, this}}
{
d->uids = uids;
d->init();
}
RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID::Signature &signature)
: Command{signature.parent().parent(), new Private{InputType::Certifications, this}}
{
if (!signature.isNull()) {
const Key certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID());
d->certificationsToRevoke = {{signature.parent(), certificationKey}};
}
d->init();
}
RevokeCertificationCommand::~RevokeCertificationCommand()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
}
// static
bool RevokeCertificationCommand::isSupported()
{
return engineInfo(GpgEngine).engineVersion() >= "2.2.24";
}
void RevokeCertificationCommand::doStart()
{
if (d->certificationTarget.isNull()) {
d->finished();
return;
}
if (!Kleo::all_of(d->uids, userIDBelongsToKey(d->certificationTarget))) {
qCWarning(KLEOPATRA_LOG) << this << "User ID <-> Key mismatch!";
d->finished();
return;
}
const auto certificationsToRevoke = d->getCertificationsToRevoke();
if (certificationsToRevoke.empty()) {
switch (d->inputType) {
case InputType::Key:
d->information(i18n("You cannot revoke any certifications of this key."));
break;
case InputType::UserIDs:
d->information(i18np("You cannot revoke any certifications of this user ID.", //
"You cannot revoke any certifications of these user IDs.",
d->uids.size()));
break;
case InputType::Certifications:
d->information(i18n("You cannot revoke this certification."));
break;
}
d->finished();
return;
}
if (!confirmRevocations(d->parentWidgetOrView(), certificationsToRevoke)) {
d->canceled();
return;
}
d->scheduleNextRevocation();
}
void RevokeCertificationCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
#include "moc_revokecertificationcommand.cpp"
diff --git a/src/commands/revokekeycommand.cpp b/src/commands/revokekeycommand.cpp
index 3c3484f8c..6c2593362 100644
--- a/src/commands/revokekeycommand.cpp
+++ b/src/commands/revokekeycommand.cpp
@@ -1,263 +1,263 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/revokekeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "revokekeycommand.h"
#include "command_p.h"
#include "dialogs/revokekeydialog.h"
#include <Libkleo/Formatting>
#include <KLocalizedString>
#if QGPGME_SUPPORTS_KEY_REVOCATION
#include <QGpgME/RevokeKeyJob>
#endif
#include "kleopatra_debug.h"
#include <QGpgME/Protocol>
using namespace Kleo;
using namespace GpgME;
class RevokeKeyCommand::Private : public Command::Private
{
friend class ::RevokeKeyCommand;
RevokeKeyCommand *q_func() const
{
return static_cast<RevokeKeyCommand *>(q);
}
public:
explicit Private(RevokeKeyCommand *qq, KeyListController *c = nullptr);
~Private() override;
void start();
void cancel();
private:
void ensureDialogCreated();
void onDialogAccepted();
void onDialogRejected();
#if QGPGME_SUPPORTS_KEY_REVOCATION
std::unique_ptr<QGpgME::RevokeKeyJob> startJob();
#endif
void onJobResult(const Error &err);
void showError(const Error &err);
private:
Key key;
QPointer<RevokeKeyDialog> dialog;
#if QGPGME_SUPPORTS_KEY_REVOCATION
QPointer<QGpgME::RevokeKeyJob> job;
#endif
};
RevokeKeyCommand::Private *RevokeKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RevokeKeyCommand::Private *RevokeKeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
RevokeKeyCommand::Private::Private(RevokeKeyCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
RevokeKeyCommand::Private::~Private() = default;
namespace
{
Key getKey(const std::vector<Key> &keys)
{
if (keys.size() != 1) {
qCWarning(KLEOPATRA_LOG) << "Expected exactly one key, but got" << keys.size();
return {};
}
const Key key = keys.front();
if (key.protocol() != GpgME::OpenPGP) {
qCWarning(KLEOPATRA_LOG) << "Expected OpenPGP key, but got" << Formatting::displayName(key.protocol()) << "key";
return {};
}
return key;
}
}
void RevokeKeyCommand::Private::start()
{
key = getKey(keys());
if (key.isNull()) {
finished();
return;
}
if (key.isRevoked()) {
information(i18nc("@info", "This key has already been revoked."));
finished();
return;
}
ensureDialogCreated();
Q_ASSERT(dialog);
dialog->setKey(key);
dialog->show();
}
void RevokeKeyCommand::Private::cancel()
{
#if QGPGME_SUPPORTS_KEY_REVOCATION
if (job) {
job->slotCancel();
}
job.clear();
#endif
}
void RevokeKeyCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new RevokeKeyDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, q, [this]() { onDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { onDialogRejected(); });
}
void RevokeKeyCommand::Private::onDialogAccepted()
{
#if QGPGME_SUPPORTS_KEY_REVOCATION
auto revokeJob = startJob();
if (!revokeJob) {
finished();
return;
}
job = revokeJob.release();
#endif
}
void RevokeKeyCommand::Private::onDialogRejected()
{
canceled();
}
namespace
{
std::vector<std::string> toStdStrings(const QStringList &l)
{
std::vector<std::string> v;
v.reserve(l.size());
std::transform(std::begin(l), std::end(l),
std::back_inserter(v),
std::mem_fn(&QString::toStdString));
return v;
}
auto descriptionToLines(const QString &description)
{
std::vector<std::string> lines;
if (!description.isEmpty()) {
lines = toStdStrings(description.split(QLatin1Char('\n')));
}
return lines;
}
}
#if QGPGME_SUPPORTS_KEY_REVOCATION
std::unique_ptr<QGpgME::RevokeKeyJob> RevokeKeyCommand::Private::startJob()
{
std::unique_ptr<QGpgME::RevokeKeyJob> revokeJob{QGpgME::openpgp()->revokeKeyJob()};
Q_ASSERT(revokeJob);
connect(revokeJob.get(), &QGpgME::RevokeKeyJob::result,
q, [this](const GpgME::Error &err) {
onJobResult(err);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(revokeJob.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(revokeJob.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
const auto description = descriptionToLines(dialog->description());
const GpgME::Error err = revokeJob->start(key, dialog->reason(), description);
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Revoking key..."));
return revokeJob;
}
#endif
void RevokeKeyCommand::Private::onJobResult(const Error &err)
{
if (err) {
showError(err);
finished();
return;
}
if (!err.isCanceled()) {
information(i18nc("@info", "The key was revoked successfully."),
i18nc("@title:window", "Key Revoked"));
}
finished();
}
void RevokeKeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred during the revocation:</para>"
"<para><message>%1</message></para>",
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
i18nc("@title:window", "Revocation Failed"));
}
RevokeKeyCommand::RevokeKeyCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{this, c}}
{
}
RevokeKeyCommand::RevokeKeyCommand(const GpgME::Key &key)
: Command{key, new Private{this}}
{
}
RevokeKeyCommand::~RevokeKeyCommand() = default;
void RevokeKeyCommand::doStart()
{
d->start();
}
void RevokeKeyCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_revokekeycommand.cpp"
diff --git a/src/commands/revokeuseridcommand.cpp b/src/commands/revokeuseridcommand.cpp
index afce4b589..a54e4542d 100644
--- a/src/commands/revokeuseridcommand.cpp
+++ b/src/commands/revokeuseridcommand.cpp
@@ -1,176 +1,177 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/revokeuseridcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "revokeuseridcommand.h"
#include "command_p.h"
+#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <KLocalizedString>
#include <QGpgME/Protocol>
#include <QGpgME/QuickJob>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
class RevokeUserIDCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::RevokeUserIDCommand;
RevokeUserIDCommand *q_func() const
{
return static_cast<RevokeUserIDCommand *>(q);
}
public:
explicit Private(RevokeUserIDCommand *qq, const UserID &userId);
~Private() override;
void startJob();
private:
void createJob();
void slotResult(const Error &err);
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::UserID userId;
QPointer<QGpgME::QuickJob> job;
};
RevokeUserIDCommand::Private *RevokeUserIDCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RevokeUserIDCommand::Private *RevokeUserIDCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
RevokeUserIDCommand::Private::Private(RevokeUserIDCommand *qq, const UserID &userId)
: Command::Private{qq}
, userId{userId}
{
}
RevokeUserIDCommand::Private::~Private() = default;
void Commands::RevokeUserIDCommand::Private::startJob()
{
createJob();
if (!job) {
finished();
return;
}
const QString uidToRevoke = QString::fromUtf8(engineIsVersion(2, 3, 7) ? userId.uidhash() : userId.id());
job->startRevUid(userId.parent(), uidToRevoke);
}
void RevokeUserIDCommand::Private::createJob()
{
Q_ASSERT(!job);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
const auto j = backend->quickJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &QGpgME::QuickJob::result,
q, [this](const GpgME::Error &err) { slotResult(err); });
job = j;
}
void RevokeUserIDCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
} else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void RevokeUserIDCommand::Private::showErrorDialog(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred while trying to revoke the user ID<nl/><emphasis>%1</emphasis>.</para>"
"<para><message>%2</message></para>",
- QString::fromUtf8(userId.id()), QString::fromLocal8Bit(err.asString())),
+ QString::fromUtf8(userId.id()), Formatting::errorAsString(err)),
i18nc("@title:window", "Revocation Failed"));
}
void RevokeUserIDCommand::Private::showSuccessDialog()
{
information(xi18nc("@info", "<para>The user ID<nl/><emphasis>%1</emphasis><nl/>has been revoked successfully.</para>",
QString::fromUtf8(userId.id())),
i18nc("@title:window", "Revocation Succeeded"));
}
RevokeUserIDCommand::RevokeUserIDCommand(const GpgME::UserID &userId)
: Command{new Private{this, userId}}
{
}
RevokeUserIDCommand::~RevokeUserIDCommand()
{
}
void RevokeUserIDCommand::doStart()
{
if (d->userId.isNull()) {
d->finished();
return;
}
const auto key = d->userId.parent();
if (key.protocol() != GpgME::OpenPGP || !key.hasSecret()) {
d->finished();
return;
}
d->startJob();
}
void RevokeUserIDCommand::doCancel()
{
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
#include "moc_revokeuseridcommand.cpp"
diff --git a/src/commands/setpivcardapplicationadministrationkeycommand.cpp b/src/commands/setpivcardapplicationadministrationkeycommand.cpp
index ac1fbd3b9..5805f5cbe 100644
--- a/src/commands/setpivcardapplicationadministrationkeycommand.cpp
+++ b/src/commands/setpivcardapplicationadministrationkeycommand.cpp
@@ -1,220 +1,222 @@
/* commands/setpivcardapplicationadministrationkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "setpivcardapplicationadministrationkeycommand.h"
#include "cardcommand_p.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "commands/authenticatepivcardapplicationcommand.h"
#include "dialogs/pivcardapplicationadministrationkeyinputdialog.h"
+#include <Libkleo/Formatting>
+
#include <KLocalizedString>
#include <gpgme++/error.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace Kleo::SmartCard;
using namespace GpgME;
class SetPIVCardApplicationAdministrationKeyCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::SetPIVCardApplicationAdministrationKeyCommand;
SetPIVCardApplicationAdministrationKeyCommand *q_func() const
{
return static_cast<SetPIVCardApplicationAdministrationKeyCommand *>(q);
}
public:
explicit Private(SetPIVCardApplicationAdministrationKeyCommand *qq, const std::string &serialNumber, QWidget *p);
~Private() override;
void init();
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void authenticate();
void authenticationFinished();
void authenticationCanceled();
void setAdminKey();
void ensureDialogCreated();
private:
QByteArray newAdminKey;
QPointer<PIVCardApplicationAdministrationKeyInputDialog> dialog;
bool hasBeenCanceled = false;
};
SetPIVCardApplicationAdministrationKeyCommand::Private *SetPIVCardApplicationAdministrationKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const SetPIVCardApplicationAdministrationKeyCommand::Private *SetPIVCardApplicationAdministrationKeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
SetPIVCardApplicationAdministrationKeyCommand::Private::Private(SetPIVCardApplicationAdministrationKeyCommand *qq, const std::string &serialNumber, QWidget *p)
: CardCommand::Private(qq, serialNumber, p)
, dialog()
{
}
SetPIVCardApplicationAdministrationKeyCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::Private::~Private()";
}
SetPIVCardApplicationAdministrationKeyCommand::SetPIVCardApplicationAdministrationKeyCommand(const std::string &serialNumber, QWidget *p)
: CardCommand(new Private(this, serialNumber, p))
{
d->init();
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::init()
{
}
SetPIVCardApplicationAdministrationKeyCommand::~SetPIVCardApplicationAdministrationKeyCommand()
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::~SetPIVCardApplicationAdministrationKeyCommand()";
}
void SetPIVCardApplicationAdministrationKeyCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::doStart()";
d->authenticate();
}
void SetPIVCardApplicationAdministrationKeyCommand::doCancel()
{
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
cmd->setPrompt(i18n("Please enter the old PIV Card Application Administration Key in hex-encoded form."));
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
q, [this]() { authenticationFinished(); });
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
q, [this]() { authenticationCanceled(); });
cmd->start();
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::authenticationFinished()";
if (!hasBeenCanceled) {
setAdminKey();
}
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::setAdminKey()
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::setAdminKey()";
ensureDialogCreated();
Q_ASSERT(dialog);
dialog->show();
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new PIVCardApplicationAdministrationKeyInputDialog(parentWidgetOrView());
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setLabelText(newAdminKey.isEmpty() ?
i18n("Please enter the new PIV Card Application Administration Key in hex-encoded form. "
"The key needs to consist of 24 bytes, i.e. 48 hex-characters.") :
i18n("Please enter the new PIV Card Application Administration Key again."));
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::slotDialogAccepted()
{
if (newAdminKey.isEmpty()) {
newAdminKey = dialog->adminKey();
dialog = nullptr;
setAdminKey();
return;
}
const QByteArray newAdminKey2 = dialog->adminKey();
if (newAdminKey != newAdminKey2) {
error(i18nc("@info", "The two keys you have entered do not match. Please retry."));
newAdminKey.clear();
dialog = nullptr;
setAdminKey();
return;
}
auto pivCard = ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const QByteArray plusPercentEncodedAdminKey = newAdminKey.toPercentEncoding().replace(' ', '+');
const QByteArray command = QByteArray("SCD SETATTR SET-ADM-KEY ") + plusPercentEncodedAdminKey;
ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, command, q, [this](const GpgME::Error &err) {
slotResult(err);
});
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::slotDialogRejected()
{
finished();
}
void SetPIVCardApplicationAdministrationKeyCommand::Private::slotResult(const GpgME::Error& err)
{
qCDebug(KLEOPATRA_LOG) << "SetPIVCardApplicationAdministrationKeyCommand::slotResult():"
- << err.asString() << "(" << err.code() << ")";
+ << Formatting::errorAsString(err) << "(" << err.code() << ")";
if (err) {
- error(i18nc("@info", "Setting the PIV Card Application Administration Key failed: %1", QString::fromLatin1(err.asString())));
+ error(i18nc("@info", "Setting the PIV Card Application Administration Key failed: %1", Formatting::errorAsString(err)));
} else if (!err.isCanceled()) {
success(i18nc("@info", "PIV Card Application Administration Key set successfully."));
ReaderStatus::mutableInstance()->updateStatus();
}
finished();
}
#undef d
#undef q
#include "moc_setpivcardapplicationadministrationkeycommand.cpp"
diff --git a/src/commands/setprimaryuseridcommand.cpp b/src/commands/setprimaryuseridcommand.cpp
index 8957c92db..df10a3431 100644
--- a/src/commands/setprimaryuseridcommand.cpp
+++ b/src/commands/setprimaryuseridcommand.cpp
@@ -1,187 +1,189 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/setprimaryuseridcommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "setprimaryuseridcommand.h"
#include "command_p.h"
+#include <Libkleo/Formatting>
+
#include <KLocalizedString>
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
#include <QGpgME/SetPrimaryUserIDJob>
#endif
#include <QGpgME/Protocol>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
class SetPrimaryUserIDCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::SetPrimaryUserIDCommand;
SetPrimaryUserIDCommand *q_func() const
{
return static_cast<SetPrimaryUserIDCommand *>(q);
}
public:
explicit Private(SetPrimaryUserIDCommand *qq, const UserID &userId);
~Private() override;
void startJob();
private:
void createJob();
void slotResult(const Error &err);
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::UserID userId;
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
QPointer<QGpgME::SetPrimaryUserIDJob> job;
#endif
};
SetPrimaryUserIDCommand::Private *SetPrimaryUserIDCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const SetPrimaryUserIDCommand::Private *SetPrimaryUserIDCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
SetPrimaryUserIDCommand::Private::Private(SetPrimaryUserIDCommand *qq, const UserID &userId)
: Command::Private{qq}
, userId{userId}
{
}
SetPrimaryUserIDCommand::Private::~Private() = default;
void Commands::SetPrimaryUserIDCommand::Private::startJob()
{
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
createJob();
if (!job) {
finished();
return;
}
job->start(userId);
#else
error(i18nc("@info", "The backend does not support this operation."));
#endif
}
void SetPrimaryUserIDCommand::Private::createJob()
{
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
Q_ASSERT(!job);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
const auto j = backend->setPrimaryUserIDJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
connect(j, &QGpgME::SetPrimaryUserIDJob::result, q, [this](const GpgME::Error &err) {
slotResult(err);
});
job = j;
#endif
}
void SetPrimaryUserIDCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
} else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void SetPrimaryUserIDCommand::Private::showErrorDialog(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred while trying to flag the user ID<nl/><emphasis>%1</emphasis><nl/>as the primary user ID.</para>"
"<para><message>%2</message></para>",
QString::fromUtf8(userId.id()),
- QString::fromLocal8Bit(err.asString())));
+ Formatting::errorAsString(err)));
}
void SetPrimaryUserIDCommand::Private::showSuccessDialog()
{
success(xi18nc("@info",
"<para>The user ID<nl/><emphasis>%1</emphasis><nl/>has been flagged successfully as the primary user ID.</para>",
QString::fromUtf8(userId.id())));
}
SetPrimaryUserIDCommand::SetPrimaryUserIDCommand(const GpgME::UserID &userId)
: Command{new Private{this, userId}}
{
}
SetPrimaryUserIDCommand::~SetPrimaryUserIDCommand()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
}
void SetPrimaryUserIDCommand::doStart()
{
if (d->userId.isNull()) {
d->finished();
return;
}
const auto key = d->userId.parent();
if (key.protocol() != GpgME::OpenPGP || !key.hasSecret()) {
d->finished();
return;
}
d->startJob();
}
void SetPrimaryUserIDCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
if (d->job) {
d->job->slotCancel();
}
#endif
}
#undef d
#undef q
diff --git a/src/crypto/decryptverifytask.cpp b/src/crypto/decryptverifytask.cpp
index 59e24707c..584cd664f 100644
--- a/src/crypto/decryptverifytask.cpp
+++ b/src/crypto/decryptverifytask.cpp
@@ -1,1726 +1,1726 @@
/* -*- mode: c++; c-basic-offset:4 -*-
decryptverifytask.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "decryptverifytask.h"
#include <QGpgME/Protocol>
#include <QGpgME/VerifyOpaqueJob>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/DecryptJob>
#include <QGpgME/DecryptVerifyJob>
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/DecryptVerifyArchiveJob>
#endif
#include <Libkleo/AuditLogEntry>
#include <Libkleo/Compliance>
#include <Libkleo/Dn>
#include <Libkleo/KleoException>
#include <Libkleo/Stl_Util>
#include <Libkleo/KeyCache>
#include <Libkleo/Predicates>
#include <Libkleo/Formatting>
#include <Libkleo/Classify>
#include <utils/detail_p.h>
#include <utils/input.h>
#include <utils/output.h>
#include <utils/kleo_assert.h>
#include <Libkleo/GnuPG>
#include <KMime/HeaderParsing>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/context.h>
#include <gpg-error.h>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <QLocale>
#include <QByteArray>
#include <QDateTime>
#include <QIODevice>
#include <QStringList>
#include <QTextDocument> // Qt::escape
#include <algorithm>
#include <sstream>
using namespace Kleo::Crypto;
using namespace Kleo;
using namespace GpgME;
using namespace KMime::Types;
namespace
{
static AuditLogEntry auditLogFromSender(QObject *sender)
{
return AuditLogEntry::fromJob(qobject_cast<const QGpgME::Job *>(sender));
}
static bool addrspec_equal(const AddrSpec &lhs, const AddrSpec &rhs, Qt::CaseSensitivity cs)
{
return lhs.localPart.compare(rhs.localPart, cs) == 0 && lhs.domain.compare(rhs.domain, Qt::CaseInsensitive) == 0;
}
static bool mailbox_equal(const Mailbox &lhs, const Mailbox &rhs, Qt::CaseSensitivity cs)
{
return addrspec_equal(lhs.addrSpec(), rhs.addrSpec(), cs);
}
static std::string stripAngleBrackets(const std::string &str)
{
if (str.empty()) {
return str;
}
if (str[0] == '<' && str[str.size() - 1] == '>') {
return str.substr(1, str.size() - 2);
}
return str;
}
static std::string email(const UserID &uid)
{
if (uid.parent().protocol() == OpenPGP) {
if (const char *const email = uid.email()) {
return stripAngleBrackets(email);
} else {
return std::string();
}
}
Q_ASSERT(uid.parent().protocol() == CMS);
if (const char *const id = uid.id())
if (*id == '<') {
return stripAngleBrackets(id);
} else {
return DN(id)[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
}
else {
return std::string();
}
}
static Mailbox mailbox(const UserID &uid)
{
const std::string e = email(uid);
Mailbox mbox;
if (!e.empty()) {
mbox.setAddress(e.c_str());
}
return mbox;
}
static std::vector<Mailbox> extractMailboxes(const Key &key)
{
std::vector<Mailbox> res;
const auto userIDs{key.userIDs()};
for (const UserID &id : userIDs) {
const Mailbox mbox = mailbox(id);
if (!mbox.addrSpec().isEmpty()) {
res.push_back(mbox);
}
}
return res;
}
static std::vector<Mailbox> extractMailboxes(const std::vector<Key> &signers)
{
std::vector<Mailbox> res;
for (const Key &i : signers) {
const std::vector<Mailbox> bxs = extractMailboxes(i);
res.insert(res.end(), bxs.begin(), bxs.end());
}
return res;
}
static bool keyContainsMailbox(const Key &key, const Mailbox &mbox)
{
const std::vector<Mailbox> mbxs = extractMailboxes(key);
return std::find_if(mbxs.cbegin(), mbxs.cend(),
[mbox](const Mailbox &m) {
return mailbox_equal(mbox, m, Qt::CaseInsensitive);
}) != mbxs.cend();
}
static bool keysContainMailbox(const std::vector<Key> &keys, const Mailbox &mbox)
{
return std::find_if(keys.cbegin(), keys.cend(),
[mbox](const Key &key) {
return keyContainsMailbox(key, mbox);
}) != keys.cend();
}
static bool relevantInDecryptVerifyContext(const VerificationResult &r)
{
// for D/V operations, we ignore verification results which are not errors and contain
// no signatures (which means that the data was just not signed)
return (r.error() && r.error().code() != GPG_ERR_DECRYPT_FAILED) || r.numSignatures() > 0;
}
static QString signatureSummaryToString(int summary)
{
if (summary & Signature::None) {
return i18n("Error: Signature not verified");
} else if (summary & Signature::Valid || summary & Signature::Green) {
return i18n("Good signature");
} else if (summary & Signature::KeyRevoked) {
return i18n("Signing certificate was revoked");
} else if (summary & Signature::KeyExpired) {
return i18n("Signing certificate is expired");
} else if (summary & Signature::KeyMissing) {
return i18n("Certificate is not available");
} else if (summary & Signature::SigExpired) {
return i18n("Signature expired");
} else if (summary & Signature::CrlMissing) {
return i18n("CRL missing");
} else if (summary & Signature::CrlTooOld) {
return i18n("CRL too old");
} else if (summary & Signature::BadPolicy) {
return i18n("Bad policy");
} else if (summary & Signature::SysError) {
return i18n("System error"); //### retrieve system error details?
} else if (summary & Signature::Red) {
return i18n("Bad signature");
}return QString();
}
static QString formatValidSignatureWithTrustLevel(const UserID &id)
{
if (id.isNull()) {
return QString();
}
switch (id.validity()) {
case UserID::Marginal:
return i18n("The signature is valid but the trust in the certificate's validity is only marginal.");
case UserID::Full:
return i18n("The signature is valid and the certificate's validity is fully trusted.");
case UserID::Ultimate:
return i18n("The signature is valid and the certificate's validity is ultimately trusted.");
case UserID::Never:
return i18n("The signature is valid but the certificate's validity is <em>not trusted</em>.");
case UserID::Unknown:
return i18n("The signature is valid but the certificate's validity is unknown.");
case UserID::Undefined:
default:
return i18n("The signature is valid but the certificate's validity is undefined.");
}
}
static QString renderKeyLink(const QString &fpr, const QString &text)
{
return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(fpr, text);
}
static QString renderKey(const Key &key)
{
if (key.isNull()) {
return i18n("Unknown certificate");
}
if (key.primaryFingerprint() && strlen(key.primaryFingerprint()) > 16 && key.numUserIDs()) {
const QString text = QStringLiteral("%1 (%2)").arg(Formatting::prettyNameAndEMail(key).toHtmlEscaped()).arg(
Formatting::prettyID(QString::fromLocal8Bit(key.primaryFingerprint()).right(16).toLatin1().constData()));
return renderKeyLink(QLatin1String(key.primaryFingerprint()), text);
}
return renderKeyLink(QLatin1String(key.primaryFingerprint()), Formatting::prettyID(key.primaryFingerprint()));
}
static QString renderKeyEMailOnlyNameAsFallback(const Key &key)
{
if (key.isNull()) {
return i18n("Unknown certificate");
}
const QString email = Formatting::prettyEMail(key);
const QString user = !email.isEmpty() ? email : Formatting::prettyName(key);
return renderKeyLink(QLatin1String(key.primaryFingerprint()), user);
}
static QString formatDate(const QDateTime &dt)
{
return QLocale().toString(dt);
}
static QString formatSigningInformation(const Signature &sig)
{
if (sig.isNull()) {
return QString();
}
const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(quint32(sig.creationTime())) : QDateTime();
QString text;
Key key = sig.key();
if (dt.isValid()) {
text = i18nc("1 is a date", "Signature created on %1", formatDate(dt)) + QStringLiteral("<br>");
}
if (key.isNull()) {
return text += i18n("With unavailable certificate:") + QStringLiteral("<br>ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper());
}
text += i18n("With certificate:") + QStringLiteral("<br>") + renderKey(key);
if (DeVSCompliance::isCompliant()) {
text +=
(QStringLiteral("<br/>")
+ (sig.isDeVs()
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The signature is %1", DeVSCompliance::name(true))
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The signature <b>is not</b> %1.", DeVSCompliance::name(true))));
}
return text;
}
static QString strikeOut(const QString &str, bool strike)
{
return QString(strike ? QStringLiteral("<s>%1</s>") : QStringLiteral("%1")).arg(str.toHtmlEscaped());
}
static QString formatInputOutputLabel(const QString &input, const QString &output, bool inputDeleted, bool outputDeleted)
{
if (output.isEmpty()) {
return strikeOut(input, inputDeleted);
}
return i18nc("Input file --> Output file (rarr is arrow", "%1 &rarr; %2",
strikeOut(input, inputDeleted),
strikeOut(output, outputDeleted));
}
static bool IsErrorOrCanceled(const GpgME::Error &err)
{
return err || err.isCanceled();
}
static bool IsErrorOrCanceled(const Result &res)
{
return IsErrorOrCanceled(res.error());
}
static bool IsBad(const Signature &sig)
{
return sig.summary() & Signature::Red;
}
static bool IsGoodOrValid(const Signature &sig)
{
return (sig.summary() & Signature::Valid) || (sig.summary() & Signature::Green);
}
static UserID findUserIDByMailbox(const Key &key, const Mailbox &mbox)
{
const auto userIDs{key.userIDs()};
for (const UserID &id : userIDs)
if (mailbox_equal(mailbox(id), mbox, Qt::CaseInsensitive)) {
return id;
}
return UserID();
}
static void updateKeys(const VerificationResult &result) {
// This little hack works around the problem that GnuPG / GpgME does not
// provide Key information in a verification result. The Key object is
// a dummy just holding the KeyID. This hack ensures that all available
// keys are fetched from the backend and are populated
for (const auto &sig: result.signatures()) {
// Update key information
sig.key(true, true);
}
}
}
class DecryptVerifyResult::SenderInfo
{
public:
explicit SenderInfo(const Mailbox &infSender, const std::vector<Key> &signers_) : informativeSender(infSender), signers(signers_) {}
const Mailbox informativeSender;
const std::vector<Key> signers;
bool hasInformativeSender() const
{
return !informativeSender.addrSpec().isEmpty();
}
bool conflicts() const
{
return hasInformativeSender() && hasKeys() && !keysContainMailbox(signers, informativeSender);
}
bool hasKeys() const
{
return std::any_of(signers.cbegin(), signers.cend(), [](const Key &key) { return !key.isNull(); });
}
std::vector<Mailbox> signerMailboxes() const
{
return extractMailboxes(signers);
}
};
namespace
{
static Task::Result::VisualCode codeForVerificationResult(const VerificationResult &res)
{
if (res.isNull()) {
return Task::Result::NeutralSuccess;
}
const std::vector<Signature> sigs = res.signatures();
if (sigs.empty()) {
return Task::Result::Warning;
}
if (std::find_if(sigs.begin(), sigs.end(), IsBad) != sigs.end()) {
return Task::Result::Danger;
}
if ((size_t)std::count_if(sigs.begin(), sigs.end(), IsGoodOrValid) == sigs.size()) {
return Task::Result::AllGood;
}
return Task::Result::Warning;
}
static QString formatVerificationResultOverview(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info)
{
if (res.isNull()) {
return QString();
}
const Error err = res.error();
if (err.isCanceled()) {
return i18n("<b>Verification canceled.</b>");
} else if (err) {
- return i18n("<b>Verification failed: %1.</b>", QString::fromLocal8Bit(err.asString()).toHtmlEscaped());
+ return i18n("<b>Verification failed: %1.</b>", Formatting::errorAsString(err).toHtmlEscaped());
}
const std::vector<Signature> sigs = res.signatures();
if (sigs.empty()) {
return i18n("<b>No signatures found.</b>");
}
const uint bad = std::count_if(sigs.cbegin(), sigs.cend(), IsBad);
if (bad > 0) {
return i18np("<b>Invalid signature.</b>", "<b>%1 invalid signatures.</b>", bad);
}
const uint warn = std::count_if(sigs.cbegin(), sigs.cend(), [](const Signature &sig) { return !IsGoodOrValid(sig); });
if (warn == sigs.size()) {
return i18np("<b>The data could not be verified.</b>", "<b>%1 signatures could not be verified.</b>", warn);
}
//Good signature:
QString text;
if (sigs.size() == 1) {
text = i18n("<b>Valid signature by %1</b>", renderKeyEMailOnlyNameAsFallback(sigs[0].key()));
if (info.conflicts())
text += i18n("<br/><b>Warning:</b> The sender's mail address is not stored in the %1 used for signing.",
renderKeyLink(QLatin1String(sigs[0].key().primaryFingerprint()), i18n("certificate")));
} else {
text = i18np("<b>Valid signature.</b>", "<b>%1 valid signatures.</b>", sigs.size());
if (info.conflicts()) {
text += i18n("<br/><b>Warning:</b> The sender's mail address is not stored in the certificates used for signing.");
}
}
return text;
}
static QString formatDecryptionResultOverview(const DecryptionResult &result, const QString &errorString = QString())
{
const Error err = result.error();
if (err.isCanceled()) {
return i18n("<b>Decryption canceled.</b>");
}
else if (result.isLegacyCipherNoMDC()) {
return i18n("<b>Decryption failed: %1.</b>", i18n("No integrity protection (MDC)."));
}
else if (!errorString.isEmpty()) {
return i18n("<b>Decryption failed: %1.</b>", errorString.toHtmlEscaped());
} else if (err) {
- return i18n("<b>Decryption failed: %1.</b>", QString::fromLocal8Bit(err.asString()).toHtmlEscaped());
+ return i18n("<b>Decryption failed: %1.</b>", Formatting::errorAsString(err).toHtmlEscaped());
}
return i18n("<b>Decryption succeeded.</b>");
}
static QString formatSignature(const Signature &sig, const DecryptVerifyResult::SenderInfo &info)
{
if (sig.isNull()) {
return QString();
}
const QString text = formatSigningInformation(sig) + QLatin1String("<br/>");
const Key key = sig.key();
// Green
if (sig.summary() & Signature::Valid) {
const UserID id = findUserIDByMailbox(key, info.informativeSender);
return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0));
}
// Red
if ((sig.summary() & Signature::Red)) {
const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
if (sig.summary() & Signature::SysError) {
- return ret + QStringLiteral(" (%1)").arg(QString::fromLocal8Bit(sig.status().asString()));
+ return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status()));
}
return ret;
}
// Key missing
if ((sig.summary() & Signature::KeyMissing)) {
return text + i18n("You can search the certificate on a keyserver or import it from a file.");
}
// Yellow
if ((sig.validity() & Signature::Validity::Undefined) ||
(sig.validity() & Signature::Validity::Unknown) ||
(sig.summary() == Signature::Summary::None)) {
return text + (key.protocol() == OpenPGP ? i18n("The used key is not certified by you or any trusted person.") :
i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."));
}
// Catch all fall through
const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
if (sig.summary() & Signature::SysError) {
- return ret + QStringLiteral(" (%1)").arg(QString::fromLocal8Bit(sig.status().asString()));
+ return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status()));
}
return ret;
}
static QStringList format(const std::vector<Mailbox> &mbxs)
{
QStringList res;
std::transform(mbxs.cbegin(), mbxs.cend(), std::back_inserter(res),
[](const Mailbox &mbox) {
return mbox.prettyAddress();
});
return res;
}
static QString formatVerificationResultDetails(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info, const QString &errorString)
{
if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) {
return i18n("Input error: %1", errorString);
}
const std::vector<Signature> sigs = res.signatures();
QString details;
for (const Signature &sig : sigs) {
details += formatSignature(sig, info) + QLatin1Char('\n');
}
details = details.trimmed();
details.replace(QLatin1Char('\n'), QStringLiteral("<br/><br/>"));
if (info.conflicts()) {
details += i18n("<p>The sender's address %1 is not stored in the certificate. Stored: %2</p>", info.informativeSender.prettyAddress(), format(info.signerMailboxes()).join(i18nc("separator for a list of e-mail addresses", ", ")));
}
return details;
}
static QString formatRecipientsDetails(const std::vector<Key> &knownRecipients, unsigned int numRecipients)
{
if (numRecipients == 0) {
return {};
}
if (knownRecipients.empty()) {
return QLatin1String("<i>") +
i18np("One unknown recipient.", "%1 unknown recipients.", numRecipients) +
QLatin1String("</i>");
}
QString details = i18np("Recipient:", "Recipients:", numRecipients);
if (numRecipients == 1) {
details += QLatin1Char(' ') + renderKey(knownRecipients.front());
} else {
details += QLatin1String("<ul>");
for (const Key &key : knownRecipients) {
details += QLatin1String("<li>") + renderKey(key) + QLatin1String("</li>");
}
if (knownRecipients.size() < numRecipients) {
details += QLatin1String("<li><i>") +
i18np("One unknown recipient", "%1 unknown recipients",
numRecipients - knownRecipients.size()) +
QLatin1String("</i></li>");
}
details += QLatin1String("</ul>");
}
return details;
}
static QString formatDecryptionResultDetails(const DecryptionResult &res, const std::vector<Key> &recipients,
const QString &errorString, bool isSigned, const QPointer<Task> &task)
{
if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) {
return i18n("Input error: %1", errorString);
}
if (res.isNull() || res.error() || res.error().isCanceled()) {
return formatRecipientsDetails(recipients, res.numRecipients());
}
QString details;
if (DeVSCompliance::isCompliant()) {
details += ((res.isDeVs()
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The decryption is %1.", DeVSCompliance::name(true))
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The decryption <b>is not</b> %1.", DeVSCompliance::name(true)))
+ QStringLiteral("<br/>"));
}
if (res.fileName()) {
const auto decVerifyTask = qobject_cast<AbstractDecryptVerifyTask*> (task.data());
if (decVerifyTask) {
const auto embedFileName = QString::fromUtf8(res.fileName()).toHtmlEscaped();
if (embedFileName != decVerifyTask->outputLabel()) {
details += i18n("Embedded file name: '%1'", embedFileName);
details += QStringLiteral("<br/>");
}
}
}
if (!isSigned) {
details += i18n("<b>Note:</b> You cannot be sure who encrypted this message as it is not signed.")
+ QLatin1String("<br/>");
}
if (res.isLegacyCipherNoMDC()) {
details += i18nc("Integrity protection was missing because an old cipher was used.",
"<b>Hint:</b> If this file was encrypted before the year 2003 it is "
"likely that the file is legitimate. This is because back "
"then integrity protection was not widely used.") + QStringLiteral("<br/><br/>") +
i18nc("The user is offered to force decrypt a non integrity protected message. With the strong advice to re-encrypt it.",
"If you are confident that the file was not manipulated you should re-encrypt it after you have forced the decryption.") +
QStringLiteral("<br/><br/>");
}
details += formatRecipientsDetails(recipients, res.numRecipients());
return details;
}
static QString formatDecryptVerifyResultOverview(const DecryptionResult &dr, const VerificationResult &vr, const DecryptVerifyResult::SenderInfo &info)
{
if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) {
return formatDecryptionResultOverview(dr);
}
return formatVerificationResultOverview(vr, info);
}
static QString formatDecryptVerifyResultDetails(const DecryptionResult &dr,
const VerificationResult &vr,
const std::vector<Key> &recipients,
const DecryptVerifyResult::SenderInfo &info,
const QString &errorString,
const QPointer<Task> &task)
{
const QString drDetails = formatDecryptionResultDetails(dr, recipients, errorString, relevantInDecryptVerifyContext(vr), task);
if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) {
return drDetails;
}
return drDetails + (drDetails.isEmpty() ? QString() : QStringLiteral("<br/>")) + formatVerificationResultDetails(vr, info, errorString);
}
} // anon namespace
class DecryptVerifyResult::Private
{
DecryptVerifyResult *const q;
public:
Private(DecryptVerifyOperation type,
const VerificationResult &vr,
const DecryptionResult &dr,
const QByteArray &stuff,
const QString &fileName,
const GpgME::Error &error,
const QString &errString,
const QString &input,
const QString &output,
const AuditLogEntry &auditLog,
Task *parentTask,
const Mailbox &informativeSender,
DecryptVerifyResult *qq) :
q(qq),
m_type(type),
m_verificationResult(vr),
m_decryptionResult(dr),
m_stuff(stuff),
m_fileName(fileName),
m_error(error),
m_errorString(errString),
m_inputLabel(input),
m_outputLabel(output),
m_auditLog(auditLog),
m_parentTask(QPointer<Task>(parentTask)),
m_informativeSender(informativeSender)
{
}
QString label() const
{
return formatInputOutputLabel(m_inputLabel, m_outputLabel, false, q->hasError());
}
DecryptVerifyResult::SenderInfo makeSenderInfo() const;
bool isDecryptOnly() const
{
return m_type == Decrypt;
}
bool isVerifyOnly() const
{
return m_type == Verify;
}
bool isDecryptVerify() const
{
return m_type == DecryptVerify;
}
DecryptVerifyOperation m_type;
VerificationResult m_verificationResult;
DecryptionResult m_decryptionResult;
QByteArray m_stuff;
QString m_fileName;
GpgME::Error m_error;
QString m_errorString;
QString m_inputLabel;
QString m_outputLabel;
const AuditLogEntry m_auditLog;
QPointer <Task> m_parentTask;
const Mailbox m_informativeSender;
};
DecryptVerifyResult::SenderInfo DecryptVerifyResult::Private::makeSenderInfo() const
{
return SenderInfo(m_informativeSender, KeyCache::instance()->findSigners(m_verificationResult));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptResult(const DecryptionResult &dr, const QByteArray &plaintext, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
Decrypt,
VerificationResult(),
dr,
plaintext,
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptResult(const GpgME::Error &err, const QString &what, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
Decrypt,
VerificationResult(),
DecryptionResult(err),
QByteArray(),
{},
err,
what,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptVerifyResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plaintext, const QString &fileName, const AuditLogEntry &auditLog)
{
const auto err = dr.error().code() ? dr.error() : vr.error();
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
DecryptVerify,
vr,
dr,
plaintext,
fileName,
err,
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptVerifyResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
DecryptVerify,
VerificationResult(),
DecryptionResult(err),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const VerificationResult &vr, const QByteArray &plaintext, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
Verify,
vr,
DecryptionResult(),
plaintext,
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
Verify,
VerificationResult(err),
DecryptionResult(),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyDetachedResult(const VerificationResult &vr, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
Verify,
vr,
DecryptionResult(),
QByteArray(),
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyDetachedResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
Verify,
VerificationResult(err),
DecryptionResult(),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
DecryptVerifyResult::DecryptVerifyResult(DecryptVerifyOperation type,
const VerificationResult &vr,
const DecryptionResult &dr,
const QByteArray &stuff,
const QString &fileName,
const GpgME::Error &error,
const QString &errString,
const QString &inputLabel,
const QString &outputLabel,
const AuditLogEntry &auditLog,
Task *parentTask,
const Mailbox &informativeSender)
: Task::Result(), d(new Private(type, vr, dr, stuff, fileName, error, errString, inputLabel, outputLabel, auditLog, parentTask, informativeSender, this))
{
}
QString DecryptVerifyResult::overview() const
{
QString ov;
if (d->isDecryptOnly()) {
ov += formatDecryptionResultOverview(d->m_decryptionResult);
} else if (d->isVerifyOnly()) {
ov += formatVerificationResultOverview(d->m_verificationResult, d->makeSenderInfo());
} else {
ov += formatDecryptVerifyResultOverview(d->m_decryptionResult, d->m_verificationResult, d->makeSenderInfo());
}
if (ov.size() + d->label().size() > 120) {
// Avoid ugly breaks
ov = QStringLiteral("<br>") + ov;
}
return i18nc("label: result example: foo.sig: Verification failed. ", "%1: %2", d->label(), ov);
}
QString DecryptVerifyResult::details() const
{
if (d->isDecryptOnly()) {
return formatDecryptionResultDetails(d->m_decryptionResult,
KeyCache::instance()->findRecipients(d->m_decryptionResult),
errorString(), false, d->m_parentTask);
}
if (d->isVerifyOnly()) {
return formatVerificationResultDetails(d->m_verificationResult, d->makeSenderInfo(), errorString());
}
return formatDecryptVerifyResultDetails(d->m_decryptionResult,
d->m_verificationResult, KeyCache::instance()->findRecipients(
d->m_decryptionResult), d->makeSenderInfo(), errorString(),
d->m_parentTask);
}
GpgME::Error DecryptVerifyResult::error() const
{
return d->m_error;
}
QString DecryptVerifyResult::errorString() const
{
return d->m_errorString;
}
AuditLogEntry DecryptVerifyResult::auditLog() const
{
return d->m_auditLog;
}
QPointer<Task> DecryptVerifyResult::parentTask() const
{
return d->m_parentTask;
}
Task::Result::VisualCode DecryptVerifyResult::code() const
{
if ((d->m_type == DecryptVerify || d->m_type == Verify) && relevantInDecryptVerifyContext(verificationResult())) {
return codeForVerificationResult(verificationResult());
}
return hasError() ? NeutralError : NeutralSuccess;
}
GpgME::VerificationResult DecryptVerifyResult::verificationResult() const
{
return d->m_verificationResult;
}
GpgME::DecryptionResult DecryptVerifyResult::decryptionResult() const
{
return d->m_decryptionResult;
}
QString DecryptVerifyResult::fileName() const
{
return d->m_fileName;
}
class AbstractDecryptVerifyTask::Private
{
public:
Mailbox informativeSender;
};
AbstractDecryptVerifyTask::AbstractDecryptVerifyTask(QObject *parent) : Task(parent), d(new Private) {}
AbstractDecryptVerifyTask::~AbstractDecryptVerifyTask() {}
Mailbox AbstractDecryptVerifyTask::informativeSender() const
{
return d->informativeSender;
}
void AbstractDecryptVerifyTask::setInformativeSender(const Mailbox &sender)
{
d->informativeSender = sender;
}
class DecryptVerifyTask::Private
{
DecryptVerifyTask *const q;
public:
explicit Private(DecryptVerifyTask *qq)
: q{qq}
{
}
void startDecryptVerifyJob();
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void startDecryptVerifyArchiveJob();
#endif
void slotResult(const DecryptionResult &, const VerificationResult &, const QByteArray & = {});
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
bool m_ignoreMDCError = false;
bool m_extractArchive = false;
QString m_outputDirectory;
};
void DecryptVerifyTask::Private::slotResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plainText)
{
updateKeys(vr);
{
std::stringstream ss;
ss << dr << '\n' << vr;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (m_output) {
if (dr.error().code() || vr.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!dr.isNull() || !vr.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
}
const int drErr = dr.error().code();
const QString errorString = m_output ? m_output->errorString() : QString{};
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) ||
(m_output && m_output->failed())) {
q->emitResult(q->fromDecryptResult(drErr ? dr.error() : Error::fromCode(GPG_ERR_EIO),
errorString, auditLog));
return;
}
q->emitResult(q->fromDecryptVerifyResult(dr, vr, plainText, m_output ? m_output->fileName() : QString{}, auditLog));
}
DecryptVerifyTask::DecryptVerifyTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
DecryptVerifyTask::~DecryptVerifyTask()
{
}
void DecryptVerifyTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void DecryptVerifyTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void DecryptVerifyTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = prot == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void DecryptVerifyTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature/ciphertext - maybe it is neither ciphertext nor a signature?"), Exception::MessageOnly);
}
setProtocol(p);
}
QString DecryptVerifyTask::label() const
{
return i18n("Decrypting: %1...", d->m_input->label());
}
unsigned long long DecryptVerifyTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString DecryptVerifyTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QString();
}
QString DecryptVerifyTask::outputLabel() const
{
return d->m_output ? d->m_output->label() : d->m_outputDirectory;
}
Protocol DecryptVerifyTask::protocol() const
{
return d->m_protocol;
}
void DecryptVerifyTask::cancel()
{
}
static void ensureIOOpen(QIODevice *input, QIODevice *output)
{
if (input && !input->isOpen()) {
input->open(QIODevice::ReadOnly);
}
if (output && !output->isOpen()) {
output->open(QIODevice::WriteOnly);
}
}
void DecryptVerifyTask::setIgnoreMDCError(bool value)
{
d->m_ignoreMDCError = value;
}
void DecryptVerifyTask::setExtractArchive(bool extract)
{
d->m_extractArchive = extract;
}
void DecryptVerifyTask::setOutputDirectory(const QString &directory)
{
d->m_outputDirectory = directory;
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
static bool archiveJobsCanBeUsed(GpgME::Protocol protocol)
{
return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported();
}
#endif
void DecryptVerifyTask::doStart()
{
kleo_assert(d->m_backend);
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) {
d->startDecryptVerifyArchiveJob();
} else
#endif
{
d->startDecryptVerifyJob();
}
}
static void setIgnoreMDCErrorFlag(QGpgME::Job *job, bool ignoreMDCError)
{
if (ignoreMDCError) {
qCDebug(KLEOPATRA_LOG) << "Modifying job to ignore MDC errors.";
auto ctx = QGpgME::Job::context(job);
if (!ctx) {
qCWarning(KLEOPATRA_LOG) << "Failed to get context for job";
} else {
const auto err = ctx->setFlag("ignore-mdc-error", "1");
if (err) {
- qCWarning(KLEOPATRA_LOG) << "Failed to set ignore mdc errors" << err.asString();
+ qCWarning(KLEOPATRA_LOG) << "Failed to set ignore mdc errors" << Formatting::errorAsString(err);
}
}
}
}
void DecryptVerifyTask::Private::startDecryptVerifyJob()
{
try {
QGpgME::DecryptVerifyJob *const job = m_backend->decryptVerifyJob();
kleo_assert(job);
setIgnoreMDCErrorFlag(job, m_ignoreMDCError);
QObject::connect(job, &QGpgME::DecryptVerifyJob::result, q, [this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult, const QByteArray &plainText) {
slotResult(decryptResult, verifyResult, plainText);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job, &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress);
#else
QObject::connect(job, &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
ensureIOOpen(m_input->ioDevice().get(), m_output->ioDevice().get());
job->start(m_input->ioDevice(), m_output->ioDevice());
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptVerifyResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void DecryptVerifyTask::Private::startDecryptVerifyArchiveJob()
{
auto *const job = m_backend->decryptVerifyArchiveJob();
kleo_assert(job);
setIgnoreMDCErrorFlag(job, m_ignoreMDCError);
job->setOutputDirectory(m_outputDirectory);
connect(job, &QGpgME::DecryptVerifyArchiveJob::result, q, [this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult) {
slotResult(decryptResult, verifyResult);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job, &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress);
#else
connect(job, &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
ensureIOOpen(m_input->ioDevice().get(), nullptr);
const auto err = job->start(m_input->ioDevice());
if (err) {
q->emitResult(q->fromDecryptVerifyResult(err, {}, {}));
}
}
#endif
class DecryptTask::Private
{
DecryptTask *const q;
public:
explicit Private(DecryptTask *qq)
: q{qq}
{
}
void slotResult(const DecryptionResult &, const QByteArray &);
void registerJob(QGpgME::DecryptJob *job)
{
q->connect(job, SIGNAL(result(GpgME::DecryptionResult,QByteArray)),
q, SLOT(slotResult(GpgME::DecryptionResult,QByteArray)));
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
q->connect(job, &QGpgME::Job::jobProgress, q, &DecryptTask::setProgress);
#else
q->connect(job, &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
}
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
};
void DecryptTask::Private::slotResult(const DecryptionResult &result, const QByteArray &plainText)
{
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (result.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!result.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
const int drErr = result.error().code();
const QString errorString = m_output->errorString();
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) ||
m_output->failed()) {
q->emitResult(q->fromDecryptResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO),
errorString, auditLog));
return;
}
q->emitResult(q->fromDecryptResult(result, plainText, auditLog));
}
DecryptTask::DecryptTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
DecryptTask::~DecryptTask()
{
}
void DecryptTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void DecryptTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void DecryptTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void DecryptTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this was S/MIME- or OpenPGP-encrypted - maybe it is not ciphertext at all?"), Exception::MessageOnly);
}
setProtocol(p);
}
QString DecryptTask::label() const
{
return i18n("Decrypting: %1...", d->m_input->label());
}
unsigned long long DecryptTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString DecryptTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QString();
}
QString DecryptTask::outputLabel() const
{
return d->m_output ? d->m_output->label() : QString();
}
Protocol DecryptTask::protocol() const
{
return d->m_protocol;
}
void DecryptTask::cancel()
{
}
void DecryptTask::doStart()
{
kleo_assert(d->m_backend);
try {
QGpgME::DecryptJob *const job = d->m_backend->decryptJob();
kleo_assert(job);
d->registerJob(job);
ensureIOOpen(d->m_input->ioDevice().get(), d->m_output->ioDevice().get());
job->start(d->m_input->ioDevice(), d->m_output->ioDevice());
} catch (const GpgME::Exception &e) {
emitResult(fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
class VerifyOpaqueTask::Private
{
VerifyOpaqueTask *const q;
public:
explicit Private(VerifyOpaqueTask *qq)
: q{qq}
{
}
void startVerifyOpaqueJob();
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void startDecryptVerifyArchiveJob();
#endif
void slotResult(const VerificationResult &, const QByteArray & = {});
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
bool m_extractArchive = false;
QString m_outputDirectory;
};
void VerifyOpaqueTask::Private::slotResult(const VerificationResult &result, const QByteArray &plainText)
{
updateKeys(result);
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (m_output) {
if (result.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!result.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
}
const int drErr = result.error().code();
const QString errorString = m_output ? m_output->errorString() : QString{};
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) ||
(m_output && m_output->failed())) {
q->emitResult(q->fromVerifyOpaqueResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO),
errorString, auditLog));
return;
}
q->emitResult(q->fromVerifyOpaqueResult(result, plainText, auditLog));
}
VerifyOpaqueTask::VerifyOpaqueTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
VerifyOpaqueTask::~VerifyOpaqueTask()
{
}
void VerifyOpaqueTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void VerifyOpaqueTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void VerifyOpaqueTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void VerifyOpaqueTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly);
}
setProtocol(p);
}
QString VerifyOpaqueTask::label() const
{
return i18n("Verifying: %1...", d->m_input->label());
}
unsigned long long VerifyOpaqueTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString VerifyOpaqueTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QString();
}
QString VerifyOpaqueTask::outputLabel() const
{
return d->m_output ? d->m_output->label() : d->m_outputDirectory;
}
Protocol VerifyOpaqueTask::protocol() const
{
return d->m_protocol;
}
void VerifyOpaqueTask::setExtractArchive(bool extract)
{
d->m_extractArchive = extract;
}
void VerifyOpaqueTask::setOutputDirectory(const QString &directory)
{
d->m_outputDirectory = directory;
}
void VerifyOpaqueTask::cancel()
{
}
void VerifyOpaqueTask::doStart()
{
kleo_assert(d->m_backend);
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) {
d->startDecryptVerifyArchiveJob();
} else
#endif
{
d->startVerifyOpaqueJob();
}
}
void VerifyOpaqueTask::Private::startVerifyOpaqueJob()
{
try {
QGpgME::VerifyOpaqueJob *const job = m_backend->verifyOpaqueJob();
kleo_assert(job);
connect(job, &QGpgME::VerifyOpaqueJob::result, q, [this](const GpgME::VerificationResult &result, const QByteArray &plainText) {
slotResult(result, plainText);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job, &QGpgME::Job::jobProgress, q, &VerifyOpaqueTask::setProgress);
#else
connect(job, &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
ensureIOOpen(m_input->ioDevice().get(), m_output ? m_output->ioDevice().get() : nullptr);
job->start(m_input->ioDevice(), m_output ? m_output->ioDevice() : std::shared_ptr<QIODevice>());
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void VerifyOpaqueTask::Private::startDecryptVerifyArchiveJob()
{
auto *const job = m_backend->decryptVerifyArchiveJob();
kleo_assert(job);
job->setOutputDirectory(m_outputDirectory);
connect(job, &QGpgME::DecryptVerifyArchiveJob::result, q, [this](const DecryptionResult &, const VerificationResult &verifyResult) {
slotResult(verifyResult);
});
connect(job, &QGpgME::DecryptVerifyArchiveJob::dataProgress, q, &VerifyOpaqueTask::setProgress);
ensureIOOpen(m_input->ioDevice().get(), nullptr);
const auto err = job->start(m_input->ioDevice());
if (err) {
q->emitResult(q->fromVerifyOpaqueResult(err, {}, {}));
}
}
#endif
class VerifyDetachedTask::Private
{
VerifyDetachedTask *const q;
public:
explicit Private(VerifyDetachedTask *qq)
: q{qq}
{
}
void slotResult(const VerificationResult &);
void registerJob(QGpgME::VerifyDetachedJob *job)
{
q->connect(job, SIGNAL(result(GpgME::VerificationResult)),
q, SLOT(slotResult(GpgME::VerificationResult)));
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
q->connect(job, &QGpgME::Job::jobProgress, q, &VerifyDetachedTask::setProgress);
#else
q->connect(job, &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
}
std::shared_ptr<Input> m_input, m_signedData;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
};
void VerifyDetachedTask::Private::slotResult(const VerificationResult &result)
{
updateKeys(result);
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
try {
kleo_assert(!result.isNull());
q->emitResult(q->fromVerifyDetachedResult(result, auditLog));
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
} catch (const std::exception &e) {
q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
} catch (...) {
q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
}
}
VerifyDetachedTask::VerifyDetachedTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
VerifyDetachedTask::~VerifyDetachedTask()
{
}
void VerifyDetachedTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void VerifyDetachedTask::setSignedData(const std::shared_ptr<Input> &signedData)
{
d->m_signedData = signedData;
kleo_assert(d->m_signedData && d->m_signedData->ioDevice());
}
void VerifyDetachedTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void VerifyDetachedTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly);
}
setProtocol(p);
}
unsigned long long VerifyDetachedTask::inputSize() const
{
return d->m_signedData ? d->m_signedData->size() : 0;
}
QString VerifyDetachedTask::label() const
{
if (d->m_signedData) {
return xi18nc("Verification of a detached signature in progress. The first file contains the data."
"The second file is the signature file.",
"Verifying: <filename>%1</filename> with <filename>%2</filename>...",
d->m_signedData->label(),
d->m_input->label());
}
return i18n("Verifying signature: %1...", d->m_input->label());
}
QString VerifyDetachedTask::inputLabel() const
{
if (d->m_signedData && d->m_input) {
return xi18nc("Verification of a detached signature summary. The first file contains the data."
"The second file is signature.",
"Verified <filename>%1</filename> with <filename>%2</filename>",
d->m_signedData->label(),
d->m_input->label());
}
return d->m_input ? d->m_input->label() : QString();
}
QString VerifyDetachedTask::outputLabel() const
{
return QString();
}
Protocol VerifyDetachedTask::protocol() const
{
return d->m_protocol;
}
void VerifyDetachedTask::cancel()
{
}
void VerifyDetachedTask::doStart()
{
kleo_assert(d->m_backend);
try {
QGpgME::VerifyDetachedJob *const job = d->m_backend->verifyDetachedJob();
kleo_assert(job);
d->registerJob(job);
ensureIOOpen(d->m_input->ioDevice().get(), nullptr);
ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr);
job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice());
} catch (const GpgME::Exception &e) {
emitResult(fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
#include "moc_decryptverifytask.cpp"
diff --git a/src/crypto/encryptemailtask.cpp b/src/crypto/encryptemailtask.cpp
index 8abdeac75..dd7ad1822 100644
--- a/src/crypto/encryptemailtask.cpp
+++ b/src/crypto/encryptemailtask.cpp
@@ -1,237 +1,239 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/encryptemailtask.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "encryptemailtask.h"
#include <utils/input.h>
#include <utils/output.h>
#include <utils/kleo_assert.h>
#include <Libkleo/AuditLogEntry>
+#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
+
#include <QGpgME/Protocol>
#include <QGpgME/EncryptJob>
#include <gpgme++/encryptionresult.h>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QPointer>
#include <QTextDocument> // for Qt::escape
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
namespace
{
class EncryptEMailResult : public Task::Result
{
const EncryptionResult m_result;
const AuditLogEntry m_auditLog;
public:
EncryptEMailResult(const EncryptionResult &r, const AuditLogEntry &auditLog)
: Task::Result(), m_result(r), m_auditLog(auditLog) {}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override;
QString errorString() const override;
VisualCode code() const override;
AuditLogEntry auditLog() const override;
};
QString makeResultString(const EncryptionResult &res)
{
const Error err = res.error();
if (err.isCanceled()) {
return i18n("Encryption canceled.");
}
if (err) {
- return i18n("Encryption failed: %1", QString::fromLocal8Bit(err.asString()).toHtmlEscaped());
+ return i18n("Encryption failed: %1", Formatting::errorAsString(err).toHtmlEscaped());
}
return i18n("Encryption succeeded.");
}
}
class EncryptEMailTask::Private
{
friend class ::Kleo::Crypto::EncryptEMailTask;
EncryptEMailTask *const q;
public:
explicit Private(EncryptEMailTask *qq);
private:
std::unique_ptr<QGpgME::EncryptJob> createJob(GpgME::Protocol proto);
private:
void slotResult(const EncryptionResult &);
private:
std::shared_ptr<Input> input;
std::shared_ptr<Output> output;
std::vector<Key> recipients;
QPointer<QGpgME::EncryptJob> job;
};
EncryptEMailTask::Private::Private(EncryptEMailTask *qq)
: q(qq),
input(),
output(),
job(nullptr)
{
}
EncryptEMailTask::EncryptEMailTask(QObject *p)
: Task(p), d(new Private(this))
{
}
EncryptEMailTask::~EncryptEMailTask() {}
void EncryptEMailTask::setInput(const std::shared_ptr<Input> &input)
{
kleo_assert(!d->job);
kleo_assert(input);
d->input = input;
}
void EncryptEMailTask::setOutput(const std::shared_ptr<Output> &output)
{
kleo_assert(!d->job);
kleo_assert(output);
d->output = output;
}
void EncryptEMailTask::setRecipients(const std::vector<Key> &recipients)
{
kleo_assert(!d->job);
kleo_assert(!recipients.empty());
d->recipients = recipients;
}
Protocol EncryptEMailTask::protocol() const
{
kleo_assert(!d->recipients.empty());
return d->recipients.front().protocol();
}
QString EncryptEMailTask::label() const
{
return d->input ? d->input->label() : QString();
}
unsigned long long EncryptEMailTask::inputSize() const
{
return d->input ? d->input->size() : 0;
}
void EncryptEMailTask::doStart()
{
kleo_assert(!d->job);
kleo_assert(d->input);
kleo_assert(d->output);
kleo_assert(!d->recipients.empty());
std::unique_ptr<QGpgME::EncryptJob> job = d->createJob(protocol());
kleo_assert(job.get());
job->start(d->recipients,
d->input->ioDevice(), d->output->ioDevice(),
/*alwaysTrust=*/true);
d->job = job.release();
}
void EncryptEMailTask::cancel()
{
if (d->job) {
d->job->slotCancel();
}
}
std::unique_ptr<QGpgME::EncryptJob> EncryptEMailTask::Private::createJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt();
std::unique_ptr<QGpgME::EncryptJob> encryptJob(backend->encryptJob(shouldArmor, /*textmode=*/false));
kleo_assert(encryptJob.get());
if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) {
encryptJob->setOutputIsBase64Encoded(true);
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &EncryptEMailTask::setProgress);
#else
connect(encryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult,QByteArray)),
q, SLOT(slotResult(GpgME::EncryptionResult)));
return encryptJob;
}
void EncryptEMailTask::Private::slotResult(const EncryptionResult &result)
{
const auto *const job = qobject_cast<const QGpgME::Job *>(q->sender());
if (result.error().code()) {
output->cancel();
} else {
output->finalize();
}
q->emitResult(std::shared_ptr<Result>(new EncryptEMailResult(result, AuditLogEntry::fromJob(job))));
}
QString EncryptEMailResult::overview() const
{
return makeOverview(makeResultString(m_result));
}
QString EncryptEMailResult::details() const
{
return QString();
}
GpgME::Error EncryptEMailResult::error() const
{
return m_result.error();
}
QString EncryptEMailResult::errorString() const
{
return hasError() ? makeResultString(m_result) : QString();
}
AuditLogEntry EncryptEMailResult::auditLog() const
{
return m_auditLog;
}
Task::Result::VisualCode EncryptEMailResult::code() const
{
if (m_result.error().isCanceled()) {
return Warning;
}
return m_result.error().code() ? NeutralError : NeutralSuccess;
}
#include "moc_encryptemailtask.cpp"
diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp
index b7392d384..18d49632a 100644
--- a/src/crypto/gui/certificatelineedit.cpp
+++ b/src/crypto/gui/certificatelineedit.cpp
@@ -1,721 +1,721 @@
/* crypto/gui/certificatelineedit.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
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 "certificatelineedit.h"
#include "commands/detailscommand.h"
#include "dialogs/groupdetailsdialog.h"
#include "utils/accessibility.h"
#include "view/errorlabel.h"
#include <QAccessible>
#include <QCompleter>
#include <QPushButton>
#include <QAction>
#include <QSignalBlocker>
#include "kleopatra_debug.h"
#include <Libkleo/Debug>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QMenu>
#include <QToolButton>
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
Q_DECLARE_METATYPE(KeyGroup)
static QStringList s_lookedUpKeys;
namespace
{
class CompletionProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
CompletionProxyModel(QObject *parent = nullptr)
: KeyListSortFilterProxyModel(parent)
{
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// completion pop-up
return 1;
}
QVariant data(const QModelIndex &idx, int role) const override
{
if (!idx.isValid()) {
return QVariant();
}
switch (role) {
case Qt::DecorationRole: {
const auto key = KeyListSortFilterProxyModel::data(idx, KeyList::KeyRole).value<GpgME::Key>();
if (!key.isNull()) {
return Kleo::Formatting::iconForUid(key.userID(0));
}
const auto group = KeyListSortFilterProxyModel::data(idx, KeyList::GroupRole).value<KeyGroup>();
if (!group.isNull()) {
return QIcon::fromTheme(QStringLiteral("group"));
}
Q_ASSERT(!key.isNull() || !group.isNull());
return QVariant();
}
default:
return KeyListSortFilterProxyModel::data(index(idx.row(), KeyList::Summary), role);
}
}
};
auto createSeparatorAction(QObject *parent)
{
auto action = new QAction{parent};
action->setSeparator(true);
return action;
}
} // namespace
class CertificateLineEdit::Private
{
CertificateLineEdit *q;
public:
enum class Status
{
Empty, //< text is empty
Success, //< a certificate or group is set
None, //< entered text does not match any certificates or groups
Ambiguous, //< entered text matches multiple certificates or groups
};
enum class CursorPositioning
{
MoveToEnd,
KeepPosition,
MoveToStart,
Default = MoveToEnd,
};
explicit Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter);
QString text() const;
void setKey(const GpgME::Key &key);
void setGroup(const KeyGroup &group);
void setKeyFilter(const std::shared_ptr<KeyFilter> &filter);
void setAccessibleName(const QString &s);
private:
void updateKey(CursorPositioning positioning);
void editChanged();
void editFinished();
void checkLocate();
void onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector<GpgME::Key> &keys);
void openDetailsDialog();
void setTextWithBlockedSignals(const QString &s, CursorPositioning positioning);
void showContextMenu(const QPoint &pos);
QString errorMessage() const;
QIcon statusIcon() const;
QString statusToolTip() const;
void updateStatusAction();
void updateErrorLabel();
void updateAccessibleNameAndDescription();
public:
Status mStatus = Status::Empty;
bool mEditingInProgress = false;
GpgME::Key mKey;
KeyGroup mGroup;
struct Ui {
explicit Ui(QWidget *parent)
: lineEdit{parent}
, button{parent}
, errorLabel{parent}
{}
QLineEdit lineEdit;
QToolButton button;
ErrorLabel errorLabel;
} ui;
private:
QString mAccessibleName;
KeyListSortFilterProxyModel *const mFilterModel;
KeyListSortFilterProxyModel *const mCompleterFilterModel;
QCompleter *mCompleter = nullptr;
std::shared_ptr<KeyFilter> mFilter;
QAction *const mStatusAction;
QAction *const mShowDetailsAction;
QPointer<QGpgME::Job> mLocateJob;
};
CertificateLineEdit::Private::Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter)
: q{qq}
, ui{qq}
, mFilterModel{new KeyListSortFilterProxyModel{qq}}
, mCompleterFilterModel{new CompletionProxyModel{qq}}
, mCompleter{new QCompleter{qq}}
, mFilter{std::shared_ptr<KeyFilter>{filter}}
, mStatusAction{new QAction{qq}}
, mShowDetailsAction{new QAction{qq}}
{
ui.lineEdit.setPlaceholderText(i18n("Please enter a name or email address..."));
ui.lineEdit.setClearButtonEnabled(true);
ui.lineEdit.setContextMenuPolicy(Qt::CustomContextMenu);
ui.lineEdit.addAction(mStatusAction, QLineEdit::LeadingPosition);
mCompleterFilterModel->setKeyFilter(mFilter);
mCompleterFilterModel->setSourceModel(model);
mCompleter->setModel(mCompleterFilterModel);
mCompleter->setFilterMode(Qt::MatchContains);
mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
ui.lineEdit.setCompleter(mCompleter);
ui.button.setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
ui.button.setToolTip(i18n("Show certificate list"));
ui.button.setAccessibleName(i18n("Show certificate list"));
ui.errorLabel.setVisible(false);
auto vbox = new QVBoxLayout{q};
vbox->setContentsMargins(0, 0, 0, 0);
auto l = new QHBoxLayout;
l->setContentsMargins(0, 0, 0, 0);
l->addWidget(&ui.lineEdit);
l->addWidget(&ui.button);
vbox->addLayout(l);
vbox->addWidget(&ui.errorLabel);
q->setFocusPolicy(ui.lineEdit.focusPolicy());
q->setFocusProxy(&ui.lineEdit);
mShowDetailsAction->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
mShowDetailsAction->setText(i18nc("@action:inmenu", "Show Details"));
mShowDetailsAction->setEnabled(false);
mFilterModel->setSourceModel(model);
mFilterModel->setFilterKeyColumn(KeyList::Summary);
if (filter) {
mFilterModel->setKeyFilter(mFilter);
}
connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged,
q, [this]() { updateKey(CursorPositioning::KeepPosition); });
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupUpdated,
q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
setTextWithBlockedSignals(Formatting::summaryLine(group), CursorPositioning::KeepPosition);
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(q, [this]() { updateKey(CursorPositioning::KeepPosition); }, Qt::QueuedConnection);
}
});
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupRemoved,
q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
mGroup = KeyGroup();
QSignalBlocker blocky{&ui.lineEdit};
ui.lineEdit.clear();
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(q, [this]() { updateKey(CursorPositioning::KeepPosition); }, Qt::QueuedConnection);
}
});
connect(&ui.lineEdit, &QLineEdit::editingFinished,
q, [this]() {
// queue the call of editFinished() to ensure that QCompleter::activated is handled first
QMetaObject::invokeMethod(q, [this]() { editFinished(); }, Qt::QueuedConnection);
});
connect(&ui.lineEdit, &QLineEdit::textChanged,
q, [this]() { editChanged(); });
connect(&ui.lineEdit, &QLineEdit::customContextMenuRequested,
q, [this](const QPoint &pos) { showContextMenu(pos); });
connect(mStatusAction, &QAction::triggered,
q, [this]() { openDetailsDialog(); });
connect(mShowDetailsAction, &QAction::triggered,
q, [this]() { openDetailsDialog(); });
connect(&ui.button, &QToolButton::clicked,
q, &CertificateLineEdit::certificateSelectionRequested);
connect(mCompleter, qOverload<const QModelIndex &>(&QCompleter::activated),
q, [this] (const QModelIndex &index) {
Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value<Key>();
auto group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value<KeyGroup>();
if (!key.isNull()) {
q->setKey(key);
} else if (!group.isNull()) {
q->setGroup(group);
} else {
qCDebug(KLEOPATRA_LOG) << "Activated item is neither key nor group";
}
});
updateKey(CursorPositioning::Default);
}
void CertificateLineEdit::Private::openDetailsDialog()
{
if (!q->key().isNull()) {
auto cmd = new Commands::DetailsCommand{q->key()};
cmd->setParentWidget(q);
cmd->start();
} else if (!q->group().isNull()) {
auto dlg = new Dialogs::GroupDetailsDialog{q};
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setGroup(q->group());
dlg->show();
}
}
void CertificateLineEdit::Private::setTextWithBlockedSignals(const QString &s, CursorPositioning positioning)
{
QSignalBlocker blocky{&ui.lineEdit};
const auto cursorPos = ui.lineEdit.cursorPosition();
ui.lineEdit.setText(s);
switch(positioning)
{
case CursorPositioning::KeepPosition:
ui.lineEdit.setCursorPosition(cursorPos);
break;
case CursorPositioning::MoveToStart:
ui.lineEdit.setCursorPosition(0);
break;
case CursorPositioning::MoveToEnd:
default:
; // setText() already moved the cursor to the end of the line
};
}
void CertificateLineEdit::Private::showContextMenu(const QPoint &pos)
{
if (QMenu *menu = ui.lineEdit.createStandardContextMenu()) {
auto *const firstStandardAction = menu->actions().value(0);
menu->insertActions(firstStandardAction,
{mShowDetailsAction, createSeparatorAction(menu)});
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(ui.lineEdit.mapToGlobal(pos));
}
}
CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model,
KeyFilter *filter,
QWidget *parent)
: QWidget{parent}
, d{new Private{this, model, filter}}
{
/* Take ownership of the model to prevent double deletion when the
* filter models are deleted */
model->setParent(parent ? parent : this);
}
CertificateLineEdit::~CertificateLineEdit() = default;
void CertificateLineEdit::Private::editChanged()
{
const bool editingStarted = !mEditingInProgress;
mEditingInProgress = true;
updateKey(CursorPositioning::Default);
if (editingStarted) {
Q_EMIT q->editingStarted();
}
}
void CertificateLineEdit::Private::editFinished()
{
// perform a first update with the "editing in progress" flag still set
updateKey(CursorPositioning::MoveToStart);
mEditingInProgress = false;
checkLocate();
// perform another update with the "editing in progress" flag cleared
// after a key locate may have been started; this makes sure that displaying
// an error is delayed until the key locate job has finished
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::Private::checkLocate()
{
if (mStatus != Status::None) {
// try to locate key only if text matches no local certificates or groups
return;
}
// Only check once per mailbox
const auto mailText = ui.lineEdit.text().trimmed();
if (mailText.isEmpty() || s_lookedUpKeys.contains(mailText)) {
return;
}
s_lookedUpKeys << mailText;
if (mLocateJob) {
mLocateJob->slotCancel();
mLocateJob.clear();
}
auto job = QGpgME::openpgp()->locateKeysJob();
connect(job, &QGpgME::KeyListJob::result, q, [this, job, mailText](const KeyListResult &result, const std::vector<GpgME::Key> &keys) {
onLocateJobResult(job, mailText, result, keys);
});
if (auto err = job->start({mailText}, /*secretOnly=*/false)) {
- qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Starting" << job << "for" << mailText << "failed with" << err.asString();
+ qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Starting" << job << "for" << mailText << "failed with" << Formatting::errorAsString(err);
} else {
mLocateJob = job;
qCDebug(KLEOPATRA_LOG) << __func__ << "Started" << job << "for" << mailText;
}
}
void CertificateLineEdit::Private::onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector<GpgME::Key> &keys)
{
if (mLocateJob != job) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Ignoring outdated finished" << job << "for" << email;
return;
}
- qCDebug(KLEOPATRA_LOG) << __func__ << job << "for" << email << "finished with" << result.error().asString() << "and keys" << keys;
+ qCDebug(KLEOPATRA_LOG) << __func__ << job << "for" << email << "finished with" << Formatting::errorAsString(result.error()) << "and keys" << keys;
mLocateJob.clear();
if (!keys.empty() && !keys.front().isNull()) {
KeyCache::mutableInstance()->insert(keys.front());
// inserting the key implicitly triggers an update
} else {
// explicitly trigger an update to display "no key" error
updateKey(CursorPositioning::MoveToStart);
}
}
void CertificateLineEdit::Private::updateKey(CursorPositioning positioning)
{
static const _detail::ByFingerprint<std::equal_to> keysHaveSameFingerprint;
const auto mailText = ui.lineEdit.text().trimmed();
auto newKey = Key();
auto newGroup = KeyGroup();
if (mailText.isEmpty()) {
mStatus = Status::Empty;
} else {
mFilterModel->setFilterRegularExpression(QRegularExpression::escape(mailText));
if (mFilterModel->rowCount() > 1) {
// keep current key or group if they still match
if (!mKey.isNull()) {
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
Key key = mFilterModel->key(index);
if (!key.isNull() && keysHaveSameFingerprint(key, mKey)) {
newKey = mKey;
break;
}
}
} else if (!mGroup.isNull()) {
newGroup = mGroup;
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
KeyGroup group = mFilterModel->group(index);
if (!group.isNull() && group.source() == mGroup.source() && group.id() == mGroup.id()) {
newGroup = mGroup;
break;
}
}
}
if (newKey.isNull() && newGroup.isNull()) {
mStatus = Status::Ambiguous;
}
} else if (mFilterModel->rowCount() == 1) {
const auto index = mFilterModel->index(0, 0);
newKey = mFilterModel->data(index, KeyList::KeyRole).value<Key>();
newGroup = mFilterModel->data(index, KeyList::GroupRole).value<KeyGroup>();
Q_ASSERT(!newKey.isNull() || !newGroup.isNull());
if (newKey.isNull() && newGroup.isNull()) {
mStatus = Status::None;
}
} else {
mStatus = Status::None;
}
}
mKey = newKey;
mGroup = newGroup;
if (!mKey.isNull()) {
/* FIXME: This needs to be solved by a multiple UID supporting model */
mStatus = Status::Success;
ui.lineEdit.setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions));
if (!mEditingInProgress) {
setTextWithBlockedSignals(Formatting::summaryLine(mKey), positioning);
}
} else if (!mGroup.isNull()) {
mStatus = Status::Success;
ui.lineEdit.setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions));
if (!mEditingInProgress) {
setTextWithBlockedSignals(Formatting::summaryLine(mGroup), positioning);
}
} else {
ui.lineEdit.setToolTip({});
}
mShowDetailsAction->setEnabled(mStatus == Status::Success);
updateStatusAction();
updateErrorLabel();
Q_EMIT q->keyChanged();
}
QString CertificateLineEdit::Private::errorMessage() const
{
switch (mStatus) {
case Status::Empty:
case Status::Success:
return {};
case Status::None:
return i18n("No matching certificates or groups found");
case Status::Ambiguous:
return i18n("Multiple matching certificates or groups found");
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast<int>(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
QIcon CertificateLineEdit::Private::statusIcon() const
{
switch (mStatus) {
case Status::Empty:
return QIcon::fromTheme(QStringLiteral("emblem-unavailable"));
case Status::Success:
if (!mKey.isNull()) {
return Formatting::iconForUid(mKey.userID(0));
} else if (!mGroup.isNull()) {
return Formatting::validityIcon(mGroup);
} else {
qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key nor group.";
return {};
}
case Status::None:
case Status::Ambiguous:
if (mEditingInProgress || mLocateJob) {
return QIcon::fromTheme(QStringLiteral("emblem-question"));
} else {
return QIcon::fromTheme(QStringLiteral("emblem-error"));
}
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast<int>(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
QString CertificateLineEdit::Private::statusToolTip() const
{
switch (mStatus) {
case Status::Empty:
return {};
case Status::Success:
if (!mKey.isNull()) {
return Formatting::validity(mKey.userID(0));
} else if (!mGroup.isNull()) {
return Formatting::validity(mGroup);
} else {
qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key nor group.";
return {};
}
case Status::None:
case Status::Ambiguous:
return errorMessage();
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast<int>(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
void CertificateLineEdit::Private::updateStatusAction()
{
mStatusAction->setIcon(statusIcon());
mStatusAction->setToolTip(statusToolTip());
}
namespace
{
QString decoratedError(const QString &text)
{
return text.isEmpty() ? QString() : i18nc("@info", "Error: %1", text);
}
}
void CertificateLineEdit::Private::updateErrorLabel()
{
const auto currentErrorMessage = ui.errorLabel.text();
const auto newErrorMessage = decoratedError(errorMessage());
if (newErrorMessage == currentErrorMessage) {
return;
}
if (currentErrorMessage.isEmpty() && (mEditingInProgress || mLocateJob)) {
// delay showing the error message until editing is finished, so that we
// do not annoy the user with an error message while they are still
// entering the recipient;
// on the other hand, we clear the error message immediately if it does
// not apply anymore and we update the error message immediately if it
// changed
return;
}
ui.errorLabel.setVisible(!newErrorMessage.isEmpty());
ui.errorLabel.setText(newErrorMessage);
updateAccessibleNameAndDescription();
}
void CertificateLineEdit::Private::setAccessibleName(const QString &s)
{
mAccessibleName = s;
updateAccessibleNameAndDescription();
}
void CertificateLineEdit::Private::updateAccessibleNameAndDescription()
{
// fall back to default accessible name if accessible name wasn't set explicitly
if (mAccessibleName.isEmpty()) {
mAccessibleName = getAccessibleName(&ui.lineEdit);
}
const bool errorShown = ui.errorLabel.isVisible();
// Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute);
// emulate this by setting the error message as accessible description of the input field
const auto description = errorShown ? ui.errorLabel.text() : QString{};
if (ui.lineEdit.accessibleDescription() != description) {
ui.lineEdit.setAccessibleDescription(description);
}
// Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute);
// screen readers say something like "invalid data" if this state is set;
// emulate this by adding "invalid data" to the accessible name of the input field
const auto name = errorShown ? mAccessibleName + QLatin1String{", "} + invalidEntryText()
: mAccessibleName;
if (ui.lineEdit.accessibleName() != name) {
ui.lineEdit.setAccessibleName(name);
}
}
Key CertificateLineEdit::key() const
{
if (isEnabled()) {
return d->mKey;
} else {
return Key();
}
}
KeyGroup CertificateLineEdit::group() const
{
if (isEnabled()) {
return d->mGroup;
} else {
return KeyGroup();
}
}
QString CertificateLineEdit::Private::text() const
{
return ui.lineEdit.text().trimmed();
}
QString CertificateLineEdit::text() const
{
return d->text();
}
void CertificateLineEdit::Private::setKey(const Key &key)
{
mKey = key;
mGroup = KeyGroup();
qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(key);
// position cursor, so that that the start of the summary is visible
setTextWithBlockedSignals(Formatting::summaryLine(key), CursorPositioning::MoveToStart);
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::setKey(const Key &key)
{
d->setKey(key);
}
void CertificateLineEdit::Private::setGroup(const KeyGroup &group)
{
mGroup = group;
mKey = Key();
const QString summary = Formatting::summaryLine(group);
qCDebug(KLEOPATRA_LOG) << "Setting KeyGroup. " << summary;
// position cursor, so that that the start of the summary is visible
setTextWithBlockedSignals(summary, CursorPositioning::MoveToStart);
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::setGroup(const KeyGroup &group)
{
d->setGroup(group);
}
bool CertificateLineEdit::isEmpty() const
{
return d->mStatus == Private::Status::Empty;
}
bool CertificateLineEdit::isEditingInProgress() const
{
return d->mEditingInProgress;
}
bool CertificateLineEdit::hasAcceptableInput() const
{
return d->mStatus == Private::Status::Empty
|| d->mStatus == Private::Status::Success;
}
void CertificateLineEdit::Private::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
mFilter = filter;
mFilterModel->setKeyFilter(filter);
mCompleterFilterModel->setKeyFilter(mFilter);
updateKey(CursorPositioning::Default);
}
void CertificateLineEdit::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
d->setKeyFilter(filter);
}
void CertificateLineEdit::setAccessibleNameOfLineEdit(const QString &name)
{
d->setAccessibleName(name);
}
#include "certificatelineedit.moc"
diff --git a/src/crypto/signemailtask.cpp b/src/crypto/signemailtask.cpp
index 05027517b..b05580150 100644
--- a/src/crypto/signemailtask.cpp
+++ b/src/crypto/signemailtask.cpp
@@ -1,289 +1,290 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/signemailtask.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "signemailtask.h"
#include <utils/input.h>
#include <utils/output.h>
#include <utils/kleo_assert.h>
#include <Libkleo/AuditLogEntry>
+#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
#include <QGpgME/Protocol>
#include <QGpgME/SignJob>
#include <gpgme++/signingresult.h>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QPointer>
#include <QTextDocument> // for Qt::escape
#include <algorithm>
#include <functional>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
namespace
{
class SignEMailResult : public Task::Result
{
const SigningResult m_result;
const AuditLogEntry m_auditLog;
public:
explicit SignEMailResult(const SigningResult &r, const AuditLogEntry &auditLog)
: Task::Result(), m_result(r), m_auditLog(auditLog) {}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override;
QString errorString() const override;
VisualCode code() const override;
AuditLogEntry auditLog() const override;
};
QString makeResultString(const SigningResult &res)
{
const Error err = res.error();
if (err.isCanceled()) {
return i18n("Signing canceled.");
}
if (err) {
- return i18n("Signing failed: %1", QString::fromLocal8Bit(err.asString()).toHtmlEscaped());
+ return i18n("Signing failed: %1", Formatting::errorAsString(err).toHtmlEscaped());
}
return i18n("Signing succeeded.");
}
}
class SignEMailTask::Private
{
friend class ::Kleo::Crypto::SignEMailTask;
SignEMailTask *const q;
public:
explicit Private(SignEMailTask *qq);
private:
std::unique_ptr<QGpgME::SignJob> createJob(GpgME::Protocol proto);
private:
void slotResult(const SigningResult &);
private:
std::shared_ptr<Input> input;
std::shared_ptr<Output> output;
std::vector<Key> signers;
bool detached;
bool clearsign;
QString micAlg;
QPointer<QGpgME::SignJob> job;
};
SignEMailTask::Private::Private(SignEMailTask *qq)
: q(qq),
input(),
output(),
signers(),
detached(false),
clearsign(false),
micAlg(),
job(nullptr)
{
}
SignEMailTask::SignEMailTask(QObject *p)
: Task(p), d(new Private(this))
{
}
SignEMailTask::~SignEMailTask() {}
void SignEMailTask::setInput(const std::shared_ptr<Input> &input)
{
kleo_assert(!d->job);
kleo_assert(input);
d->input = input;
}
void SignEMailTask::setOutput(const std::shared_ptr<Output> &output)
{
kleo_assert(!d->job);
kleo_assert(output);
d->output = output;
}
void SignEMailTask::setSigners(const std::vector<Key> &signers)
{
kleo_assert(!d->job);
kleo_assert(!signers.empty());
kleo_assert(std::none_of(signers.cbegin(), signers.cend(), std::mem_fn(&Key::isNull)));
d->signers = signers;
}
void SignEMailTask::setDetachedSignature(bool detached)
{
kleo_assert(!d->job);
d->detached = detached;
d->clearsign = false;
}
void SignEMailTask::setClearsign(bool clear)
{
kleo_assert(!d->job);
d->clearsign = clear;
d->detached = false;
}
Protocol SignEMailTask::protocol() const
{
kleo_assert(!d->signers.empty());
return d->signers.front().protocol();
}
QString SignEMailTask::label() const
{
return d->input ? d->input->label() : QString();
}
unsigned long long SignEMailTask::inputSize() const
{
return d->input ? d->input->size() : 0;
}
void SignEMailTask::doStart()
{
kleo_assert(!d->job);
kleo_assert(d->input);
kleo_assert(d->output);
kleo_assert(!d->signers.empty());
d->micAlg.clear();
std::unique_ptr<QGpgME::SignJob> job = d->createJob(protocol());
kleo_assert(job.get());
job->start(d->signers,
d->input->ioDevice(), d->output->ioDevice(),
d->clearsign ? GpgME::Clearsigned : d->detached ? GpgME::Detached : GpgME::NormalSignatureMode);
d->job = job.release();
}
void SignEMailTask::cancel()
{
if (d->job) {
d->job->slotCancel();
}
}
std::unique_ptr<QGpgME::SignJob> SignEMailTask::Private::createJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt();
std::unique_ptr<QGpgME::SignJob> signJob(backend->signJob(/*armor=*/ shouldArmor, /*textmode=*/false));
kleo_assert(signJob.get());
if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) {
signJob->setOutputIsBase64Encoded(true);
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEMailTask::setProgress);
#else
connect(signJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(signJob.get(), SIGNAL(result(GpgME::SigningResult,QByteArray)),
q, SLOT(slotResult(GpgME::SigningResult)));
return signJob;
}
static QString collect_micalgs(const GpgME::SigningResult &result, GpgME::Protocol proto)
{
const std::vector<GpgME::CreatedSignature> css = result.createdSignatures();
QStringList micalgs;
std::transform(css.begin(), css.end(),
std::back_inserter(micalgs),
[](const GpgME::CreatedSignature &sig) {
return QString::fromLatin1(sig.hashAlgorithmAsString()).toLower();
});
if (proto == GpgME::OpenPGP)
for (QStringList::iterator it = micalgs.begin(), end = micalgs.end(); it != end; ++it) {
it->prepend(QLatin1String("pgp-"));
}
micalgs.sort();
micalgs.erase(std::unique(micalgs.begin(), micalgs.end()), micalgs.end());
return micalgs.join(QLatin1Char(','));
}
void SignEMailTask::Private::slotResult(const SigningResult &result)
{
const auto *const job = qobject_cast<const QGpgME::Job *>(q->sender());
if (result.error().code()) {
output->cancel();
} else {
output->finalize();
micAlg = collect_micalgs(result, q->protocol());
}
q->emitResult(std::shared_ptr<Result>(new SignEMailResult(result, AuditLogEntry::fromJob(job))));
}
QString SignEMailTask::micAlg() const
{
return d->micAlg;
}
QString SignEMailResult::overview() const
{
return makeOverview(makeResultString(m_result));
}
QString SignEMailResult::details() const
{
return QString();
}
GpgME::Error SignEMailResult::error() const
{
return m_result.error();
}
QString SignEMailResult::errorString() const
{
return hasError() ? makeResultString(m_result) : QString();
}
Task::Result::VisualCode SignEMailResult::code() const
{
if (m_result.error().isCanceled()) {
return Warning;
}
return m_result.error().code() ? NeutralError : NeutralSuccess;
}
AuditLogEntry SignEMailResult::auditLog() const
{
return m_auditLog;
}
#include "moc_signemailtask.cpp"
diff --git a/src/crypto/signencrypttask.cpp b/src/crypto/signencrypttask.cpp
index 0d5bbbcf6..f9fa5ff97 100644
--- a/src/crypto/signencrypttask.cpp
+++ b/src/crypto/signencrypttask.cpp
@@ -1,814 +1,814 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/signencrypttask.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "signencrypttask.h"
#include <utils/input.h>
#include <utils/output.h>
#include <utils/path-helper.h>
#include <utils/kleo_assert.h>
#include <Libkleo/AuditLogEntry>
#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
#include <Libkleo/KleoException>
#include <QGpgME/Protocol>
#include <QGpgME/SignJob>
#include <QGpgME/SignEncryptJob>
#include <QGpgME/EncryptJob>
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/EncryptArchiveJob>
#include <QGpgME/SignArchiveJob>
#include <QGpgME/SignEncryptArchiveJob>
#endif
#include <gpgme++/signingresult.h>
#include <gpgme++/encryptionresult.h>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <QFileInfo>
#include <QPointer>
#include <QTextDocument> // for Qt::escape
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
namespace
{
QString formatInputOutputLabel(const QString &input, const QString &output, bool outputDeleted)
{
return i18nc("Input file --> Output file (rarr is arrow", "%1 &rarr; %2",
input.toHtmlEscaped(),
outputDeleted ? QStringLiteral("<s>%1</s>").arg(output.toHtmlEscaped()) : output.toHtmlEscaped());
}
class ErrorResult : public Task::Result
{
public:
ErrorResult(bool sign, bool encrypt, const Error &err, const QString &errStr, const QString &input, const QString &output, const AuditLogEntry &auditLog)
: Task::Result(), m_sign(sign), m_encrypt(encrypt), m_error(err), m_errString(errStr), m_inputLabel(input), m_outputLabel(output), m_auditLog(auditLog) {}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override
{
return m_error;
}
QString errorString() const override
{
return m_errString;
}
VisualCode code() const override
{
return NeutralError;
}
AuditLogEntry auditLog() const override
{
return m_auditLog;
}
private:
const bool m_sign;
const bool m_encrypt;
const Error m_error;
const QString m_errString;
const QString m_inputLabel;
const QString m_outputLabel;
const AuditLogEntry m_auditLog;
};
namespace
{
struct LabelAndError
{
QString label;
QString errorString;
};
}
class SignEncryptFilesResult : public Task::Result
{
public:
SignEncryptFilesResult(const SigningResult &sr, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog)
: Task::Result(),
m_sresult(sr),
m_input{input},
m_output{output},
m_outputCreated(outputCreated),
m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString
<< "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_sresult.isNull());
}
SignEncryptFilesResult(const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog)
: Task::Result(),
m_eresult(er),
m_input{input},
m_output{output},
m_outputCreated(outputCreated),
m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString
<< "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_eresult.isNull());
}
SignEncryptFilesResult(const SigningResult &sr, const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog)
: Task::Result(),
m_sresult(sr),
m_eresult(er),
m_input{input},
m_output{output},
m_outputCreated(outputCreated),
m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString
<< "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_sresult.isNull() || !m_eresult.isNull());
}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override;
QString errorString() const override;
VisualCode code() const override;
AuditLogEntry auditLog() const override;
private:
const SigningResult m_sresult;
const EncryptionResult m_eresult;
const LabelAndError m_input;
const LabelAndError m_output;
const bool m_outputCreated;
const AuditLogEntry m_auditLog;
};
static QString makeSigningOverview(const Error &err)
{
if (err.isCanceled()) {
return i18n("Signing canceled.");
}
if (err) {
return i18n("Signing failed.");
}
return i18n("Signing succeeded.");
}
static QString makeResultOverview(const SigningResult &result)
{
return makeSigningOverview(result.error());
}
static QString makeEncryptionOverview(const Error &err)
{
if (err.isCanceled()) {
return i18n("Encryption canceled.");
}
if (err) {
return i18n("Encryption failed.");
}
return i18n("Encryption succeeded.");
}
static QString makeResultOverview(const EncryptionResult &result)
{
return makeEncryptionOverview(result.error());
}
static QString makeResultOverview(const SigningResult &sr, const EncryptionResult &er)
{
if (er.isNull() && sr.isNull()) {
return QString();
}
if (er.isNull()) {
return makeResultOverview(sr);
}
if (sr.isNull()) {
return makeResultOverview(er);
}
if (sr.error().isCanceled() || sr.error()) {
return makeResultOverview(sr);
}
if (er.error().isCanceled() || er.error()) {
return makeResultOverview(er);
}
return i18n("Signing and encryption succeeded.");
}
static QString escape(QString s)
{
s = s.toHtmlEscaped();
s.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
return s;
}
static QString makeResultDetails(const SigningResult &result, const QString &inputError, const QString &outputError)
{
const Error err = result.error();
if (err.code() == GPG_ERR_EIO) {
if (!inputError.isEmpty()) {
return i18n("Input error: %1", escape(inputError));
} else if (!outputError.isEmpty()) {
return i18n("Output error: %1", escape(outputError));
}
}
if (err || err.isCanceled()) {
- return QString::fromLocal8Bit(err.asString()).toHtmlEscaped();
+ return Formatting::errorAsString(err).toHtmlEscaped();
}
return QString();
}
static QString makeResultDetails(const EncryptionResult &result, const QString &inputError, const QString &outputError)
{
const Error err = result.error();
if (err.code() == GPG_ERR_EIO) {
if (!inputError.isEmpty()) {
return i18n("Input error: %1", escape(inputError));
} else if (!outputError.isEmpty()) {
return i18n("Output error: %1", escape(outputError));
}
}
if (err || err.isCanceled()) {
- return QString::fromLocal8Bit(err.asString()).toHtmlEscaped();
+ return Formatting::errorAsString(err).toHtmlEscaped();
}
return i18n(" Encryption succeeded.");
}
}
QString ErrorResult::overview() const
{
Q_ASSERT(m_error || m_error.isCanceled());
Q_ASSERT(m_sign || m_encrypt);
const QString label = formatInputOutputLabel(m_inputLabel, m_outputLabel, true);
const bool canceled = m_error.isCanceled();
if (m_sign && m_encrypt) {
return canceled ? i18n("%1: <b>Sign/encrypt canceled.</b>", label) : i18n(" %1: Sign/encrypt failed.", label);
}
return i18nc("label: result. Example: foo -> foo.gpg: Encryption failed.", "%1: <b>%2</b>", label,
m_sign ? makeSigningOverview(m_error) : makeEncryptionOverview(m_error));
}
QString ErrorResult::details() const
{
return m_errString;
}
class SignEncryptTask::Private
{
friend class ::Kleo::Crypto::SignEncryptTask;
SignEncryptTask *const q;
public:
explicit Private(SignEncryptTask *qq);
private:
void startSignEncryptJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignJob> createSignJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignEncryptJob> createSignEncryptJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::EncryptJob> createEncryptJob(GpgME::Protocol proto);
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void startSignEncryptArchiveJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignArchiveJob> createSignArchiveJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignEncryptArchiveJob> createSignEncryptArchiveJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::EncryptArchiveJob> createEncryptArchiveJob(GpgME::Protocol proto);
#endif
std::shared_ptr<const Task::Result> makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog);
private:
void slotResult(const SigningResult &);
void slotResult(const SigningResult &, const EncryptionResult &);
void slotResult(const EncryptionResult &);
void slotResult(const QGpgME::Job *, const SigningResult &, const EncryptionResult &);
private:
std::shared_ptr<Input> input;
std::shared_ptr<Output> output;
QStringList inputFileNames;
QString outputFileName;
std::vector<Key> signers;
std::vector<Key> recipients;
bool sign : 1;
bool encrypt : 1;
bool detached : 1;
bool symmetric: 1;
bool clearsign: 1;
bool archive : 1;
QPointer<QGpgME::Job> job;
std::shared_ptr<OverwritePolicy> m_overwritePolicy;
};
SignEncryptTask::Private::Private(SignEncryptTask *qq)
: q{qq}
, sign{true}
, encrypt{true}
, detached{false}
, clearsign{false}
, archive{false}
, m_overwritePolicy{new OverwritePolicy{nullptr}}
{
q->setAsciiArmor(true);
}
std::shared_ptr<const Task::Result> SignEncryptTask::Private::makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog)
{
return std::shared_ptr<const ErrorResult>(new ErrorResult(sign, encrypt, err, errStr, q->label(), output ? output->label() : QString{}, auditLog));
}
SignEncryptTask::SignEncryptTask(QObject *p)
: Task(p), d(new Private(this))
{
}
SignEncryptTask::~SignEncryptTask() {}
void SignEncryptTask::setInputFileName(const QString &fileName)
{
kleo_assert(!d->job);
kleo_assert(!fileName.isEmpty());
d->inputFileNames = QStringList(fileName);
}
void SignEncryptTask::setInputFileNames(const QStringList &fileNames)
{
kleo_assert(!d->job);
kleo_assert(!fileNames.empty());
d->inputFileNames = fileNames;
}
void SignEncryptTask::setInput(const std::shared_ptr<Input> &input)
{
kleo_assert(!d->job);
kleo_assert(input);
d->input = input;
}
void SignEncryptTask::setOutput(const std::shared_ptr<Output> &output)
{
kleo_assert(!d->job);
kleo_assert(output);
d->output = output;
}
void SignEncryptTask::setOutputFileName(const QString &fileName)
{
kleo_assert(!d->job);
kleo_assert(!fileName.isEmpty());
d->outputFileName = fileName;
}
void SignEncryptTask::setSigners(const std::vector<Key> &signers)
{
kleo_assert(!d->job);
d->signers = signers;
}
void SignEncryptTask::setRecipients(const std::vector<Key> &recipients)
{
kleo_assert(!d->job);
d->recipients = recipients;
}
void SignEncryptTask::setOverwritePolicy(const std::shared_ptr<OverwritePolicy> &policy)
{
kleo_assert(!d->job);
d->m_overwritePolicy = policy;
}
void SignEncryptTask::setSign(bool sign)
{
kleo_assert(!d->job);
d->sign = sign;
}
void SignEncryptTask::setEncrypt(bool encrypt)
{
kleo_assert(!d->job);
d->encrypt = encrypt;
}
void SignEncryptTask::setDetachedSignature(bool detached)
{
kleo_assert(!d->job);
d->detached = detached;
}
void SignEncryptTask::setEncryptSymmetric(bool symmetric)
{
kleo_assert(!d->job);
d->symmetric = symmetric;
}
void SignEncryptTask::setClearsign(bool clearsign)
{
kleo_assert(!d->job);
d->clearsign = clearsign;
}
void SignEncryptTask::setCreateArchive(bool archive)
{
kleo_assert(!d->job);
d->archive = archive;
}
Protocol SignEncryptTask::protocol() const
{
if (d->sign && !d->signers.empty()) {
return d->signers.front().protocol();
}
if (d->encrypt || d->symmetric) {
if (!d->recipients.empty()) {
return d->recipients.front().protocol();
} else {
return GpgME::OpenPGP; // symmetric OpenPGP encryption
}
}
throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL),
i18n("Cannot determine protocol for task"));
}
QString SignEncryptTask::label() const
{
if (d->input) {
return d->input->label();
} else if (!d->inputFileNames.empty()) {
const auto firstFile = QFileInfo{d->inputFileNames.front()}.fileName();
return d->inputFileNames.size() == 1 ? firstFile : i18nc("<name of first file>, ...", "%1, ...", firstFile);
}
return {};
}
QString SignEncryptTask::tag() const
{
return Formatting::displayName(protocol());
}
unsigned long long SignEncryptTask::inputSize() const
{
return d->input ? d->input->size() : 0U;
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
static bool archiveJobsCanBeUsed(GpgME::Protocol protocol)
{
return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported();
}
#endif
void SignEncryptTask::doStart()
{
kleo_assert(!d->job);
if (d->sign) {
kleo_assert(!d->signers.empty());
if (d->archive) {
kleo_assert(!d->detached && !d->clearsign);
}
}
if (!d->output) {
d->output = Output::createFromFile(d->outputFileName, d->m_overwritePolicy);
}
// release grab on policy, so that output can infer multiple outputs from use count > 1
d->m_overwritePolicy.reset();
const auto proto = protocol();
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
if (d->archive && archiveJobsCanBeUsed(proto)) {
d->startSignEncryptArchiveJob(proto);
} else
#endif
{
d->startSignEncryptJob(proto);
}
}
void SignEncryptTask::Private::startSignEncryptJob(GpgME::Protocol proto)
{
kleo_assert(input);
if (encrypt || symmetric) {
Context::EncryptionFlags flags = Context::AlwaysTrust;
if (symmetric) {
flags = static_cast<Context::EncryptionFlags>(flags | Context::Symmetric);
qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag";
}
if (sign) {
std::unique_ptr<QGpgME::SignEncryptJob> job = createSignEncryptJob(proto);
kleo_assert(job.get());
#if QGPGME_SUPPORTS_SET_FILENAME
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
#endif
job->start(signers, recipients,
input->ioDevice(), output->ioDevice(), flags);
this->job = job.release();
} else {
std::unique_ptr<QGpgME::EncryptJob> job = createEncryptJob(proto);
kleo_assert(job.get());
#if QGPGME_SUPPORTS_SET_FILENAME
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
#endif
job->start(recipients, input->ioDevice(), output->ioDevice(), flags);
this->job = job.release();
}
} else if (sign) {
std::unique_ptr<QGpgME::SignJob> job = createSignJob(proto);
kleo_assert(job.get());
kleo_assert(! (detached && clearsign));
job->start(signers,
input->ioDevice(), output->ioDevice(),
detached ? GpgME::Detached : clearsign ?
GpgME::Clearsigned : GpgME::NormalSignatureMode);
this->job = job.release();
} else {
kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!");
}
}
void SignEncryptTask::cancel()
{
if (d->job) {
d->job->slotCancel();
}
}
std::unique_ptr<QGpgME::SignJob> SignEncryptTask::Private::createSignJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignJob> signJob(backend->signJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(signJob.get());
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
#else
connect(signJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(signJob.get(), SIGNAL(result(GpgME::SigningResult,QByteArray)),
q, SLOT(slotResult(GpgME::SigningResult)));
return signJob;
}
std::unique_ptr<QGpgME::SignEncryptJob> SignEncryptTask::Private::createSignEncryptJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignEncryptJob> signEncryptJob(backend->signEncryptJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(signEncryptJob.get());
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(signEncryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
#else
connect(signEncryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(signEncryptJob.get(), SIGNAL(result(GpgME::SigningResult,GpgME::EncryptionResult,QByteArray)),
q, SLOT(slotResult(GpgME::SigningResult,GpgME::EncryptionResult)));
return signEncryptJob;
}
std::unique_ptr<QGpgME::EncryptJob> SignEncryptTask::Private::createEncryptJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::EncryptJob> encryptJob(backend->encryptJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(encryptJob.get());
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
#else
connect(encryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult,QByteArray)),
q, SLOT(slotResult(GpgME::EncryptionResult)));
return encryptJob;
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void SignEncryptTask::Private::startSignEncryptArchiveJob(GpgME::Protocol proto)
{
kleo_assert(!input);
const auto baseDirectory = heuristicBaseDirectory(inputFileNames);
if (baseDirectory.isEmpty()) {
throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", inputFileNames.join(QLatin1Char('\n'))));
}
qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << inputFileNames << ") ->" << baseDirectory;
const auto tempPaths = makeRelativeTo(baseDirectory, inputFileNames);
const auto relativePaths = std::vector<QString>{tempPaths.begin(), tempPaths.end()};
qCDebug(KLEOPATRA_LOG) << "relative paths:" << relativePaths;
if (encrypt || symmetric) {
Context::EncryptionFlags flags = Context::AlwaysTrust;
if (symmetric) {
flags = static_cast<Context::EncryptionFlags>(flags | Context::Symmetric);
qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag";
}
if (sign) {
std::unique_ptr<QGpgME::SignEncryptArchiveJob> job = createSignEncryptArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
job->start(signers, recipients, relativePaths, output->ioDevice(), flags);
this->job = job.release();
} else {
std::unique_ptr<QGpgME::EncryptArchiveJob> job = createEncryptArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
job->start(recipients, relativePaths, output->ioDevice(), flags);
this->job = job.release();
}
} else if (sign) {
std::unique_ptr<QGpgME::SignArchiveJob> job = createSignArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
job->start(signers, relativePaths, output->ioDevice());
this->job = job.release();
} else {
kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!");
}
}
std::unique_ptr<QGpgME::SignArchiveJob> SignEncryptTask::Private::createSignArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignArchiveJob> signJob(backend->signArchiveJob(q->asciiArmor()));
auto job = signJob.get();
kleo_assert(job);
connect(job, &QGpgME::SignArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::SignArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult) {
slotResult(job, signResult, EncryptionResult{});
});
return signJob;
}
std::unique_ptr<QGpgME::SignEncryptArchiveJob> SignEncryptTask::Private::createSignEncryptArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignEncryptArchiveJob> signEncryptJob(backend->signEncryptArchiveJob(q->asciiArmor()));
auto job = signEncryptJob.get();
kleo_assert(job);
connect(job, &QGpgME::SignEncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::SignEncryptArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult, const GpgME::EncryptionResult &encryptResult) {
slotResult(job, signResult, encryptResult);
});
return signEncryptJob;
}
std::unique_ptr<QGpgME::EncryptArchiveJob> SignEncryptTask::Private::createEncryptArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::EncryptArchiveJob> encryptJob(backend->encryptArchiveJob(q->asciiArmor()));
auto job = encryptJob.get();
kleo_assert(job);
connect(job, &QGpgME::EncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::EncryptArchiveJob::result, q, [this, job](const GpgME::EncryptionResult &encryptResult) {
slotResult(job, SigningResult{}, encryptResult);
});
return encryptJob;
}
#endif
void SignEncryptTask::Private::slotResult(const SigningResult &result)
{
slotResult(qobject_cast<const QGpgME::Job *>(q->sender()), result, EncryptionResult{});
}
void SignEncryptTask::Private::slotResult(const SigningResult &sresult, const EncryptionResult &eresult)
{
slotResult(qobject_cast<const QGpgME::Job *>(q->sender()), sresult, eresult);
}
void SignEncryptTask::Private::slotResult(const EncryptionResult &result)
{
slotResult(qobject_cast<const QGpgME::Job *>(q->sender()), SigningResult{}, result);
}
void SignEncryptTask::Private::slotResult(const QGpgME::Job *job, const SigningResult &sresult, const EncryptionResult &eresult)
{
const AuditLogEntry auditLog = AuditLogEntry::fromJob(job);
bool outputCreated = false;
if (input && input->failed()) {
output->cancel();
q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO),
i18n("Input error: %1", escape( input->errorString())),
auditLog));
return;
} else if (sresult.error().code() || eresult.error().code()) {
output->cancel();
} else {
try {
kleo_assert(!sresult.isNull() || !eresult.isNull());
output->finalize();
outputCreated = true;
if (input) {
input->finalize();
}
} catch (const GpgME::Exception &e) {
q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
}
}
const LabelAndError inputInfo{q->label(), input ? input->errorString() : QString{}};
const LabelAndError outputInfo{output->label(), output->errorString()};
q->emitResult(std::shared_ptr<Result>(new SignEncryptFilesResult(sresult, eresult, inputInfo, outputInfo, outputCreated, auditLog)));
}
QString SignEncryptFilesResult::overview() const
{
const QString files = formatInputOutputLabel(m_input.label, m_output.label, !m_outputCreated);
return files + QLatin1String(": ") + makeOverview(makeResultOverview(m_sresult, m_eresult));
}
QString SignEncryptFilesResult::details() const
{
return errorString();
}
GpgME::Error SignEncryptFilesResult::error() const
{
if (m_sresult.error().code()) {
return m_sresult.error();
}
if (m_eresult.error().code()) {
return m_eresult.error();
}
return {};
}
QString SignEncryptFilesResult::errorString() const
{
const bool sign = !m_sresult.isNull();
const bool encrypt = !m_eresult.isNull();
kleo_assert(sign || encrypt);
if (sign && encrypt) {
return
m_sresult.error().code() ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) :
m_eresult.error().code() ? makeResultDetails(m_eresult, m_input.errorString, m_output.errorString) :
QString();
}
return
sign ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) :
/*else*/ makeResultDetails(m_eresult, m_input.errorString, m_output.errorString);
}
Task::Result::VisualCode SignEncryptFilesResult::code() const
{
if (m_sresult.error().isCanceled() || m_eresult.error().isCanceled()) {
return Warning;
}
return (m_sresult.error().code() || m_eresult.error().code()) ? NeutralError : NeutralSuccess;
}
AuditLogEntry SignEncryptFilesResult::auditLog() const
{
return m_auditLog;
}
#include "moc_signencrypttask.cpp"
diff --git a/src/dialogs/certifywidget.cpp b/src/dialogs/certifywidget.cpp
index aec6b299e..c06ef999b 100644
--- a/src/dialogs/certifywidget.cpp
+++ b/src/dialogs/certifywidget.cpp
@@ -1,826 +1,826 @@
/* dialogs/certifywidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2019, 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certifywidget.h"
#include <utils/accessibility.h>
#include <utils/keys.h>
#include "view/infofield.h"
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <KConfigGroup>
#include <KDateComboBox>
#include <KMessageBox>
#include <KMessageWidget>
#include <KSeparator>
#include <KSharedConfig>
#include <Libkleo/Algorithm>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/Predicates>
#include <QGpgME/ChangeOwnerTrustJob>
#include <QGpgME/Protocol>
#include <QAction>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QListView>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QScrollArea>
#include <QStandardItemModel>
#include <QToolButton>
#include <QToolTip>
#include <QVBoxLayout>
#include <gpgme++/key.h>
using namespace Kleo;
static QDebug operator<<(QDebug s, const GpgME::UserID &userID)
{
return s << Formatting::prettyUserID(userID);
}
namespace {
// Maybe move this in its own file
// based on code from StackOverflow
class AnimatedExpander: public QWidget
{
Q_OBJECT
public:
explicit AnimatedExpander(const QString &title,
const QString &accessibleTitle = {},
QWidget *parent = nullptr);
void setContentLayout(QLayout *contentLayout);
private:
QGridLayout mainLayout;
QToolButton toggleButton;
QFrame headerLine;
QParallelAnimationGroup toggleAnimation;
QWidget contentArea;
int animationDuration{300};
};
AnimatedExpander::AnimatedExpander(const QString &title, const QString &accessibleTitle, QWidget *parent)
: QWidget{parent}
{
#ifdef Q_OS_WIN
// draw dotted focus frame if button has focus; otherwise, draw invisible frame using background color
toggleButton.setStyleSheet(QStringLiteral("QToolButton { border: 1px solid palette(window); }"
"QToolButton:focus { border: 1px dotted palette(window-text); }"));
#else
// this works with Breeze style because Breeze draws the focus frame when drawing CE_ToolButtonLabel
// while the Windows styles (and Qt's common base style) draw the focus frame before drawing CE_ToolButtonLabel
toggleButton.setStyleSheet(QStringLiteral("QToolButton { border: none; }"));
#endif
toggleButton.setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toggleButton.setArrowType(Qt::ArrowType::RightArrow);
toggleButton.setText(title);
if (!accessibleTitle.isEmpty()) {
toggleButton.setAccessibleName(accessibleTitle);
}
toggleButton.setCheckable(true);
toggleButton.setChecked(false);
headerLine.setFrameShape(QFrame::HLine);
headerLine.setFrameShadow(QFrame::Sunken);
headerLine.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
contentArea.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// start out collapsed
contentArea.setMaximumHeight(0);
contentArea.setMinimumHeight(0);
contentArea.setVisible(false);
// let the entire widget grow and shrink with its content
toggleAnimation.addAnimation(new QPropertyAnimation(this, "minimumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(this, "maximumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(&contentArea, "maximumHeight"));
mainLayout.setVerticalSpacing(0);
mainLayout.setContentsMargins(0, 0, 0, 0);
int row = 0;
mainLayout.addWidget(&toggleButton, row, 0, 1, 1, Qt::AlignLeft);
mainLayout.addWidget(&headerLine, row++, 2, 1, 1);
mainLayout.addWidget(&contentArea, row, 0, 1, 3);
setLayout(&mainLayout);
QObject::connect(&toggleButton, &QToolButton::clicked, [this](const bool checked) {
if (checked) {
// make the content visible when expanding starts
contentArea.setVisible(true);
}
toggleButton.setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow);
toggleAnimation.setDirection(checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
toggleAnimation.start();
});
connect(&toggleAnimation, &QAbstractAnimation::finished, [this]() {
// hide the content area when it is fully collapsed
if (!toggleButton.isChecked()) {
contentArea.setVisible(false);
}
});
}
void AnimatedExpander::setContentLayout(QLayout *contentLayout)
{
delete contentArea.layout();
contentArea.setLayout(contentLayout);
const auto collapsedHeight = sizeHint().height() - contentArea.maximumHeight();
auto contentHeight = contentLayout->sizeHint().height();
for (int i = 0; i < toggleAnimation.animationCount() - 1; ++i) {
auto expanderAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(i));
expanderAnimation->setDuration(animationDuration);
expanderAnimation->setStartValue(collapsedHeight);
expanderAnimation->setEndValue(collapsedHeight + contentHeight);
}
auto contentAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(toggleAnimation.animationCount() - 1));
contentAnimation->setDuration(animationDuration);
contentAnimation->setStartValue(0);
contentAnimation->setEndValue(contentHeight);
}
class SecKeyFilter: public DefaultKeyFilter
{
public:
SecKeyFilter() : DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setHasSecret(DefaultKeyFilter::Set);
setCanCertify(DefaultKeyFilter::Set);
setIsOpenPGP(DefaultKeyFilter::Set);
}
bool matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const override
{
if (!(availableMatchContexts() & contexts)) {
return false;
}
if (_detail::ByFingerprint<std::equal_to>()(key, mExcludedKey)) {
return false;
}
return DefaultKeyFilter::matches(key, contexts);
}
void setExcludedKey(const GpgME::Key &key)
{
mExcludedKey = key;
}
private:
GpgME::Key mExcludedKey;
};
class UserIDItem : public QStandardItem
{
public:
explicit UserIDItem(const GpgME::UserID &uid)
: mUserId{uid}
{}
GpgME::UserID userId()
{
return mUserId;
}
private:
GpgME::UserID mUserId;
};
class UserIDModel : public QStandardItemModel
{
Q_OBJECT
public:
enum Role {
UserID = Qt::UserRole
};
explicit UserIDModel(QObject *parent = nullptr) : QStandardItemModel(parent) {}
GpgME::Key certificateToCertify() const
{
return m_key;
}
void setKey(const GpgME::Key &key)
{
m_key = key;
clear();
const std::vector<GpgME::UserID> ids = key.userIDs();
int i = 0;
for (const auto &uid: key.userIDs()) {
if (uid.isRevoked() || uid.isInvalid() || Kleo::isRevokedOrExpired(uid)) {
// Skip user IDs that cannot really be certified.
i++;
continue;
}
const auto item = new UserIDItem{uid};
item->setText(Formatting::prettyUserID(uid));
item->setCheckable(true);
item->setEditable(false);
item->setCheckState(Qt::Checked);
appendRow(item);
i++;
}
}
void setCheckedUserIDs(const std::vector<GpgME::UserID> &uids)
{
for (int i = 0, end = rowCount(); i != end; ++i) {
const auto uidItem = userIdItem(i);
const auto itemUserId = uidItem->userId();
const bool userIdIsInList = Kleo::any_of(uids, [itemUserId](const auto &uid) {
return Kleo::userIDsAreEqual(itemUserId, uid);
});
uidItem->setCheckState(userIdIsInList ? Qt::Checked : Qt::Unchecked);
}
}
std::vector<GpgME::UserID> checkedUserIDs() const
{
std::vector<GpgME::UserID> userIds;
userIds.reserve(rowCount());
for (int i = 0; i < rowCount(); ++i) {
const auto uidItem = userIdItem(i);
if (uidItem->checkState() == Qt::Checked) {
userIds.push_back(uidItem->userId());
}
}
qCDebug(KLEOPATRA_LOG) << "Checked user IDs:" << userIds;
return userIds;
}
private:
UserIDItem *userIdItem(int row) const
{
return static_cast<UserIDItem *>(item(row));
}
private:
GpgME::Key m_key;
};
auto checkBoxSize(const QCheckBox *checkBox)
{
QStyleOptionButton opt;
return checkBox->style()->sizeFromContents(QStyle::CT_CheckBox, &opt, QSize(), checkBox);
}
auto createInfoButton(const QString &text, QWidget *parent)
{
auto infoBtn = new QPushButton{parent};
infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
infoBtn->setFlat(true);
QObject::connect(infoBtn, &QPushButton::clicked, infoBtn, [infoBtn, text] () {
const auto pos = infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0);
showToolTip(pos, text, infoBtn);
});
return infoBtn;
}
QString dateFormatWithFourDigitYear(QLocale::FormatType format)
{
// Force the year to be formatted as four digit number, so that
// the user can distinguish between 2006 and 2106.
return QLocale{}.dateFormat(format).
replace(QLatin1String("yy"), QLatin1String("yyyy")).
replace(QLatin1String("yyyyyyyy"), QLatin1String("yyyy"));
}
QString formatDate(const QDate &date, QLocale::FormatType format)
{
return QLocale{}.toString(date, dateFormatWithFourDigitYear(format));
}
class ListView : public QListView
{
Q_OBJECT
public:
using QListView::QListView;
protected:
void focusInEvent(QFocusEvent *event) override
{
QListView::focusInEvent(event);
// queue the invokation, so that it happens after the widget itself got focus
QMetaObject::invokeMethod(this, &ListView::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
}
private:
void forceAccessibleFocusEventForCurrentItem()
{
// force Qt to send a focus event for the current item to accessibility
// tools; otherwise, the user has no idea which item is selected when the
// list gets keyboard input focus
const auto current = currentIndex();
setCurrentIndex({});
setCurrentIndex(current);
}
};
}
class CertifyWidget::Private
{
public:
Private(CertifyWidget *qq)
: q{qq}
{
auto mainLay = new QVBoxLayout{q};
{
auto label = new QLabel{i18n("Verify the fingerprint, mark the user IDs you want to certify, "
"and select the key you want to certify the user IDs with.<br>"
"<i>Note: Only the fingerprint clearly identifies the key and its owner.</i>"), q};
label->setWordWrap(true);
labelHelper.addLabel(label);
mainLay->addWidget(label);
}
mainLay->addWidget(new KSeparator{Qt::Horizontal, q});
{
auto grid = new QGridLayout;
grid->setColumnStretch(1, 1);
int row = -1;
row++;
mFprField = std::make_unique<InfoField>(i18n("Fingerprint:"), q);
grid->addWidget(mFprField->label(), row, 0);
grid->addLayout(mFprField->layout(), row, 1);
row++;
auto label = new QLabel{i18n("Certify with:"), q};
mSecKeySelect = new KeySelectionCombo{/* secretOnly= */true, q};
mSecKeySelect->setKeyFilter(std::make_shared<SecKeyFilter>());
label->setBuddy(mSecKeySelect);
grid->addWidget(label, row, 0);
grid->addWidget(mSecKeySelect);
mainLay->addLayout(grid);
}
mMissingOwnerTrustInfo = new KMessageWidget{q};
mSetOwnerTrustAction = new QAction{q};
mSetOwnerTrustAction->setText(i18nc("@action:button", "Set Owner Trust"));
mSetOwnerTrustAction->setToolTip(i18nc("@info:tooltip",
"Click to set the trust level of the selected certification key to ultimate trust. "
"This is what you usually want to do for your own keys."));
connect(mSetOwnerTrustAction, &QAction::triggered, q, [this] () { setOwnerTrust(); });
mMissingOwnerTrustInfo->addAction(mSetOwnerTrustAction);
mMissingOwnerTrustInfo->setVisible(false);
mainLay->addWidget(mMissingOwnerTrustInfo);
mainLay->addWidget(new KSeparator{Qt::Horizontal, q});
userIdListView = new ListView{q};
userIdListView->setAccessibleName(i18n("User IDs"));
userIdListView->setModel(&mUserIDModel);
mainLay->addWidget(userIdListView, 1);
// Setup the advanced area
auto expander = new AnimatedExpander{i18n("Advanced"), i18n("Show advanced options"), q};
mainLay->addWidget(expander);
auto advLay = new QVBoxLayout;
mExportCB = new QCheckBox{q};
mExportCB->setText(i18n("Certify for everyone to see (exportable)"));
advLay->addWidget(mExportCB);
{
auto layout = new QHBoxLayout;
mPublishCB = new QCheckBox{q};
mPublishCB->setText(i18n("Publish on keyserver afterwards"));
mPublishCB->setEnabled(mExportCB->isChecked());
layout->addSpacing(checkBoxSize(mExportCB).width());
layout->addWidget(mPublishCB);
advLay->addLayout(layout);
}
{
auto tagsLay = new QHBoxLayout;
auto label = new QLabel{i18n("Tags:"), q};
mTagsLE = new QLineEdit{q};
label->setBuddy(mTagsLE);
auto infoBtn = createInfoButton(i18n("You can use this to add additional info to a certification.") +
QStringLiteral("<br/><br/>") +
i18n("Tags created by anyone with full certification trust "
"are shown in the keylist and can be searched."),
q);
infoBtn->setAccessibleName(i18n("Explain tags"));
tagsLay->addWidget(label);
tagsLay->addWidget(mTagsLE, 1);
tagsLay->addWidget(infoBtn);
advLay->addLayout(tagsLay);
}
{
auto layout = new QHBoxLayout;
mExpirationCheckBox = new QCheckBox{q};
mExpirationCheckBox->setText(i18n("Expiration:"));
mExpirationDateEdit = new KDateComboBox{q};
mExpirationDateEdit->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker |
KDateComboBox::DateKeywords | KDateComboBox::WarnOnInvalid);
static const QDate maxAllowedDate{2106, 2, 5};
const QDate today = QDate::currentDate();
mExpirationDateEdit->setDateRange(today.addDays(1), maxAllowedDate,
i18n("The certification must be valid at least until tomorrow."),
i18n("The latest allowed certification date is %1.",
formatDate(maxAllowedDate, QLocale::ShortFormat)));
mExpirationDateEdit->setDateMap({
{today.addYears(2), i18nc("Date for expiration of certification", "Two years from now")},
{today.addYears(1), i18nc("Date for expiration of certification", "One year from now")}
});
mExpirationDateEdit->setDate(today.addYears(2));
mExpirationDateEdit->setEnabled(mExpirationCheckBox->isChecked());
auto infoBtn = createInfoButton(i18n("You can use this to set an expiration date for a certification.") +
QStringLiteral("<br/><br/>") +
i18n("By setting an expiration date, you can limit the validity of "
"your certification to a certain amount of time. Once the expiration "
"date has passed, your certification is no longer valid."),
q);
infoBtn->setAccessibleName(i18n("Explain expiration"));
layout->addWidget(mExpirationCheckBox);
layout->addWidget(mExpirationDateEdit, 1);
layout->addWidget(infoBtn);
advLay->addLayout(layout);
}
{
auto layout = new QHBoxLayout;
mTrustSignatureCB = new QCheckBox{q};
mTrustSignatureCB->setText(i18n("Certify as trusted introducer"));
auto infoBtn = createInfoButton(i18n("You can use this to certify a trusted introducer for a domain.") +
QStringLiteral("<br/><br/>") +
i18n("All certificates with email addresses belonging to the domain "
"that have been certified by the trusted introducer are treated "
"as certified, i.e. a trusted introducer acts as a kind of "
"intermediate CA for a domain."),
q);
infoBtn->setAccessibleName(i18n("Explain trusted introducer"));
layout->addWidget(mTrustSignatureCB, 1);
layout->addWidget(infoBtn);
advLay->addLayout(layout);
}
{
auto layout = new QHBoxLayout;
auto label = new QLabel{i18n("Domain:"), q};
mTrustSignatureDomainLE = new QLineEdit{q};
mTrustSignatureDomainLE->setEnabled(mTrustSignatureCB->isChecked());
label->setBuddy(mTrustSignatureDomainLE);
layout->addSpacing(checkBoxSize(mTrustSignatureCB).width());
layout->addWidget(label);
layout->addWidget(mTrustSignatureDomainLE);
advLay->addLayout(layout);
}
expander->setContentLayout(advLay);
connect(&mUserIDModel, &QStandardItemModel::itemChanged, q, [this](QStandardItem *item) {
onItemChanged(item);
});
connect(mExportCB, &QCheckBox::toggled, [this] (bool on) {
mPublishCB->setEnabled(on);
});
connect(mSecKeySelect, &KeySelectionCombo::currentKeyChanged, [this] (const GpgME::Key &) {
updateTags();
checkOwnerTrust();
Q_EMIT q->changed();
});
connect(mExpirationCheckBox, &QCheckBox::toggled, q, [this] (bool checked) {
mExpirationDateEdit->setEnabled(checked);
Q_EMIT q->changed();
});
connect(mExpirationDateEdit, &KDateComboBox::dateChanged, q, &CertifyWidget::changed);
connect(mTrustSignatureCB, &QCheckBox::toggled, q, [this] (bool on) {
mTrustSignatureDomainLE->setEnabled(on);
Q_EMIT q->changed();
});
connect(mTrustSignatureDomainLE, &QLineEdit::textChanged, q, &CertifyWidget::changed);
loadConfig();
}
~Private() = default;
void loadConfig()
{
const KConfigGroup conf(KSharedConfig::openConfig(), "CertifySettings");
mSecKeySelect->setDefaultKey(conf.readEntry("LastKey", QString()));
mExportCB->setChecked(conf.readEntry("ExportCheckState", false));
mPublishCB->setChecked(conf.readEntry("PublishCheckState", false));
}
void updateTags()
{
if (mTagsLE->isModified()) {
return;
}
GpgME::Key remarkKey = mSecKeySelect->currentKey();
if (!remarkKey.isNull()) {
std::vector<GpgME::UserID> uidsWithRemark;
QString remark;
for (const auto &uid: mTarget.userIDs()) {
GpgME::Error err;
const char *c_remark = uid.remark(remarkKey, err);
if (c_remark) {
const QString candidate = QString::fromUtf8(c_remark);
if (candidate != remark) {
qCDebug(KLEOPATRA_LOG) << "Different remarks on user IDs. Taking last.";
remark = candidate;
uidsWithRemark.clear();
}
uidsWithRemark.push_back(uid);
}
}
// Only select the user IDs with the correct remark
if (!remark.isEmpty()) {
selectUserIDs(uidsWithRemark);
}
mTagsLE->setText(remark);
}
}
void updateTrustSignatureDomain()
{
if (mTrustSignatureDomainLE->text().isEmpty() && mTarget.numUserIDs() == 1) {
// try to guess the domain to use for the trust signature
const auto address = mTarget.userID(0).addrSpec();
const auto atPos = address.find('@');
if (atPos != std::string::npos) {
const auto domain = address.substr(atPos + 1);
mTrustSignatureDomainLE->setText(QString::fromUtf8(domain.c_str(), domain.size()));
}
}
}
void setTarget(const GpgME::Key &key)
{
mFprField->setValue(QStringLiteral("<b>") + Formatting::prettyID(key.primaryFingerprint()) + QStringLiteral("</b>"),
Formatting::accessibleHexID(key.primaryFingerprint()));
mUserIDModel.setKey(key);
mTarget = key;
auto keyFilter = std::make_shared<SecKeyFilter>();
keyFilter->setExcludedKey(mTarget);
mSecKeySelect->setKeyFilter(keyFilter);
updateTags();
updateTrustSignatureDomain();
}
GpgME::Key secKey() const
{
return mSecKeySelect->currentKey();
}
void selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
mUserIDModel.setCheckedUserIDs(uids);
}
std::vector<GpgME::UserID> selectedUserIDs() const
{
return mUserIDModel.checkedUserIDs();
}
bool exportableSelected() const
{
return mExportCB->isChecked();
}
bool publishSelected() const
{
return mPublishCB->isChecked();
}
QString tags() const
{
return mTagsLE->text().trimmed();
}
GpgME::Key target() const
{
return mTarget;
}
bool isValid() const
{
static const QRegularExpression domainNameRegExp{QStringLiteral(R"(^\s*((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\s*$)"),
QRegularExpression::CaseInsensitiveOption};
// do not accept null keys
if (mTarget.isNull() || mSecKeySelect->currentKey().isNull()) {
return false;
}
// do not accept empty list of user IDs
if (selectedUserIDs().empty()) {
return false;
}
// do not accept if the key to certify is selected as certification key;
// this shouldn't happen because the key to certify is excluded from the choice, but better safe than sorry
if (_detail::ByFingerprint<std::equal_to>()(mTarget, mSecKeySelect->currentKey())) {
return false;
}
if (mExpirationCheckBox->isChecked() && !mExpirationDateEdit->isValid()) {
return false;
}
if (mTrustSignatureCB->isChecked() && !domainNameRegExp.match(mTrustSignatureDomainLE->text()).hasMatch()) {
return false;
}
return true;
}
void checkOwnerTrust()
{
const auto secretKey = secKey();
if (secretKey.ownerTrust() != GpgME::Key::Ultimate) {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Information);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("question")));
mMissingOwnerTrustInfo->setText(i18n("Is this your own key?"));
mSetOwnerTrustAction->setEnabled(true);
mMissingOwnerTrustInfo->animatedShow();
} else {
mMissingOwnerTrustInfo->animatedHide();
}
}
void setOwnerTrust()
{
mSetOwnerTrustAction->setEnabled(false);
QGpgME::ChangeOwnerTrustJob *const j = QGpgME::openpgp()->changeOwnerTrustJob();
connect(j, &QGpgME::ChangeOwnerTrustJob::result, q, [this] (const GpgME::Error &err) {
if (err) {
KMessageBox::error(q,
i18n("<p>Changing the certification trust of the key <b>%1</b> failed:</p><p>%2</p>",
Formatting::formatForComboBox(secKey()),
- QString::fromLocal8Bit(err.asString())),
+ Formatting::errorAsString(err)),
i18n("Certification Trust Change Failed"));
}
if (err || err.isCanceled()) {
mSetOwnerTrustAction->setEnabled(true);
} else {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Positive);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("checkmark")));
mMissingOwnerTrustInfo->setText(i18n("Owner trust set successfully."));
}
});
j->start(secKey(), GpgME::Key::Ultimate);
}
void onItemChanged(QStandardItem *item)
{
Q_EMIT q->changed();
#ifndef QT_NO_ACCESSIBILITY
if (item) {
// assume that the checked state changed
QAccessible::State st;
st.checked = true;
QAccessibleStateChangeEvent e(userIdListView, st);
e.setChild(item->index().row());
QAccessible::updateAccessibility(&e);
}
#endif
}
public:
CertifyWidget *const q;
std::unique_ptr<InfoField> mFprField;
KeySelectionCombo *mSecKeySelect = nullptr;
KMessageWidget *mMissingOwnerTrustInfo = nullptr;
ListView *userIdListView = nullptr;
QCheckBox *mExportCB = nullptr;
QCheckBox *mPublishCB = nullptr;
QLineEdit *mTagsLE = nullptr;
QCheckBox *mTrustSignatureCB = nullptr;
QLineEdit *mTrustSignatureDomainLE = nullptr;
QCheckBox *mExpirationCheckBox = nullptr;
KDateComboBox *mExpirationDateEdit = nullptr;
QAction *mSetOwnerTrustAction = nullptr;
LabelHelper labelHelper;
UserIDModel mUserIDModel;
GpgME::Key mTarget;
};
CertifyWidget::CertifyWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
Kleo::CertifyWidget::~CertifyWidget() = default;
void CertifyWidget::setTarget(const GpgME::Key &key)
{
d->setTarget(key);
}
GpgME::Key CertifyWidget::target() const
{
return d->target();
}
void CertifyWidget::selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
d->selectUserIDs(uids);
}
std::vector<GpgME::UserID> CertifyWidget::selectedUserIDs() const
{
return d->selectedUserIDs();
}
GpgME::Key CertifyWidget::secKey() const
{
return d->secKey();
}
bool CertifyWidget::exportableSelected() const
{
return d->exportableSelected();
}
QString CertifyWidget::tags() const
{
return d->tags();
}
bool CertifyWidget::publishSelected() const
{
return d->publishSelected();
}
bool CertifyWidget::trustSignatureSelected() const
{
return d->mTrustSignatureCB->isChecked();
}
QString CertifyWidget::trustSignatureDomain() const
{
return d->mTrustSignatureDomainLE->text().trimmed();
}
QDate CertifyWidget::expirationDate() const
{
return d->mExpirationCheckBox->isChecked() ? d->mExpirationDateEdit->date() : QDate{};
}
bool CertifyWidget::isValid() const
{
return d->isValid();
}
// For UserID model
#include "certifywidget.moc"
diff --git a/src/dialogs/exportdialog.cpp b/src/dialogs/exportdialog.cpp
index 62cdcb6e0..e164e3f11 100644
--- a/src/dialogs/exportdialog.cpp
+++ b/src/dialogs/exportdialog.cpp
@@ -1,218 +1,218 @@
/* SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "exportdialog.h"
#include "kleopatra_debug.h"
#include "view/waitwidget.h"
#include <QDialogButtonBox>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#include <gpgme++/key.h>
#include <QGpgME/Protocol>
#include <QGpgME/ExportJob>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KConfigGroup>
#include <Libkleo/Formatting>
using namespace Kleo;
class ExportWidget::Private
{
public:
Private(ExportWidget *qq)
: q(qq)
{}
void setupUi();
GpgME::Key key;
GpgME::Subkey subkey;
QTextEdit *textEdit;
WaitWidget *waitWidget;
unsigned int flags;
private:
ExportWidget *const q;
};
void ExportWidget::Private::setupUi()
{
auto vlay = new QVBoxLayout(q);
vlay->setContentsMargins(0, 0, 0, 0);
textEdit = new QTextEdit;
textEdit->setVisible(false);
textEdit->setReadOnly(true);
auto fixedFont = QFont(QStringLiteral("Monospace"));
fixedFont.setStyleHint(QFont::TypeWriter);
textEdit->setFont(fixedFont);
textEdit->setReadOnly(true);
vlay->addWidget(textEdit);
waitWidget = new WaitWidget;
waitWidget->setText(i18n("Exporting ..."));
vlay->addWidget(waitWidget);
}
ExportWidget::ExportWidget(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
d->setupUi();
}
ExportWidget::~ExportWidget()
{
}
static QString injectComments(const GpgME::Key &key, const QByteArray &data)
{
QString ret = QString::fromUtf8(data);
if (key.protocol() != GpgME::OpenPGP) {
return ret;
}
auto overView = Formatting::toolTip(key, Formatting::Fingerprint |
Formatting::UserIDs |
Formatting::Issuer |
Formatting::Subject |
Formatting::ExpiryDates |
Formatting::CertificateType |
Formatting::CertificateUsage);
// Fixup the HTML coming from the toolTip for our own format.
overView.remove(QLatin1String("<tr><th>"));
overView.replace(QLatin1String("</th><td>"), QLatin1String("\t"));
overView.replace(QLatin1String("</td></tr>"), QLatin1String("\n"));
overView.remove(QLatin1String("<table border=\"0\">"));
overView.remove(QLatin1String("\n</table>"));
overView.replace(QLatin1String("&lt;"), QLatin1String("<"));
overView.replace(QLatin1String("&gt;"), QLatin1String(">"));
auto overViewLines = overView.split(QLatin1Char('\n'));
// Format comments so that they fit for RFC 4880
auto comments = QStringLiteral("Comment: ");
comments += overViewLines.join(QLatin1String("\nComment: ")) + QLatin1Char('\n');
ret.insert(37 /* -----BEGIN PGP PUBLIC KEY BLOCK-----\n */, comments);
return ret;
}
void ExportWidget::exportResult(const GpgME::Error &err, const QByteArray &data)
{
d->waitWidget->setVisible(false);
d->textEdit->setVisible(true);
if (err) {
/* Should not happen. But well,.. */
- d->textEdit->setText(i18nc("%1 is error message", "Failed to export: '%1'", QString::fromLatin1(err.asString())));
+ d->textEdit->setText(i18nc("%1 is error message", "Failed to export: '%1'", Formatting::errorAsString(err)));
}
if (!d->flags) {
d->textEdit->setText(injectComments(d->key, data));
} else {
d->textEdit->setText(QString::fromUtf8(data));
}
}
void ExportWidget::setKey(const GpgME::Subkey &key, unsigned int flags)
{
d->waitWidget->setVisible(true);
d->textEdit->setVisible(false);
d->key = key.parent();
d->subkey = key;
d->flags = flags;
auto protocol = d->key.protocol() == GpgME::CMS ?
QGpgME::smime() : QGpgME::openpgp();
auto job = protocol->publicKeyExportJob(true);
connect(job, &QGpgME::ExportJob::result,
this, &ExportWidget::exportResult);
job->setExportFlags(flags);
job->start(QStringList() << QLatin1String(key.fingerprint()) + QLatin1Char('!'));
}
void ExportWidget::setKey(const GpgME::Key &key, unsigned int flags)
{
d->waitWidget->setVisible(true);
d->textEdit->setVisible(false);
d->key = key;
d->flags = flags;
auto protocol = key.protocol() == GpgME::CMS ?
QGpgME::smime() : QGpgME::openpgp();
auto job = protocol->publicKeyExportJob(true);
connect(job, &QGpgME::ExportJob::result,
this, &ExportWidget::exportResult);
job->setExportFlags(flags);
job->start(QStringList() << QLatin1String(key.primaryFingerprint()));
}
GpgME::Key ExportWidget::key() const
{
return d->key;
}
ExportDialog::ExportDialog(QWidget *parent)
: QDialog(parent),
mWidget(new ExportWidget(this))
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "ExportDialog");
const auto size = dialog.readEntry("Size", QSize(600, 800));
if (size.isValid()) {
resize(size);
}
setWindowTitle(i18nc("@title:window", "Export"));
auto l = new QVBoxLayout(this);
l->addWidget(mWidget);
auto bbox = new QDialogButtonBox(this);
auto btn = bbox->addButton(QDialogButtonBox::Close);
connect(btn, &QPushButton::pressed, this, &QDialog::accept);
l->addWidget(bbox);
}
ExportDialog::~ExportDialog()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "ExportDialog");
dialog.writeEntry("Size", size());
dialog.sync();
}
void ExportDialog::setKey(const GpgME::Key &key, unsigned int flags)
{
mWidget->setKey(key, flags);
}
void ExportDialog::setKey(const GpgME::Subkey &key, unsigned int flags)
{
mWidget->setKey(key, flags);
}
GpgME::Key ExportDialog::key() const
{
return mWidget->key();
}
diff --git a/src/dialogs/setinitialpindialog.cpp b/src/dialogs/setinitialpindialog.cpp
index b9112e768..42d2d3c30 100644
--- a/src/dialogs/setinitialpindialog.cpp
+++ b/src/dialogs/setinitialpindialog.cpp
@@ -1,200 +1,202 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/setinitialpindialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "setinitialpindialog.h"
#include "ui_setinitialpindialog.h"
+#include <Libkleo/Formatting>
+
#include <KIconLoader>
#include <KLocalizedString>
-#include <QIcon>
+#include <QIcon>
#include <QTextDocument> // for Qt::escape
#include <gpgme++/error.h>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
enum State {
Unknown = 0,
NotSet,
AlreadySet,
Ongoing,
Ok,
Failed,
NumStates
};
const char *icons[] = {
// PENDING(marc) use better icons, once available
"", // Unknown
"", // NotSet
"security-medium", // AlreadySet
"movie-process-working-kde", // Ongoing
"security-high", // Ok
"security-low", // Failed
};
static_assert(sizeof icons / sizeof(*icons) == NumStates, "");
static_assert(sizeof("movie-") == 7, "");
static void update_widget(State state, bool delay, QLabel *resultLB, QLabel *lb, QPushButton *pb, QLabel *statusLB)
{
Q_ASSERT(state >= 0); Q_ASSERT(state < NumStates);
const char *icon = icons[state];
if (qstrncmp(icon, "movie-", sizeof("movie-") - 1) == 0) {
resultLB->setMovie(KIconLoader::global()->loadMovie(QLatin1String(icon + sizeof("movie-")), KIconLoader::NoGroup));
} else if (icon && *icon) {
resultLB->setPixmap(QIcon::fromTheme(QLatin1String(icon)).pixmap(32));
} else {
resultLB->setPixmap(QPixmap());
}
lb->setEnabled((state == NotSet || state == Failed) && !delay);
pb->setEnabled((state == NotSet || state == Failed) && !delay);
if (state == AlreadySet) {
statusLB->setText(xi18nc("@info", "No NullPin found. <warning>If this PIN was not set by you personally, the card might have been tampered with.</warning>"));
}
}
static QString format_error(const Error &err)
{
if (err.isCanceled()) {
return i18nc("@info", "Canceled setting PIN.");
}
if (err)
return xi18nc("@info",
"There was an error setting the PIN: <message>%1</message>.",
- QString::fromLocal8Bit(err.asString()).toHtmlEscaped());
+ Formatting::errorAsString(err).toHtmlEscaped());
else {
return i18nc("@info", "PIN set successfully.");
}
}
class SetInitialPinDialog::Private
{
friend class ::Kleo::Dialogs::SetInitialPinDialog;
SetInitialPinDialog *const q;
public:
explicit Private(SetInitialPinDialog *qq)
: q(qq),
nksState(Unknown),
sigGState(Unknown),
ui(q)
{
}
private:
void slotNksButtonClicked()
{
nksState = Ongoing;
ui.nksStatusLB->clear();
updateWidgets();
Q_EMIT q->nksPinRequested();
}
void slotSigGButtonClicked()
{
sigGState = Ongoing;
ui.sigGStatusLB->clear();
updateWidgets();
Q_EMIT q->sigGPinRequested();
}
private:
void updateWidgets()
{
update_widget(nksState, false,
ui.nksResultIcon, ui.nksLB, ui.nksPB, ui.nksStatusLB);
update_widget(sigGState, nksState == NotSet || nksState == Failed || nksState == Ongoing,
ui.sigGResultIcon, ui.sigGLB, ui.sigGPB, ui.sigGStatusLB);
ui.closePB()->setEnabled(q->isComplete());
ui.cancelPB()->setEnabled(!q->isComplete());
}
private:
State nksState, sigGState;
struct UI : public Ui::SetInitialPinDialog {
explicit UI(Dialogs::SetInitialPinDialog *qq)
: Ui::SetInitialPinDialog()
{
setupUi(qq);
closePB()->setEnabled(false);
connect(closePB(), &QAbstractButton::clicked, qq, &QDialog::accept);
}
QAbstractButton *closePB() const
{
Q_ASSERT(dialogButtonBox);
return dialogButtonBox->button(QDialogButtonBox::Close);
}
QAbstractButton *cancelPB() const
{
Q_ASSERT(dialogButtonBox);
return dialogButtonBox->button(QDialogButtonBox::Cancel);
}
} ui;
};
SetInitialPinDialog::SetInitialPinDialog(QWidget *p)
: QDialog(p), d(new Private(this))
{
}
SetInitialPinDialog::~SetInitialPinDialog() {}
void SetInitialPinDialog::setNksPinPresent(bool on)
{
d->nksState = on ? AlreadySet : NotSet;
d->updateWidgets();
}
void SetInitialPinDialog::setSigGPinPresent(bool on)
{
d->sigGState = on ? AlreadySet : NotSet;
d->updateWidgets();
}
void SetInitialPinDialog::setNksPinSettingResult(const Error &err)
{
d->ui.nksStatusLB->setText(format_error(err));
d->nksState =
err.isCanceled() ? NotSet :
err ? Failed :
Ok;
d->updateWidgets();
}
void SetInitialPinDialog::setSigGPinSettingResult(const Error &err)
{
d->ui.sigGStatusLB->setText(format_error(err));
d->sigGState =
err.isCanceled() ? NotSet :
err ? Failed :
Ok;
d->updateWidgets();
}
bool SetInitialPinDialog::isComplete() const
{
return (d->nksState == Ok || d->nksState == AlreadySet);
}
#include "moc_setinitialpindialog.cpp"
diff --git a/src/dialogs/updatenotification.cpp b/src/dialogs/updatenotification.cpp
index dfc451330..0f99d639c 100644
--- a/src/dialogs/updatenotification.cpp
+++ b/src/dialogs/updatenotification.cpp
@@ -1,228 +1,228 @@
/* dialogs/updatenotification.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "updatenotification.h"
#include <view/htmllabel.h>
#include <Libkleo/Compat>
#include <Libkleo/GnuPG>
#include "kleopatra_debug.h"
#include <QIcon>
#include <QGridLayout>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QDesktopServices>
#include <QProcess>
#include <QProgressDialog>
#include <QPushButton>
#include <QLabel>
#include <QUrl>
#include <QDateTime>
#include <KIconLoader>
#include <KLocalizedString>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KMessageBox>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <gpgme++/gpgmefw.h>
#include <gpgme++/swdbresult.h>
#include <gpgme++/error.h>
using namespace Kleo;
namespace
{
static void gpgconf_set_update_check(bool value)
{
auto conf = QGpgME::cryptoConfig();
auto entry = getCryptoConfigEntry(conf, "dirmngr", "allow-version-check");
if (!entry) {
qCDebug(KLEOPATRA_LOG) << "allow-version-check entry not found";
return;
}
if (entry->boolValue() != value) {
entry->setBoolValue(value);
conf->sync(true);
}
}
} // namespace
void UpdateNotification::forceUpdateCheck(QWidget *parent)
{
auto proc = new QProcess;
proc->setProgram(gnupgInstallPath() + QStringLiteral("/gpg-connect-agent.exe"));
proc->setArguments({
QStringLiteral("--dirmngr"),
QStringLiteral("loadswdb --force"),
QStringLiteral("/bye"),
});
auto progress = new QProgressDialog(i18n("Searching for updates..."),
i18n("Cancel"), 0, 0, parent);
progress->setMinimumDuration(0);
progress->show();
connect(progress, &QProgressDialog::canceled, [ proc] () {
proc->kill();
qCDebug(KLEOPATRA_LOG) << "Update force canceled. Output:"
<< QString::fromLocal8Bit(proc->readAllStandardOutput())
<< "stderr:"
<< QString::fromLocal8Bit(proc->readAllStandardError());
});
#if QT_DEPRECATED_SINCE(5, 13)
connect(proc, qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
#else
connect(proc, &QProcess::finished,
#endif
[parent, progress, proc](int exitCode, QProcess::ExitStatus exitStatus) {
qCDebug(KLEOPATRA_LOG) << "Update force exited with status:" << exitStatus
<< "code:" << exitCode;
delete progress;
proc->deleteLater();
UpdateNotification::checkUpdate(parent, exitStatus == QProcess::NormalExit);
});
qCDebug(KLEOPATRA_LOG) << "Starting:" << proc->program() << "args" << proc->arguments();
proc->start();
}
void UpdateNotification::checkUpdate(QWidget *parent, bool force)
{
#ifdef Q_OS_WIN
KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification");
if (updatecfg.readEntry("NeverShow", false) && !force) {
return;
}
// Gpg defaults to no update check. For Gpg4win we want this
// enabled if the user does not explicitly disable update
// checks neverShow would be true in that case or
// we would have set AllowVersionCheck once and the user
// explicitly removed that.
if (force || updatecfg.readEntry("AllowVersionCheckSetOnce", false)) {
gpgconf_set_update_check (true);
updatecfg.writeEntry("AllowVersionCheckSetOnce", true);
}
const auto current = gpg4winVersionNumber();
GpgME::Error err;
const auto lastshown = updatecfg.readEntry("LastShown", QDateTime());
if (!force && lastshown.isValid() &&
lastshown.addSecs(20 * 60 * 60) > QDateTime::currentDateTime()) {
qDebug() << QDateTime::currentDateTime().addSecs(20 * 60 * 60);
return;
}
const auto results = GpgME::SwdbResult::query("gpg4win",
current.toUtf8().constData(),
&err);
if (err) {
- qCDebug(KLEOPATRA_LOG) << "update check failed: " << err.asString();
+ qCDebug(KLEOPATRA_LOG) << "update check failed: " << Formatting::errorAsString(err);
return;
}
if (results.size() != 1) {
/* Should not happen */
qCDebug(KLEOPATRA_LOG) << "more then one result";
return;
}
const auto result = results[0];
if (result.update()) {
const QString newVersion = QStringLiteral("%1.%2.%3").arg(result.version().major)
.arg(result.version().minor)
.arg(result.version().patch);
qCDebug(KLEOPATRA_LOG) << "Have update to version:" << newVersion;
UpdateNotification notifier(parent, newVersion);
notifier.exec();
updatecfg.writeEntry("LastShown", QDateTime::currentDateTime());
updatecfg.sync();
} else {
qCDebug(KLEOPATRA_LOG) << "No update for:" << current;
if (force) {
KMessageBox::information(parent,
i18nc("@info",
"No update found in the available version database."),
i18nc("@title", "Up to date"));
}
}
#else
Q_UNUSED(parent)
Q_UNUSED(force)
#endif
}
UpdateNotification::UpdateNotification(QWidget *parent, const QString &version) :
QDialog(parent)
{
resize(400, 200);
auto lay = new QGridLayout(this);
auto logo = new QLabel;
logo->setMaximumWidth(110);
setAttribute(Qt::WA_QuitOnClose, false);
KIconLoader *const il = KIconLoader::global();
const QString iconPath = il->iconPath(QStringLiteral("gpg4win"),
KIconLoader::User);
logo->setPixmap(QIcon(iconPath).pixmap(100, 100));
auto label = new HtmlLabel;
const QString boldVersion = QStringLiteral("<b>%1</b>").arg(version);
label->setHtml(i18nc("%1 is the version number", "Version %1 is available.", boldVersion) +
QStringLiteral("<br><br>") +
i18nc("Link to NEWS style changelog",
"See the <a href=\"https://www.gpg4win.org/change-history.html\">new features</a>."));
label->setOpenExternalLinks(true);
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
label->setWordWrap(true);
setWindowTitle(i18nc("@title:window", "Update Available"));
setWindowIcon(QIcon(QLatin1String("gpg4win")));
lay->addWidget(logo, 0, 0);
lay->addWidget(label, 0, 1);
const auto chk = new QCheckBox (i18n("Show this notification for future updates."));
lay->addWidget(chk, 1, 0, 1, -1);
KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification");
chk->setChecked(!updatecfg.readEntry("NeverShow", false));
const auto bb = new QDialogButtonBox();
const auto b = bb->addButton(i18n("&Get update"), QDialogButtonBox::AcceptRole);
b->setDefault(true);
b->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
bb->addButton(QDialogButtonBox::Cancel);
lay->addWidget(bb, 2, 0, 1, -1);
connect (bb, &QDialogButtonBox::accepted, this, [this, chk]() {
QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.gpg4win.org/download.html")));
KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification");
updatecfg.writeEntry("NeverShow", !chk->isChecked());
gpgconf_set_update_check (chk->isChecked());
QDialog::accept();
});
connect (bb, &QDialogButtonBox::rejected, this, [this, chk]() {
KConfigGroup updatecfg(KSharedConfig::openConfig(), "UpdateNotification");
updatecfg.writeEntry("NeverShow", !chk->isChecked());
gpgconf_set_update_check (chk->isChecked());
QDialog::reject();
});
}
diff --git a/src/dialogs/weboftrustwidget.cpp b/src/dialogs/weboftrustwidget.cpp
index 6ed78c243..fee9374f8 100644
--- a/src/dialogs/weboftrustwidget.cpp
+++ b/src/dialogs/weboftrustwidget.cpp
@@ -1,347 +1,348 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "weboftrustwidget.h"
#include "commands/certifycertificatecommand.h"
#include "commands/revokecertificationcommand.h"
#include "utils/keys.h"
#include "utils/tags.h"
+#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/NavigatableTreeView>
#include <Libkleo/UserIDListModel>
#include <KLocalizedString>
#include <KMessageBox>
#include <QHeaderView>
#include <QMenu>
#include <QVBoxLayout>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include "kleopatra_debug.h"
using namespace Kleo;
class WebOfTrustWidget::Private
{
friend class ::Kleo::WebOfTrustWidget;
WebOfTrustWidget *const q;
private:
GpgME::Key key;
UserIDListModel certificationsModel;
QGpgME::KeyListJob *keyListJob = nullptr;
NavigatableTreeView *certificationsTV = nullptr;
QAction *detailsAction = nullptr;
QAction *certifyAction = nullptr;
QAction *revokeAction = nullptr;
public:
Private(WebOfTrustWidget *qq)
: q{qq}
{
certificationsModel.enableRemarks(Tags::tagsEnabled());
certificationsTV = new NavigatableTreeView{q};
certificationsTV->setAccessibleName(i18n("User IDs and certifications"));
certificationsTV->setModel(&certificationsModel);
certificationsTV->setAllColumnsShowFocus(false);
certificationsTV->setSelectionMode(QAbstractItemView::SingleSelection);
if (!Tags::tagsEnabled()) {
certificationsTV->hideColumn(static_cast<int>(UserIDListModel::Column::Tags));
}
auto vLay = new QVBoxLayout(q);
vLay->setContentsMargins(0, 0, 0, 0);
vLay->addWidget(certificationsTV);
detailsAction = new QAction{QIcon::fromTheme(QStringLiteral("dialog-information")), i18nc("@action", "Show Certificate Details"), q};
connect(detailsAction, &QAction::triggered, q, [this]() {
showCertificateDetails();
});
certifyAction = new QAction{QIcon::fromTheme(QStringLiteral("view-certificate-sign")), i18nc("@action", "Add Certification"), q};
connect(certifyAction, &QAction::triggered, q, [this]() {
addCertification();
});
if (Kleo::Commands::RevokeCertificationCommand::isSupported()) {
revokeAction = new QAction{QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action", "Revoke Certification"), q};
connect(revokeAction, &QAction::triggered, q, [this]() {
revokeCertification();
});
}
connect(certificationsTV, &QAbstractItemView::doubleClicked,
q, [this]() {
certificationDblClicked();
});
certificationsTV->setContextMenuPolicy(Qt::CustomContextMenu);
connect(certificationsTV, &QWidget::customContextMenuRequested,
q, [this] (const QPoint &p) {
contextMenuRequested(p);
});
connect(certificationsTV->selectionModel(), &QItemSelectionModel::currentRowChanged, q, [this]() {
updateActions();
});
updateActions();
}
GpgME::UserID selectedUserID()
{
return certificationsModel.userID(certificationsTV->currentIndex());
}
GpgME::UserID::Signature selectedCertification()
{
return certificationsModel.signature(certificationsTV->currentIndex());
}
void certificationDblClicked()
{
showCertificateDetails();
}
void showCertificateDetails()
{
const auto signature = selectedCertification();
if (signature.isNull()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "- no certification selected";
return;
}
auto cmd = Command::commandForQuery(QString::fromUtf8(signature.signerKeyID()));
cmd->setParentWId(q->winId());
cmd->start();
}
void addCertification()
{
auto userID = selectedUserID();
if (userID.isNull()) {
userID = selectedCertification().parent();
}
if (userID.isNull()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "- no user ID or certification selected";
return;
}
auto cmd = new Kleo::Commands::CertifyCertificateCommand(userID);
cmd->setParentWidget(q);
certificationsTV->setEnabled(false);
connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished,
q, [this]() {
certificationsTV->setEnabled(true);
// Trigger an update when done
q->setKey(key);
});
cmd->start();
}
void revokeCertification()
{
Command *cmd = nullptr;
if (const auto signature = selectedCertification(); !signature.isNull()) {
cmd = new Kleo::Commands::RevokeCertificationCommand(signature);
} else if (const auto userID = selectedUserID(); !userID.isNull()) {
cmd = new Kleo::Commands::RevokeCertificationCommand(userID);
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "- no user ID or certification selected";
return;
}
cmd->setParentWidget(q);
certificationsTV->setEnabled(false);
connect(cmd, &Kleo::Commands::RevokeCertificationCommand::finished,
q, [this]() {
certificationsTV->setEnabled(true);
// Trigger an update when done
q->setKey(key);
});
cmd->start();
}
void addActionsForUserID(QMenu *menu)
{
menu->addAction(certifyAction);
if (revokeAction) {
menu->addAction(revokeAction);
}
}
void addActionsForSignature(QMenu *menu)
{
menu->addAction(detailsAction);
menu->addAction(certifyAction);
if (revokeAction) {
menu->addAction(revokeAction);
if (!revokeAction->isEnabled()) {
menu->setToolTipsVisible(true);
}
}
}
void updateActions() {
const auto userCanSignUserIDs = userHasCertificationKey();
const auto keyCanBeCertified = Kleo::canBeCertified(key);
const auto userID = selectedUserID();
const auto signature = selectedCertification();
detailsAction->setEnabled(!signature.isNull());
certifyAction->setEnabled(keyCanBeCertified && userCanSignUserIDs && (!userID.isNull() || !signature.isNull()));
if (revokeAction) {
revokeAction->setToolTip({});
if (!signature.isNull()) {
const auto revocationFeasibility = userCanRevokeCertification(signature);
revokeAction->setEnabled(revocationFeasibility == CertificationCanBeRevoked);
switch (revocationFeasibility) {
case CertificationCanBeRevoked:
break;
case CertificationNotMadeWithOwnKey:
revokeAction->setToolTip(i18n("You cannot revoke this certification because it wasn't made with one of your keys (or the required secret key is missing)."));
break;
case CertificationIsSelfSignature:
revokeAction->setToolTip(i18n("Revocation of self-certifications is currently not possible."));
break;
case CertificationIsRevocation:
revokeAction->setToolTip(i18n("You cannot revoke this revocation certification. (But you can re-certify the corresponding user ID.)"));
break;
case CertificationIsExpired:
revokeAction->setToolTip(i18n("You cannot revoke this expired certification."));
break;
case CertificationIsInvalid:
revokeAction->setToolTip(i18n("You cannot revoke this invalid certification."));
break;
case CertificationKeyNotAvailable:
revokeAction->setToolTip(i18n("You cannot revoke this certification because the required secret key is not available."));
break;
};
} else if (!userID.isNull()) {
const bool canRevokeCertification = userCanRevokeCertifications(userID);
revokeAction->setEnabled(canRevokeCertification);
if (!canRevokeCertification) {
revokeAction->setToolTip(i18n("You cannot revoke any of the certifications of this user ID. Select any of the certifications for details."));
}
} else {
revokeAction->setEnabled(false);
}
}
}
void contextMenuRequested(const QPoint &p)
{
const auto index = certificationsTV->indexAt(p);
const auto userID = certificationsModel.userID(index);
const auto signature = certificationsModel.signature(index);
if (userID.isNull() && signature.isNull()) {
return;
}
auto menu = new QMenu(q);
if (!userID.isNull()) {
addActionsForUserID(menu);
}
else if (!signature.isNull()) {
addActionsForSignature(menu);
}
connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
menu->popup(certificationsTV->viewport()->mapToGlobal(p));
}
void startSignatureListing()
{
if (keyListJob) {
return;
}
QGpgME::KeyListJob *const job = QGpgME::openpgp()->keyListJob(/*remote*/false, /*includeSigs*/true, /*validate*/true);
if (!job) {
return;
}
if (Tags::tagsEnabled()) {
job->addMode(GpgME::SignatureNotations);
}
connect(job, &QGpgME::KeyListJob::result,
q, &WebOfTrustWidget::signatureListingDone);
connect(job, &QGpgME::KeyListJob::nextKey,
q, &WebOfTrustWidget::signatureListingNextKey);
job->start(QStringList(QString::fromLatin1(key.primaryFingerprint())));
keyListJob = job;
}
};
WebOfTrustWidget::WebOfTrustWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
WebOfTrustWidget::~WebOfTrustWidget() = default;
QAction *WebOfTrustWidget::detailsAction() const
{
return d->detailsAction;
}
QAction *WebOfTrustWidget::certifyAction() const
{
return d->certifyAction;
}
QAction *WebOfTrustWidget::revokeAction() const
{
return d->revokeAction;
}
GpgME::Key WebOfTrustWidget::key() const
{
return d->key;
}
void WebOfTrustWidget::setKey(const GpgME::Key &key)
{
if (key.protocol() != GpgME::OpenPGP) {
qCDebug(KLEOPATRA_LOG) << "List of Certifications is only supported for OpenPGP keys";
return;
}
d->key = key;
d->certificationsModel.setKey(key);
d->updateActions();
d->certificationsTV->expandAll();
d->certificationsTV->header()->resizeSections(QHeaderView::ResizeToContents);
d->startSignatureListing();
}
void WebOfTrustWidget::signatureListingNextKey(const GpgME::Key &key)
{
GpgME::Key merged = key;
merged.mergeWith(d->key);
setKey(merged);
}
void WebOfTrustWidget::signatureListingDone(const GpgME::KeyListResult &result)
{
if (result.error()) {
KMessageBox::information(this, xi18nc("@info",
"<para>An error occurred while loading the certifications: "
"<message>%1</message></para>",
- QString::fromLocal8Bit(result.error().asString())),
+ Formatting::errorAsString(result.error())),
i18nc("@title", "Certifications Loading Failed"));
}
d->keyListJob = nullptr;
}
diff --git a/src/newcertificatewizard/keycreationpage.cpp b/src/newcertificatewizard/keycreationpage.cpp
index 81fe38bb0..e39e5ec62 100644
--- a/src/newcertificatewizard/keycreationpage.cpp
+++ b/src/newcertificatewizard/keycreationpage.cpp
@@ -1,253 +1,253 @@
/* -*- mode: c++; c-basic-offset:4 -*-
newcertificatewizard/keycreationpage.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "keycreationpage_p.h"
#include "keyalgo_p.h"
#include "kleopatraapplication.h"
#include "utils/keyparameters.h"
#include "utils/keyusage.h"
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/KeyGenerationJob>
#include <QGpgME/Protocol>
#include <QLabel>
#include <QUrl>
#include <QVBoxLayout>
#include <gpgme++/context.h>
#include <gpgme++/keygenerationresult.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::NewCertificateUi;
using namespace GpgME;
struct KeyCreationPage::UI
{
UI(QWizardPage *parent)
{
parent->setTitle(i18nc("@title", "Creating Key Pair..."));
auto mainLayout = new QVBoxLayout{parent};
auto label = new QLabel{i18n("The process of creating a key requires large amounts of random numbers. This may require several minutes..."), parent};
label->setWordWrap(true);
mainLayout->addWidget(label);
}
};
KeyCreationPage::KeyCreationPage(QWidget *p)
: WizardPage{p}
, ui{new UI{this}}
{
setObjectName(QString::fromUtf8("Kleo__NewCertificateUi__KeyCreationPage"));
}
KeyCreationPage::~KeyCreationPage() = default;
bool KeyCreationPage::isComplete() const
{
return !job;
}
void KeyCreationPage::initializePage()
{
startJob();
}
void KeyCreationPage::startJob()
{
const auto proto = pgp() ? QGpgME::openpgp() : QGpgME::smime();
if (!proto) {
return;
}
QGpgME::KeyGenerationJob *const j = proto->keyGenerationJob();
if (!j) {
return;
}
if (!protectedKey() && pgp()) {
auto ctx = QGpgME::Job::context(j);
ctx->setPassphraseProvider(&mEmptyPassphraseProvider);
ctx->setPinentryMode(Context::PinentryLoopback);
}
connect(j, &QGpgME::KeyGenerationJob::result,
this, &KeyCreationPage::slotResult);
if (const Error err = j->start(createGnupgKeyParms()))
setField(QStringLiteral("error"), i18n("Could not start key pair creation: %1",
- QString::fromLocal8Bit(err.asString())));
+ Formatting::errorAsString(err)));
else {
job = j;
}
}
KeyUsage KeyCreationPage::keyUsage() const
{
KeyUsage usage;
if (signingAllowed()) {
usage.setCanSign(true);
}
if (encryptionAllowed() && !is_ecdh(subkeyType()) &&
!is_dsa(keyType()) && !is_rsa(subkeyType())) {
usage.setCanEncrypt(true);
}
if (authenticationAllowed()) {
usage.setCanAuthenticate(true);
}
if (!usage.value() && certificationAllowed()) {
/* Empty usages cause an error so we need to
* add at least certify if nothing else is selected */
usage.setCanCertify(true);
}
return usage;
}
KeyUsage KeyCreationPage::subkeyUsage() const
{
KeyUsage usage;
if (encryptionAllowed() && (is_dsa(keyType()) || is_rsa(subkeyType()) ||
is_ecdh(subkeyType()))) {
Q_ASSERT(subkeyType());
usage.setCanEncrypt(true);
}
return usage;
}
QString KeyCreationPage::createGnupgKeyParms() const
{
KeyParameters keyParameters(pgp() ? KeyParameters::OpenPGP : KeyParameters::CMS);
keyParameters.setKeyType(keyType());
if (is_ecdsa(keyType()) || is_eddsa(keyType())) {
keyParameters.setKeyCurve(keyCurve());
} else if (const unsigned int strength = keyStrength()) {
keyParameters.setKeyLength(strength);
}
keyParameters.setKeyUsage(keyUsage());
if (subkeyType()) {
keyParameters.setSubkeyType(subkeyType());
if (is_ecdh(subkeyType())) {
keyParameters.setSubkeyCurve(subkeyCurve());
} else if (const unsigned int strength = subkeyStrength()) {
keyParameters.setSubkeyLength(strength);
}
keyParameters.setSubkeyUsage(subkeyUsage());
}
if (pgp()) {
if (expiryDate().isValid()) {
keyParameters.setExpirationDate(expiryDate());
}
if (!name().isEmpty()) {
keyParameters.setName(name());
}
if (!email().isEmpty()) {
keyParameters.setEmail(email());
}
} else {
keyParameters.setDN(dn());
keyParameters.setEmail(email());
const auto addesses{additionalEMailAddresses()};
for (const QString &email : addesses) {
keyParameters.addEmail(email);
}
const auto dnsN{dnsNames()};
for (const QString &dns : dnsN) {
keyParameters.addDomainName(dns);
}
const auto urisList{uris()};
for (const QString &uri : urisList) {
keyParameters.addURI(uri);
}
}
const QString result = keyParameters.toString();
qCDebug(KLEOPATRA_LOG) << '\n' << result;
return result;
}
void KeyCreationPage::slotResult(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog)
{
Q_UNUSED(auditLog)
if (result.error().code() || (pgp() && !result.fingerprint())) {
setField(QStringLiteral("error"), result.error().isCanceled()
? i18n("Operation canceled.")
: i18n("Could not create key pair: %1",
- QString::fromLocal8Bit(result.error().asString())));
+ Formatting::errorAsString(result.error())));
setField(QStringLiteral("url"), QString());
setField(QStringLiteral("result"), QString());
} else if (pgp()) {
setField(QStringLiteral("error"), QString());
setField(QStringLiteral("url"), QString());
setField(QStringLiteral("result"), i18n("Key pair created successfully.\n"
"Fingerprint: %1", Formatting::prettyID(result.fingerprint())));
} else {
QFile file(tmpDir().absoluteFilePath(QStringLiteral("request.p10")));
if (!file.open(QIODevice::WriteOnly)) {
setField(QStringLiteral("error"), i18n("Could not write output file %1: %2",
file.fileName(), file.errorString()));
setField(QStringLiteral("url"), QString());
setField(QStringLiteral("result"), QString());
} else {
file.write(request);
setField(QStringLiteral("error"), QString());
setField(QStringLiteral("url"), QUrl::fromLocalFile(file.fileName()).toString());
setField(QStringLiteral("result"), i18n("Key pair created successfully."));
}
}
// Ensure that we have the key in the keycache
if (pgp() && !result.error().code() && result.fingerprint()) {
auto ctx = Context::createForProtocol(OpenPGP);
if (ctx) {
// Check is pretty useless something very buggy in that case.
Error e;
const auto key = ctx->key(result.fingerprint(), e, true);
if (!key.isNull()) {
KeyCache::mutableInstance()->insert(key);
} else {
qCDebug(KLEOPATRA_LOG) << "Failed to find newly generated key.";
}
delete ctx;
}
}
setField(QStringLiteral("fingerprint"), result.fingerprint() ?
QString::fromLatin1(result.fingerprint()) : QString());
job = nullptr;
Q_EMIT completeChanged();
const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard");
if (config.readEntry("SkipResultPage", false)) {
if (result.fingerprint()) {
KleopatraApplication::instance()->slotActivateRequested(QStringList() <<
QStringLiteral("kleopatra") << QStringLiteral("--query") << QLatin1String(result.fingerprint()), QString());
QMetaObject::invokeMethod(wizard(), "close", Qt::QueuedConnection);
} else {
QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection);
}
} else {
QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection);
}
}
diff --git a/src/selftest/gpgagentcheck.cpp b/src/selftest/gpgagentcheck.cpp
index 7814af823..33e7b53c4 100644
--- a/src/selftest/gpgagentcheck.cpp
+++ b/src/selftest/gpgagentcheck.cpp
@@ -1,99 +1,101 @@
/* -*- mode: c++; c-basic-offset:4 -*-
selftest/gpgagentcheck.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "gpgagentcheck.h"
#include "implementation_p.h"
+#include <Libkleo/Formatting>
+
#include <gpgme++/context.h>
#include <QTextDocument> // for Qt::escape
#include <KLocalizedString>
using namespace Kleo;
using namespace Kleo::_detail;
using namespace GpgME;
namespace
{
class GpgAgentCheck : public SelfTestImplementation
{
public:
explicit GpgAgentCheck()
: SelfTestImplementation(i18nc("@title", "Gpg-Agent Connectivity"))
{
runTest();
}
void runTest()
{
m_skipped = true;
if (!hasFeature(AssuanEngineFeature, 0)) {
m_error = i18n("GpgME library too old");
m_explanation = i18nc("@info",
"Either the GpgME library itself is too old, "
"or the GpgME++ library was compiled against "
"an older GpgME that did not support connecting to gpg-agent.");
m_proposedFix = xi18nc("@info",
"Upgrade to <application>gpgme</application> 1.2.0 or higher, "
"and ensure that gpgme++ was compiled against it.");
} else if (ensureEngineVersion(GpgME::GpgConfEngine, 2, 1, 0)) {
// 2.1 starts the agent on demand and requires it. So for 2.1.0 we can assume
// autostart works and we don't need to care about the agent.
m_skipped = false;
m_passed = true;
return;
} else {
Error error;
const std::unique_ptr<Context> ctx = Context::createForEngine(AssuanEngine, &error);
if (!ctx.get()) {
m_error = i18n("GpgME does not support gpg-agent");
m_explanation = xi18nc("@info",
"<para>The <application>GpgME</application> library is new "
"enough to support <application>gpg-agent</application>, "
"but does not seem to do so in this installation.</para>"
"<para>The error returned was: <message>%1</message>.</para>",
- QString::fromLocal8Bit(error.asString()).toHtmlEscaped());
+ Formatting::errorAsString(error).toHtmlEscaped());
// PENDING(marc) proposed fix?
} else {
m_skipped = false;
const Error error = ctx->assuanTransact("GETINFO version");
if (error) {
m_passed = false;
m_error = i18n("unexpected error");
m_explanation = xi18nc("@info",
"<para>Unexpected error while asking <application>gpg-agent</application> "
"for its version.</para>"
"<para>The error returned was: <message>%1</message>.</para>",
- QString::fromLocal8Bit(error.asString()).toHtmlEscaped());
+ Formatting::errorAsString(error).toHtmlEscaped());
// PENDING(marc) proposed fix?
} else {
m_passed = true;
}
}
}
}
};
}
std::shared_ptr<SelfTest> Kleo::makeGpgAgentConnectivitySelfTest()
{
return std::shared_ptr<SelfTest>(new GpgAgentCheck);
}
diff --git a/src/smartcard/netkeycard.cpp b/src/smartcard/netkeycard.cpp
index 822dc2760..bdda91873 100644
--- a/src/smartcard/netkeycard.cpp
+++ b/src/smartcard/netkeycard.cpp
@@ -1,123 +1,125 @@
/* smartcard/netkeycard.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "netkeycard.h"
#include "keypairinfo.h"
#include "kleopatra_debug.h"
+#include <Libkleo/Formatting>
+
#include <gpgme++/error.h>
#include <gpgme++/context.h>
#include <gpgme++/keylistresult.h>
#include <memory>
#include <string>
using namespace Kleo;
using namespace Kleo::SmartCard;
// static
const std::string NetKeyCard::AppName = "nks";
namespace
{
static GpgME::Key lookup_key(GpgME::Context *ctx, const std::string &keyGrip)
{
if (!ctx || keyGrip.empty()) {
return GpgME::Key();
}
const std::string pattern = '&' + keyGrip;
qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: pattern=" << pattern.c_str();
if (const auto err = ctx->startKeyListing(pattern.c_str())) {
- qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: startKeyListing failed:" << err.asString();
+ qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: startKeyListing failed:" << Formatting::errorAsString(err);
return GpgME::Key();
}
GpgME::Error e;
const auto key = ctx->nextKey(e);
ctx->endKeyListing();
qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: e=" << e.code() << "; key.isNull()" << key.isNull();
return key;
}
} // namespace
NetKeyCard::NetKeyCard(const Card &card)
: Card(card)
{
setAppName(AppName);
}
// static
std::string NetKeyCard::nksPinKeyRef()
{
return std::string("PW1.CH");
}
// static
std::string NetKeyCard::sigGPinKeyRef()
{
return std::string("PW1.CH.SIG");
}
void NetKeyCard::processCardInfo()
{
setKeyPairInfo(keyInfos());
}
void NetKeyCard::setKeyPairInfo(const std::vector<KeyPairInfo> &infos)
{
// check that any of the keys are new
const std::unique_ptr<GpgME::Context> klc(GpgME::Context::createForProtocol(GpgME::CMS));
if (!klc.get()) {
return;
}
klc->setKeyListMode(GpgME::Ephemeral);
klc->addKeyListMode(GpgME::Validate);
setCanLearnKeys(false);
mKeys.clear();
for (const auto &info: infos) {
const auto key = lookup_key(klc.get(), info.grip);
if (key.isNull()) {
setCanLearnKeys(true);
}
mKeys.push_back(key);
}
}
// State 0 -> NKS PIN Retry counter
// State 1 -> NKS PUK Retry counter
// State 2 -> SigG PIN Retry counter
// State 3 -> SigG PUK Retry counter
bool NetKeyCard::hasNKSNullPin() const
{
const auto states = pinStates();
if (states.size() < 2) {
qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << states.size();
return false;
}
return states[0] == Card::NullPin;
}
bool NetKeyCard::hasSigGNullPin() const
{
const auto states = pinStates();
if (states.size() < 4) {
qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << states.size();
return false;
}
return states[2] == Card::NullPin;
}
std::vector<GpgME::Key> NetKeyCard::keys() const
{
return mKeys;
}
diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp
index 602d02c3f..fdab3eeb6 100644
--- a/src/smartcard/readerstatus.cpp
+++ b/src/smartcard/readerstatus.cpp
@@ -1,1175 +1,1176 @@
/* -*- mode: c++; c-basic-offset:4 -*-
smartcard/readerstatus.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "readerstatus.h"
#include "deviceinfowatcher.h"
#include <Libkleo/Assuan>
+#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/FileSystemWatcher>
#include <Libkleo/KeyCache>
#include <Libkleo/Stl_Util>
#include <QGpgME/Debug>
#include <gpgme++/context.h>
#include <gpgme++/defaultassuantransaction.h>
#include <gpgme++/engineinfo.h>
#include <gpg-error.h>
#include "openpgpcard.h"
#include "netkeycard.h"
#include "pivcard.h"
#include "p15card.h"
#include <QMutex>
#include <QWaitCondition>
#include <QThread>
#include <QPointer>
#include <QRegularExpression>
#include "utils/kdtoolsglobal.h"
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
using namespace GpgME;
static ReaderStatus *self = nullptr;
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
static const char *flags[] = {
"NOCARD",
"PRESENT",
"ACTIVE",
"USABLE",
};
static_assert(sizeof flags / sizeof * flags == Card::_NumScdStates, "");
static const char *prettyFlags[] = {
"NoCard",
"CardPresent",
"CardActive",
"CardUsable",
"CardError",
};
static_assert(sizeof prettyFlags / sizeof * prettyFlags == Card::NumStates, "");
Q_DECLARE_METATYPE(GpgME::Error)
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
static QDebug operator<<(QDebug s, const std::string &string)
{
return s << QString::fromStdString(string);
}
#endif
namespace
{
static bool gpgHasMultiCardMultiAppSupport()
{
return !(engineInfo(GpgME::GpgEngine).engineVersion() < "2.3.0");
}
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 << ')';
}
struct CardApp {
std::string serialNumber;
std::string appName;
};
static void logUnexpectedStatusLine(const std::pair<std::string, std::string> &line,
const std::string &prefix = std::string(),
const std::string &command = std::string())
{
qCWarning(KLEOPATRA_LOG) << (!prefix.empty() ? QString::fromStdString(prefix + ": ") : QString())
<< "Unexpected status line"
<< (!command.empty() ? QString::fromStdString(" on " + command + ":") : QLatin1String(":"))
<< QString::fromStdString(line.first)
<< QString::fromStdString(line.second);
}
static int parse_app_version(const std::string &s)
{
return std::atoi(s.c_str());
}
static Card::PinState parse_pin_state(const QString &s)
{
bool ok;
int i = s.toInt(&ok);
if (!ok) {
qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s;
return Card::UnknownPinState;
}
switch (i) {
case -4: return Card::NullPin;
case -3: return Card::PinBlocked;
case -2: return Card::NoPin;
case -1: return Card::UnknownPinState;
default:
if (i < 0) {
return Card::UnknownPinState;
} else {
return Card::PinOk;
}
}
}
static const std::string scd_getattr_status(std::shared_ptr<Context> &gpgAgent, const char *what, Error &err)
{
std::string cmd = "SCD GETATTR ";
cmd += what;
return Assuan::sendStatusCommand(gpgAgent, cmd.c_str(), err);
}
static const std::string getAttribute(std::shared_ptr<Context> &gpgAgent, const char *attribute, const char *versionHint)
{
Error err;
const auto result = scd_getattr_status(gpgAgent, attribute, err);
if (err) {
if (err.code() == GPG_ERR_INV_NAME) {
qCDebug(KLEOPATRA_LOG) << "Querying for attribute" << attribute << "not yet supported; needs GnuPG" << versionHint;
} else {
qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR " << attribute << " failed:" << err;
}
return std::string();
}
return result;
}
static std::vector<CardApp> getCardsAndApps(std::shared_ptr<Context> &gpgAgent, Error &err)
{
std::vector<CardApp> result;
if (gpgHasMultiCardMultiAppSupport()) {
const std::string command = "SCD GETINFO all_active_apps";
const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err);
if (err) {
return result;
}
for (const auto &statusLine: statusLines) {
if (statusLine.first == "SERIALNO") {
const auto serialNumberAndApps = QByteArray::fromStdString(statusLine.second).split(' ');
if (serialNumberAndApps.size() >= 2) {
const auto serialNumber = serialNumberAndApps[0];
auto apps = serialNumberAndApps.mid(1);
// sort the apps to get a stable order independently of the currently selected application
std::sort(apps.begin(), apps.end());
for (const auto &app: apps) {
qCDebug(KLEOPATRA_LOG) << "getCardsAndApps(): Found card" << serialNumber << "with app" << app;
result.push_back({ serialNumber.toStdString(), app.toStdString() });
}
} else {
logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command);
}
} else {
logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command);
}
}
} else {
// use SCD SERIALNO to get the currently active card
const auto serialNumber = Assuan::sendStatusCommand(gpgAgent, "SCD SERIALNO", err);
if (err) {
return result;
}
// use SCD GETATTR APPTYPE to find out which app is active
auto appName = scd_getattr_status(gpgAgent, "APPTYPE", err);
std::transform(appName.begin(), appName.end(), appName.begin(),
[](unsigned char c){ return std::tolower(c); });
if (err) {
return result;
}
result.push_back({ serialNumber, appName });
}
return result;
}
static std::string switchCard(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber, Error &err)
{
const std::string command = "SCD SWITCHCARD " + serialNumber;
const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err);
if (err) {
return std::string();
}
if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second == serialNumber) {
return serialNumber;
}
qCWarning(KLEOPATRA_LOG) << "switchCard():" << command << "returned" << statusLines
<< "(expected:" << "SERIALNO " + serialNumber << ")";
return std::string();
}
static std::string switchApp(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber,
const std::string &appName, Error &err)
{
const std::string command = "SCD SWITCHAPP " + appName;
const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err);
if (err) {
return std::string();
}
if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" &&
statusLines[0].second.find(serialNumber + ' ' + appName) == 0) {
return appName;
}
qCWarning(KLEOPATRA_LOG) << "switchApp():" << command << "returned" << statusLines
<< "(expected:" << "SERIALNO " + serialNumber + ' ' + appName + "..." << ")";
return std::string();
}
static const char * get_openpgp_card_manufacturer_from_serial_number(const std::string &serialno)
{
qCDebug(KLEOPATRA_LOG) << "get_openpgp_card_manufacturer_from_serial_number(" << serialno.c_str() << ")";
const bool isProperOpenPGPCardSerialNumber =
serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401";
if (isProperOpenPGPCardSerialNumber) {
const char *sn = serialno.c_str();
const int manufacturerId = xtoi_2(sn + 16)*256 + xtoi_2(sn + 18);
switch (manufacturerId) {
case 0x0001: return "PPC Card Systems";
case 0x0002: return "Prism";
case 0x0003: return "OpenFortress";
case 0x0004: return "Wewid";
case 0x0005: return "ZeitControl";
case 0x0006: return "Yubico";
case 0x0007: return "OpenKMS";
case 0x0008: return "LogoEmail";
case 0x002A: return "Magrathea";
case 0x1337: return "Warsaw Hackerspace";
case 0xF517: return "FSIJ";
/* 0x0000 and 0xFFFF are defined as test cards per spec,
0xFF00 to 0xFFFE are assigned for use with randomly created
serial numbers. */
case 0x0000:
case 0xffff: return "test card";
default: return (manufacturerId & 0xff00) == 0xff00 ? "unmanaged S/N range" : "unknown";
}
} else {
return "unknown";
}
}
static std::vector<std::string> get_openpgp_card_supported_algorithms_announced_by_card(std::shared_ptr<Context> &gpgAgent)
{
static constexpr std::string_view cardSlotPrefix = "OPENPGP.1 ";
static const std::map<std::string_view, std::string_view> algoMapping = {
{"cv25519", "curve25519"},
{"cv448", "curve448"},
{"ed25519", "curve25519"},
{"ed448", "curve448"},
{"x448", "curve448"},
};
Error err;
const auto lines = Assuan::sendStatusLinesCommand(gpgAgent, "SCD GETATTR KEY-ATTR-INFO", err);
if (err) {
return {};
}
std::vector<std::string> algos;
kdtools::transform_if(lines.cbegin(),
lines.cend(),
std::back_inserter(algos),
[](const auto &line) {
auto algo = line.second.substr(cardSlotPrefix.size());
// map a few algorithms to the standard names used by us
const auto mapping = algoMapping.find(algo);
if (mapping != algoMapping.end()) {
algo = mapping->second;
}
return algo;
},
[](const auto &line) {
// only consider KEY-ATTR-INFO status lines for the first card slot;
// for now, we assume that all card slots support the same algorithms
return line.first == "KEY-ATTR-INFO" && line.second.starts_with(cardSlotPrefix);
});
// remove duplicate algorithms
std::sort(algos.begin(), algos.end());
algos.erase(std::unique(algos.begin(), algos.end()), algos.end());
qCDebug(KLEOPATRA_LOG) << __func__ << "returns" << algos;
return algos;
}
static std::vector<std::string> get_openpgp_card_supported_algorithms(Card *card, std::shared_ptr<Context> &gpgAgent)
{
// first ask the smart card for the supported algorithms
const std::vector<std::string> announcedAlgos = get_openpgp_card_supported_algorithms_announced_by_card(gpgAgent);
if (!announcedAlgos.empty()) {
return announcedAlgos;
}
// otherwise, fall back to hard-coded lists
if ((card->cardType() == "yubikey") && (card->cardVersion() >= 0x050203)) {
return {
"rsa2048",
"rsa3072",
"rsa4096",
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
"curve25519",
};
} else if ((card->cardType() == "zeitcontrol") && (card->appVersion() >= 0x0304)) {
return {
"rsa2048",
"rsa3072",
"rsa4096",
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
};
}
return {
"rsa2048",
"rsa3072",
"rsa4096"
};
}
static bool isOpenPGPCardSerialNumber(const std::string &serialNumber)
{
return serialNumber.size() == 32 && serialNumber.substr(0, 12) == "D27600012401";
}
static const std::string getDisplaySerialNumber(std::shared_ptr<Context> &gpgAgent, Error &err)
{
const auto displaySerialNumber = scd_getattr_status(gpgAgent, "$DISPSERIALNO", err);
if (err && err.code() != GPG_ERR_INV_NAME) {
qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR $DISPSERIALNO failed:" << err;
}
return displaySerialNumber;
}
static void setDisplaySerialNumber(Card *card, std::shared_ptr<Context> &gpgAgent)
{
static const QRegularExpression leadingZeros(QStringLiteral("^0*"));
Error err;
const QString displaySerialNumber = QString::fromStdString(getDisplaySerialNumber(gpgAgent, err));
if (err) {
card->setDisplaySerialNumber(QString::fromStdString(card->serialNumber()));
return;
}
if (isOpenPGPCardSerialNumber(card->serialNumber()) && displaySerialNumber.size() == 12) {
// add a space between manufacturer id and card id for OpenPGP cards
card->setDisplaySerialNumber(displaySerialNumber.left(4) + QLatin1Char(' ') + displaySerialNumber.right(8));
} else {
card->setDisplaySerialNumber(displaySerialNumber);
}
return;
}
static void learnCardKeyStubs(const Card *card, std::shared_ptr<Context> &gpg_agent)
{
for (const KeyPairInfo &keyInfo : card->keyInfos()) {
if (!keyInfo.grip.empty()) {
Error err;
const auto command = std::string("READKEY --card --no-data -- ") + keyInfo.keyRef;
(void)Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err);
if (err) {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
}
}
}
}
static void handle_openpgp_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto pgpCard = new OpenPGPCard(*ci);
const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err.code()) {
ci->setStatus(Card::CardError);
return;
}
pgpCard->setCardInfo(info);
if (pgpCard->manufacturer().empty()) {
// fallback in case MANUFACTURER is not yet included in the card info
pgpCard->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber()));
}
setDisplaySerialNumber(pgpCard, gpg_agent);
learnCardKeyStubs(pgpCard, gpg_agent);
pgpCard->setSupportedAlgorithms(get_openpgp_card_supported_algorithms(pgpCard, gpg_agent));
ci.reset(pgpCard);
}
static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr<Context> &gpg_agent)
{
Error err;
const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef;
const auto keyPairInfoLines = Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err);
if (err) {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
return;
}
// this adds the key algorithm (and the key creation date, but that seems to be unset for PIV) to the existing key pair information
pivCard->setCardInfo(keyPairInfoLines);
}
static void readCertificateFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr<Context> &gpg_agent)
{
Error err;
const std::string command = std::string("SCD READCERT ") + keyRef;
const std::string certificateData = Assuan::sendDataCommand(gpg_agent, command.c_str(), err);
if (err && err.code() != GPG_ERR_NOT_FOUND) {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
return;
}
if (certificateData.empty()) {
qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): No certificate stored on card";
return;
}
qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Found certificate stored on card";
pivCard->setCertificateData(keyRef, certificateData);
}
static void handle_piv_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto pivCard = new PIVCard(*ci);
const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err) {
ci->setStatus(Card::CardError);
return;
}
pivCard->setCardInfo(info);
setDisplaySerialNumber(pivCard, gpg_agent);
for (const KeyPairInfo &keyInfo : pivCard->keyInfos()) {
if (!keyInfo.grip.empty()) {
readKeyPairInfoFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent);
readCertificateFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent);
}
}
learnCardKeyStubs(pivCard, gpg_agent);
ci.reset(pivCard);
}
static void handle_p15_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto p15Card = new P15Card(*ci);
auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err) {
ci->setStatus(Card::CardError);
return;
}
const auto fprs = Assuan::sendStatusLinesCommand(gpg_agent, "SCD GETATTR KEY-FPR", err);
if (!err) {
info.insert(info.end(), fprs.begin(), fprs.end());
}
p15Card->setCardInfo(info);
learnCardKeyStubs(p15Card, gpg_agent);
setDisplaySerialNumber(p15Card, gpg_agent);
ci.reset(p15Card);
}
static void handle_netkey_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto nkCard = new NetKeyCard(*ci);
ci.reset(nkCard);
ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err)));
if (err.code()) {
qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR NKS-VERSION failed:" << err;
- ci->setErrorMsg(QStringLiteral ("NKS-VERSION failed: ") + QString::fromUtf8(err.asString()));
+ ci->setErrorMsg(QStringLiteral ("NKS-VERSION failed: ") + Formatting::errorAsString(err));
return;
}
if (ci->appVersion() < 3) {
qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 (or later) card, giving up. Version:" << ci->appVersion();
ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion()));
return;
}
setDisplaySerialNumber(nkCard, gpg_agent);
// the following only works for NKS v3...
const auto chvStatus = QString::fromStdString(
scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QLatin1Char(' '));
if (err.code()) {
qCDebug(KLEOPATRA_LOG) << "Running SCD GETATTR CHV-STATUS failed:" << err;
- ci->setErrorMsg(QStringLiteral ("CHV-Status failed: ") + QString::fromUtf8(err.asString()));
+ ci->setErrorMsg(QStringLiteral ("CHV-Status failed: ") + Formatting::errorAsString(err));
return;
}
std::vector<Card::PinState> states;
states.reserve(chvStatus.count());
// CHV Status for NKS v3 is
// Pin1 (Normal pin) Pin2 (Normal PUK)
// SigG1 SigG PUK.
int num = 0;
for (const auto &state: chvStatus) {
const auto parsed = parse_pin_state (state);
states.push_back(parsed);
if (parsed == Card::NullPin) {
if (num == 0) {
ci->setHasNullPin(true);
}
}
++num;
}
nkCard->setPinStates(states);
const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err) {
ci->setStatus(Card::CardError);
return;
}
nkCard->setCardInfo(info);
learnCardKeyStubs(nkCard, gpg_agent);
}
static std::shared_ptr<Card> get_card_status(const std::string &serialNumber, const std::string &appName, std::shared_ptr<Context> &gpg_agent)
{
qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')';
auto ci = std::shared_ptr<Card>(new Card());
if (gpgHasMultiCardMultiAppSupport()) {
// select card
Error err;
const auto result = switchCard(gpg_agent, serialNumber, err);
if (err) {
if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) {
ci->setStatus(Card::NoCard);
} else {
ci->setStatus(Card::CardError);
}
return ci;
}
if (result.empty()) {
qCWarning(KLEOPATRA_LOG) << "get_card_status: switching card failed";
ci->setStatus(Card::CardError);
return ci;
}
ci->setStatus(Card::CardPresent);
} else {
ci->setStatus(Card::CardPresent);
}
if (gpgHasMultiCardMultiAppSupport()) {
// select app
Error err;
const auto result = switchApp(gpg_agent, serialNumber, appName, err);
if (err) {
if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) {
ci->setStatus(Card::NoCard);
} else {
ci->setStatus(Card::CardError);
}
return ci;
}
if (result.empty()) {
qCWarning(KLEOPATRA_LOG) << "get_card_status: switching app failed";
ci->setStatus(Card::CardError);
return ci;
}
}
ci->setSerialNumber(serialNumber);
ci->setSigningKeyRef(getAttribute(gpg_agent, "$SIGNKEYID", "2.2.18"));
ci->setEncryptionKeyRef(getAttribute(gpg_agent, "$ENCRKEYID", "2.2.18"));
// Handle different card types
if (appName == NetKeyCard::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end";
handle_netkey_card(ci, gpg_agent);
return ci;
} else if (appName == OpenPGPCard::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end";
ci->setAuthenticationKeyRef(OpenPGPCard::pgpAuthKeyRef());
handle_openpgp_card(ci, gpg_agent);
return ci;
} else if (appName == PIVCard::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end";
handle_piv_card(ci, gpg_agent);
return ci;
} else if (appName == P15Card::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found P15 card" << ci->serialNumber().c_str() << "end";
handle_p15_card(ci, gpg_agent);
return ci;
} else {
qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << appName;
return ci;
}
return ci;
}
static bool isCardNotPresentError(const GpgME::Error &err)
{
// see fixup_scd_errors() in gpg-card.c
return err && ((err.code() == GPG_ERR_CARD_NOT_PRESENT) ||
((err.code() == GPG_ERR_ENODEV || err.code() == GPG_ERR_CARD_REMOVED) &&
(err.sourceID() == GPG_ERR_SOURCE_SCD)));
}
static std::vector<std::shared_ptr<Card> > update_cardinfo(std::shared_ptr<Context> &gpgAgent)
{
qCDebug(KLEOPATRA_LOG) << "update_cardinfo()";
// ensure that a card is present and that all cards are properly set up
{
Error err;
const char *command = (gpgHasMultiCardMultiAppSupport()) ? "SCD SERIALNO --all" : "SCD SERIALNO";
const std::string serialno = Assuan::sendStatusCommand(gpgAgent, command, err);
if (err) {
if (isCardNotPresentError(err)) {
qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present";
return std::vector<std::shared_ptr<Card> >();
} else {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
auto ci = std::shared_ptr<Card>(new Card());
ci->setStatus(Card::CardError);
return std::vector<std::shared_ptr<Card> >(1, ci);
}
}
}
Error err;
const std::vector<CardApp> cardApps = getCardsAndApps(gpgAgent, err);
if (err) {
if (isCardNotPresentError(err)) {
qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present";
return std::vector<std::shared_ptr<Card> >();
} else {
qCWarning(KLEOPATRA_LOG) << "Getting active apps on all inserted cards failed:" << err;
auto ci = std::shared_ptr<Card>(new Card());
ci->setStatus(Card::CardError);
return std::vector<std::shared_ptr<Card> >(1, ci);
}
}
std::vector<std::shared_ptr<Card> > cards;
for (const auto &cardApp: cardApps) {
const auto card = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent);
cards.push_back(card);
}
return cards;
}
} // namespace
struct Transaction {
CardApp cardApp;
QByteArray command;
QPointer<QObject> receiver;
ReaderStatus::TransactionFunc slot;
AssuanTransaction* assuanTransaction;
};
static const Transaction updateTransaction = { { "__all__", "__all__" }, "__update__", nullptr, nullptr, nullptr };
static const Transaction quitTransaction = { { "__all__", "__all__" }, "__quit__", nullptr, nullptr, nullptr };
namespace
{
class ReaderStatusThread : public QThread
{
Q_OBJECT
public:
explicit ReaderStatusThread(QObject *parent = nullptr)
: QThread(parent),
m_gnupgHomePath(Kleo::gnupgHomeDirectory()),
m_transactions(1, updateTransaction) // force initial scan
{
connect(this, &ReaderStatusThread::oneTransactionFinished,
this, &ReaderStatusThread::slotOneTransactionFinished);
}
std::vector<std::shared_ptr<Card> > cardInfos() const
{
const QMutexLocker locker(&m_mutex);
return m_cardInfos;
}
Card::Status cardStatus(unsigned int slot) const
{
const QMutexLocker locker(&m_mutex);
if (slot < m_cardInfos.size()) {
return m_cardInfos[slot]->status();
} else {
return Card::NoCard;
}
}
void addTransaction(const Transaction &t)
{
const QMutexLocker locker(&m_mutex);
m_transactions.push_back(t);
m_waitForTransactions.wakeOne();
}
Q_SIGNALS:
void firstCardWithNullPinChanged(const std::string &serialNumber);
void anyCardCanLearnKeysChanged(bool);
void cardAdded(const std::string &serialNumber, const std::string &appName);
void cardChanged(const std::string &serialNumber, const std::string &appName);
void cardRemoved(const std::string &serialNumber, const std::string &appName);
void updateFinished();
void oneTransactionFinished(const GpgME::Error &err);
public Q_SLOTS:
void deviceStatusChanged(const QByteArray &details)
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::deviceStatusChanged(" << details << ")";
addTransaction(updateTransaction);
}
void ping()
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()";
addTransaction(updateTransaction);
}
void stop()
{
const QMutexLocker locker(&m_mutex);
m_transactions.push_front(quitTransaction);
m_waitForTransactions.wakeOne();
}
private Q_SLOTS:
void slotOneTransactionFinished(const GpgME::Error &err)
{
std::list<Transaction> ft;
KDAB_SYNCHRONIZED(m_mutex)
ft.splice(ft.begin(), m_finishedTransactions);
for (const Transaction &t : std::as_const(ft))
if (t.receiver && t.slot) {
QMetaObject::invokeMethod(t.receiver, [&t, &err]() { t.slot(err); }, Qt::DirectConnection);
}
}
private:
void run() override {
while (true) {
std::shared_ptr<Context> gpgAgent;
CardApp cardApp;
QByteArray command;
bool nullSlot = false;
AssuanTransaction* assuanTransaction = nullptr;
std::list<Transaction> item;
std::vector<std::shared_ptr<Card> > oldCards;
while (!KeyCache::instance()->initialized()) {
qCDebug(KLEOPATRA_LOG) << "Waiting for Keycache to be initialized.";
sleep(1);
}
Error err;
std::unique_ptr<Context> c = Context::createForEngine(AssuanEngine, &err);
if (err.code() == GPG_ERR_NOT_SUPPORTED) {
return;
}
gpgAgent = std::shared_ptr<Context>(c.release());
KDAB_SYNCHRONIZED(m_mutex) {
while (m_transactions.empty()) {
// go to sleep waiting for more work:
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands";
m_waitForTransactions.wait(&m_mutex);
}
// splice off the first transaction without
// copying, so we own it without really importing
// it into this thread (the QPointer isn't
// thread-safe):
item.splice(item.end(),
m_transactions, m_transactions.begin());
// make local copies of the interesting stuff so
// we can release the mutex again:
cardApp = item.front().cardApp;
command = item.front().command;
nullSlot = !item.front().slot;
// we take ownership of the assuan transaction
std::swap(assuanTransaction, item.front().assuanTransaction);
oldCards = m_cardInfos;
}
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot;
// now, let's see what we got:
if (nullSlot && command == quitTransaction.command) {
return; // quit
}
if ((nullSlot && command == updateTransaction.command)) {
bool anyError = false;
if (cardApp.serialNumber == "__all__" || cardApp.appName == "__all__") {
std::vector<std::shared_ptr<Card> > newCards = update_cardinfo(gpgAgent);
KDAB_SYNCHRONIZED(m_mutex) {
m_cardInfos = newCards;
}
bool anyLC = false;
std::string firstCardWithNullPin;
for (const auto &newCard: newCards) {
const auto serialNumber = newCard->serialNumber();
const auto appName = newCard->appName();
const auto matchingOldCard = std::find_if(oldCards.cbegin(), oldCards.cend(),
[serialNumber, appName] (const std::shared_ptr<Card> &card) {
return card->serialNumber() == serialNumber && card->appName() == appName;
});
if (matchingOldCard == oldCards.cend()) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added";
Q_EMIT cardAdded(serialNumber, appName);
} else {
if (*newCard != **matchingOldCard) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed";
Q_EMIT cardChanged(serialNumber, appName);
}
oldCards.erase(matchingOldCard);
}
if (newCard->canLearnKeys()) {
anyLC = true;
}
if (newCard->hasNullPin() && firstCardWithNullPin.empty()) {
firstCardWithNullPin = newCard->serialNumber();
}
if (newCard->status() == Card::CardError) {
anyError = true;
}
}
for (const auto &oldCard: oldCards) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << oldCard->serialNumber() << "with app" << oldCard->appName() << "was removed";
Q_EMIT cardRemoved(oldCard->serialNumber(), oldCard->appName());
}
Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin);
Q_EMIT anyCardCanLearnKeysChanged(anyLC);
} else {
auto updatedCard = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent);
const auto serialNumber = updatedCard->serialNumber();
const auto appName = updatedCard->appName();
bool cardWasAdded = false;
bool cardWasChanged = false;
KDAB_SYNCHRONIZED(m_mutex) {
const auto matchingCard = std::find_if(m_cardInfos.begin(), m_cardInfos.end(),
[serialNumber, appName](const auto &card) {
return card->serialNumber() == serialNumber && card->appName() == appName;
});
if (matchingCard == m_cardInfos.end()) {
m_cardInfos.push_back(updatedCard);
cardWasAdded = true;
} else {
cardWasChanged = (*updatedCard != **matchingCard);
m_cardInfos[std::distance(m_cardInfos.begin(), matchingCard)] = updatedCard;
}
if (updatedCard->status() == Card::CardError) {
anyError = true;
}
}
if (cardWasAdded) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added";
Q_EMIT cardAdded(serialNumber, appName);
} else if (cardWasChanged) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed";
Q_EMIT cardChanged(serialNumber, appName);
}
}
if (anyError) {
gpgAgent.reset();
}
Q_EMIT updateFinished();
} else {
GpgME::Error err;
if (gpgHasMultiCardMultiAppSupport()) {
switchCard(gpgAgent, cardApp.serialNumber, err);
if (!err) {
switchApp(gpgAgent, cardApp.serialNumber, cardApp.appName, err);
}
}
if (!err) {
if (assuanTransaction) {
(void)Assuan::sendCommand(gpgAgent, command.constData(), std::unique_ptr<AssuanTransaction>(assuanTransaction), err);
} else {
(void)Assuan::sendCommand(gpgAgent, command.constData(), err);
}
}
KDAB_SYNCHRONIZED(m_mutex)
// splice 'item' into m_finishedTransactions:
m_finishedTransactions.splice(m_finishedTransactions.end(), item);
Q_EMIT oneTransactionFinished(err);
}
}
}
private:
mutable QMutex m_mutex;
QWaitCondition m_waitForTransactions;
const QString m_gnupgHomePath;
// protected by m_mutex:
std::vector<std::shared_ptr<Card> > m_cardInfos;
std::list<Transaction> m_transactions, m_finishedTransactions;
};
}
class ReaderStatus::Private : ReaderStatusThread
{
friend class Kleo::SmartCard::ReaderStatus;
ReaderStatus *const q;
public:
explicit Private(ReaderStatus *qq)
: ReaderStatusThread(qq),
q(qq),
watcher()
{
KDAB_SET_OBJECT_NAME(watcher);
qRegisterMetaType<Card::Status>("Kleo::SmartCard::Card::Status");
qRegisterMetaType<GpgME::Error>("GpgME::Error");
connect(this, &::ReaderStatusThread::cardAdded,
q, &ReaderStatus::cardAdded);
connect(this, &::ReaderStatusThread::cardChanged,
q, &ReaderStatus::cardChanged);
connect(this, &::ReaderStatusThread::cardRemoved,
q, &ReaderStatus::cardRemoved);
connect(this, &::ReaderStatusThread::updateFinished,
q, &ReaderStatus::updateFinished);
connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged,
q, &ReaderStatus::firstCardWithNullPinChanged);
connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged,
q, &ReaderStatus::anyCardCanLearnKeysChanged);
if (DeviceInfoWatcher::isSupported()) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using new DeviceInfoWatcher";
connect(&devInfoWatcher, &DeviceInfoWatcher::statusChanged, this, &::ReaderStatusThread::deviceStatusChanged);
} else {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using deprecated FileSystemWatcher";
watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status")));
watcher.addPath(Kleo::gnupgHomeDirectory());
watcher.setDelay(100);
connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping);
}
}
~Private() override
{
stop();
if (!wait(100)) {
terminate();
wait();
}
}
private:
std::string firstCardWithNullPinImpl() const
{
const auto cis = cardInfos();
const auto firstWithNullPin = std::find_if(cis.cbegin(), cis.cend(),
[](const std::shared_ptr<Card> &ci) { return ci->hasNullPin(); });
return firstWithNullPin != cis.cend() ? (*firstWithNullPin)->serialNumber() : std::string();
}
bool anyCardCanLearnKeysImpl() const
{
const auto cis = cardInfos();
return std::any_of(cis.cbegin(), cis.cend(),
[](const std::shared_ptr<Card> &ci) { return ci->canLearnKeys(); });
}
private:
FileSystemWatcher watcher;
DeviceInfoWatcher devInfoWatcher;
};
ReaderStatus::ReaderStatus(QObject *parent)
: QObject(parent), d(new Private(this))
{
self = this;
qRegisterMetaType<std::string>("std::string");
}
ReaderStatus::~ReaderStatus()
{
self = nullptr;
}
// slot
void ReaderStatus::startMonitoring()
{
d->start();
if (DeviceInfoWatcher::isSupported()) {
connect(&d->devInfoWatcher, &DeviceInfoWatcher::startOfGpgAgentRequested,
this, &ReaderStatus::startOfGpgAgentRequested);
d->devInfoWatcher.start();
}
}
// static
ReaderStatus *ReaderStatus::mutableInstance()
{
return self;
}
// static
const ReaderStatus *ReaderStatus::instance()
{
return self;
}
Card::Status ReaderStatus::cardStatus(unsigned int slot) const
{
return d->cardStatus(slot);
}
std::string ReaderStatus::firstCardWithNullPin() const
{
return d->firstCardWithNullPinImpl();
}
bool ReaderStatus::anyCardCanLearnKeys() const
{
return d->anyCardCanLearnKeysImpl();
}
void ReaderStatus::startSimpleTransaction(const std::shared_ptr<Card> &card, const QByteArray &command, QObject *receiver, const TransactionFunc &slot)
{
const CardApp cardApp = { card->serialNumber(), card->appName() };
const Transaction t = { cardApp, command, receiver, slot, nullptr };
d->addTransaction(t);
}
void ReaderStatus::startTransaction(const std::shared_ptr<Card> &card,
const QByteArray &command,
QObject *receiver,
const TransactionFunc &slot,
std::unique_ptr<AssuanTransaction> transaction)
{
const CardApp cardApp = { card->serialNumber(), card->appName() };
const Transaction t = { cardApp, command, receiver, slot, transaction.release() };
d->addTransaction(t);
}
void ReaderStatus::updateStatus()
{
d->ping();
}
void ReaderStatus::updateCard(const std::string &serialNumber, const std::string &appName)
{
const CardApp cardApp = { serialNumber, appName };
const Transaction t = { cardApp, updateTransaction.command, nullptr, nullptr, nullptr };
d->addTransaction(t);
}
std::vector <std::shared_ptr<Card> > ReaderStatus::getCards() const
{
return d->cardInfos();
}
std::shared_ptr<Card> ReaderStatus::getCard(const std::string &serialNumber, const std::string &appName) const
{
for (const auto &card: d->cardInfos()) {
if (card->serialNumber() == serialNumber && card->appName() == appName) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << serialNumber << "and app" << appName;
return card;
}
}
qCWarning(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << serialNumber << "and app" << appName;
return std::shared_ptr<Card>();
}
// static
std::string ReaderStatus::switchCard(std::shared_ptr<Context>& ctx, const std::string& serialNumber, Error& err)
{
return ::switchCard(ctx, serialNumber, err);
}
// static
std::string ReaderStatus::switchApp(std::shared_ptr<Context>& ctx, const std::string& serialNumber, const std::string& appName, Error& err)
{
return ::switchApp(ctx, serialNumber, appName, err);
}
// static
Error ReaderStatus::switchCardAndApp(const std::string &serialNumber, const std::string &appName)
{
Error err;
if (!(engineInfo(GpgEngine).engineVersion() < "2.3.0")) {
std::unique_ptr<Context> c = Context::createForEngine(AssuanEngine, &err);
if (err.code() == GPG_ERR_NOT_SUPPORTED) {
return err;
}
auto assuanContext = std::shared_ptr<Context>(c.release());
const auto resultSerialNumber = switchCard(assuanContext, serialNumber, err);
if (err || resultSerialNumber != serialNumber) {
qCWarning(KLEOPATRA_LOG) << "Switching to card" << QString::fromStdString(serialNumber) << "failed";
if (!err) {
err = Error::fromCode(GPG_ERR_UNEXPECTED);
}
return err;
}
const auto resultAppName = switchApp(assuanContext, serialNumber, appName, err);
if (err || resultAppName != appName) {
qCWarning(KLEOPATRA_LOG) << "Switching card to" << QString::fromStdString(appName) << "app failed";
if (!err) {
err = Error::fromCode(GPG_ERR_UNEXPECTED);
}
return err;
}
}
return err;
}
#include "readerstatus.moc"
diff --git a/src/uiserver/assuanserverconnection.cpp b/src/uiserver/assuanserverconnection.cpp
index 7c61d0dde..ced038311 100644
--- a/src/uiserver/assuanserverconnection.cpp
+++ b/src/uiserver/assuanserverconnection.cpp
@@ -1,1503 +1,1504 @@
/* -*- mode: c++; c-basic-offset:4 -*-
uiserver/assuanserverconnection.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef QT_NO_CAST_TO_ASCII
# define QT_NO_CAST_TO_ASCII
#endif
#ifndef QT_NO_CAST_FROM_ASCII
# define QT_NO_CAST_FROM_ASCII
#endif
#include <config-kleopatra.h>
#include <version-kleopatra.h>
#include "assuanserverconnection.h"
#include "assuancommand.h"
#include "sessiondata.h"
#include <utils/input.h>
#include <utils/output.h>
-#include <Libkleo/GnuPG>
#include <utils/detail_p.h>
#include <utils/log.h>
#include <utils/kleo_assert.h>
+#include <Libkleo/Formatting>
+#include <Libkleo/GnuPG>
#include <Libkleo/Hex>
#include <Libkleo/Stl_Util>
#include <Libkleo/KleoException>
#include <Libkleo/KeyCache>
#include <gpgme++/data.h>
#include <gpgme++/key.h>
#include <KMime/HeaderParsing>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <KWindowSystem>
#include <QSocketNotifier>
#include <QTimer>
#include <QVariant>
#include <QPointer>
#include <QFileInfo>
#include <QStringList>
#include <QRegularExpression>
#include <QWidget>
#include <QCoreApplication>
#include <map>
#include <algorithm>
#include <functional>
#include <type_traits>
#include <cerrno>
#ifdef __GLIBCXX__
# include <ext/algorithm> // for is_sorted
#endif
#ifdef Q_OS_WIN
# include <io.h>
# include <process.h>
#else
# include <sys/types.h>
# include <unistd.h>
#endif
using namespace Kleo;
static const unsigned int INIT_SOCKET_FLAGS = 3; // says info assuan...
//static int(*USE_DEFAULT_HANDLER)(assuan_context_t,char*) = 0;
static const int FOR_READING = 0;
static const unsigned int MAX_ACTIVE_FDS = 32;
static void my_assuan_release(assuan_context_t ctx)
{
if (ctx) {
assuan_release(ctx);
}
}
// std::shared_ptr for assuan_context_t w/ deleter enforced to assuan_deinit_server:
using AssuanContextBase = std::shared_ptr<std::remove_pointer<assuan_context_t>::type>;
struct AssuanContext : AssuanContextBase {
AssuanContext() : AssuanContextBase() {}
explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &my_assuan_release) {}
void reset(assuan_context_t ctx = nullptr)
{
AssuanContextBase::reset(ctx, &my_assuan_release);
}
};
static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const char *err_msg)
{
return assuan_process_done(ctx, assuan_set_error(ctx, err, err_msg));
}
static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const std::string &err_msg)
{
return assuan_process_done_msg(ctx, err, err_msg.c_str());
}
static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const QString &err_msg)
{
return assuan_process_done_msg(ctx, err, err_msg.toUtf8().constData());
}
static std::map<std::string, std::string> upcase_option(const char *option, std::map<std::string, std::string> options)
{
std::string value;
bool value_found = false;
auto it = options.begin();
while (it != options.end())
if (qstricmp(it->first.c_str(), option) == 0) {
value = it->second;
options.erase(it++);
value_found = true;
} else {
++it;
}
if (value_found) {
options[option] = value;
}
return options;
}
static std::map<std::string, std::string> parse_commandline(const char *line)
{
std::map<std::string, std::string> result;
if (line) {
const char *begin = line;
const char *lastEQ = nullptr;
while (*line) {
if (*line == ' ' || *line == '\t') {
if (begin != line) {
if (begin[0] == '-' && begin[1] == '-') {
begin += 2; // skip initial "--"
}
if (lastEQ && lastEQ > begin) {
result[ std::string(begin, lastEQ - begin) ] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1)));
} else {
result[ std::string(begin, line - begin) ] = std::string();
}
}
begin = line + 1;
} else if (*line == '=') {
if (line == begin)
throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX),
i18n("No option name given"));
else {
lastEQ = line;
}
}
++line;
}
if (begin != line) {
if (begin[0] == '-' && begin[1] == '-') {
begin += 2; // skip initial "--"
}
if (lastEQ && lastEQ > begin) {
result[ std::string(begin, lastEQ - begin) ] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1)));
} else {
result[ begin ] = std::string();
}
}
}
return result;
}
static WId wid_from_string(const QString &winIdStr, bool *ok = nullptr)
{
return static_cast<WId>(winIdStr.toULongLong(ok, 16));
}
static void apply_window_id(QWidget *widget, const QString &winIdStr)
{
if (!widget || winIdStr.isEmpty()) {
return;
}
bool ok = false;
const WId wid = wid_from_string(winIdStr, &ok);
if (!ok) {
qCDebug(KLEOPATRA_LOG) << "window-id value" << wid << "doesn't look like a number";
return;
}
if (QWidget *pw = QWidget::find(wid)) {
widget->setParent(pw, widget->windowFlags());
} else {
widget->setAttribute(Qt::WA_NativeWindow, true);
KWindowSystem::setMainWindow(widget->windowHandle(), wid);
}
}
//
//
// AssuanServerConnection:
//
//
class AssuanServerConnection::Private : public QObject
{
Q_OBJECT
friend class ::Kleo::AssuanServerConnection;
friend class ::Kleo::AssuanCommandFactory;
friend class ::Kleo::AssuanCommand;
AssuanServerConnection *const q;
public:
Private(assuan_fd_t fd_, const std::vector< std::shared_ptr<AssuanCommandFactory> > &factories_, AssuanServerConnection *qq);
~Private() override;
Q_SIGNALS:
void startKeyManager();
public Q_SLOTS:
void slotReadActivity(int)
{
Q_ASSERT(ctx);
int done = false;
if (const int err = assuan_process_next(ctx.get(), &done) || done) {
//if ( err == -1 || gpg_err_code(err) == GPG_ERR_EOF ) {
topHalfDeletion();
if (nohupedCommands.empty()) {
bottomHalfDeletion();
}
//} else {
//assuan_process_done( ctx.get(), err );
//return;
//}
}
}
int startCommandBottomHalf();
private:
void nohupDone(AssuanCommand *cmd)
{
const auto it = std::find_if(nohupedCommands.begin(), nohupedCommands.end(),
[cmd](const std::shared_ptr<AssuanCommand> &other) {
return other.get() == cmd;
});
Q_ASSERT(it != nohupedCommands.end());
nohupedCommands.erase(it);
if (nohupedCommands.empty() && closed) {
bottomHalfDeletion();
}
}
void commandDone(AssuanCommand *cmd)
{
if (!cmd || cmd != currentCommand.get()) {
return;
}
currentCommand.reset();
}
void topHalfDeletion()
{
if (currentCommand) {
currentCommand->canceled();
}
if (fd != ASSUAN_INVALID_FD) {
#if defined(Q_OS_WIN)
CloseHandle(fd);
#else
::close(fd);
#endif
}
notifiers.clear();
closed = true;
}
void bottomHalfDeletion()
{
if (sessionId) {
SessionDataHandler::instance()->exitSession(sessionId);
}
cleanup();
const QPointer<Private> that = this;
Q_EMIT q->closed(q);
if (that) { // still there
q->deleteLater();
}
}
private:
static gpg_error_t reset_handler(assuan_context_t ctx_, char *)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
conn.reset();
return 0;
}
static gpg_error_t option_handler(assuan_context_t ctx_, const char *key, const char *value)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (key && key[0] == '-' && key[1] == '-') {
key += 2; // skip "--"
}
conn.options[key] = QString::fromUtf8(value);
return 0;
//return gpg_error( GPG_ERR_UNKNOWN_OPTION );
}
static gpg_error_t session_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
const QString str = QString::fromUtf8(line);
static const QRegularExpression rx(QRegularExpression::anchoredPattern(uR"((\d+)(?:\s+(.*))?)"));
const QRegularExpressionMatch match = rx.match(str);
if (!match.hasMatch()) {
static const QString errorString = i18n("Parse error");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString);
}
bool ok = false;
if (const qulonglong id = match.captured(1).toULongLong(&ok)) {
if (ok && id <= std::numeric_limits<unsigned int>::max()) {
SessionDataHandler::instance()->enterSession(id);
conn.sessionId = id;
} else {
static const QString errorString = i18n("Parse error: numeric session id too large");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString);
}
}
const QString cap2 = match.captured(2);
if (!cap2.isEmpty()) {
conn.sessionTitle = cap2;
}
qCDebug(KLEOPATRA_LOG) << "session_handler: "
<< "id=" << static_cast<unsigned long>(conn.sessionId)
<< ", title=" << qPrintable(conn.sessionTitle);
return assuan_process_done(ctx_, 0);
}
static gpg_error_t capabilities_handler(assuan_context_t ctx_, char *line)
{
if (!QByteArray(line).trimmed().isEmpty()) {
static const QString errorString = i18n("CAPABILITIES does not take arguments");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
static const char capabilities[] =
"SENDER=info\n"
"RECIPIENT=info\n"
"SESSION\n"
;
return assuan_process_done(ctx_, assuan_send_data(ctx_, capabilities, sizeof capabilities - 1));
}
static gpg_error_t getinfo_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (qstrcmp(line, "version") == 0) {
static const char version[] = "Kleopatra " KLEOPATRA_VERSION_STRING;
return assuan_process_done(ctx_, assuan_send_data(ctx_, version, sizeof version - 1));
}
QByteArray ba;
if (qstrcmp(line, "pid") == 0) {
ba = QByteArray::number(QCoreApplication::applicationPid());
} else if (qstrcmp(line, "options") == 0) {
ba = conn.dumpOptions();
} else if (qstrcmp(line, "x-mementos") == 0) {
ba = conn.dumpMementos();
} else if (qstrcmp(line, "senders") == 0) {
ba = conn.dumpSenders();
} else if (qstrcmp(line, "recipients") == 0) {
ba = conn.dumpRecipients();
} else if (qstrcmp(line, "x-files") == 0) {
ba = conn.dumpFiles();
} else {
static const QString errorString = i18n("Unknown value for WHAT");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
return assuan_process_done(ctx_, assuan_send_data(ctx_, ba.constData(), ba.size()));
}
static gpg_error_t start_keymanager_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (line && *line) {
static const QString errorString = i18n("START_KEYMANAGER does not take arguments");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
Q_EMIT conn.q->startKeyManagerRequested();
return assuan_process_done(ctx_, 0);
}
static gpg_error_t start_confdialog_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (line && *line) {
static const QString errorString = i18n("START_CONFDIALOG does not take arguments");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
Q_EMIT conn.q->startConfigDialogRequested();
return assuan_process_done(ctx_, 0);
}
template <bool in>
struct Input_or_Output : std::conditional<in, Input, Output> {};
// format: TAG (FD|FD=\d+|FILE=...)
template <bool in, typename T_memptr>
static gpg_error_t IO_handler(assuan_context_t ctx_, char *line_, T_memptr which)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
char *binOpt = strstr(line_, "--binary");
if (binOpt && !in) {
/* Note there is also --armor and --base64 allowed but we don't need
* to parse those because they are default.
* We remove it here so that it is not parsed as an Option.*/
memset(binOpt, ' ', 8);
}
try {
/*const*/ std::map<std::string, std::string> options = upcase_option("FD", upcase_option("FILE", parse_commandline(line_)));
if (options.size() < 1 || options.size() > 2) {
throw gpg_error(GPG_ERR_ASS_SYNTAX);
}
std::shared_ptr< typename Input_or_Output<in>::type > io;
if (options.count("FD")) {
if (options.count("FILE")) {
throw gpg_error(GPG_ERR_CONFLICT);
}
assuan_fd_t fd = ASSUAN_INVALID_FD;
const std::string fdstr = options["FD"];
if (fdstr.empty()) {
if (const gpg_error_t err = assuan_receivefd(conn.ctx.get(), &fd)) {
throw err;
}
} else {
#if defined(Q_OS_WIN)
fd = (assuan_fd_t)std::stoi(fdstr);
#else
fd = std::stoi(fdstr);
#endif
}
io = Input_or_Output<in>::type::createFromPipeDevice(fd, in ? i18n("Message #%1", (conn.*which).size() + 1) : QString());
options.erase("FD");
} else if (options.count("FILE")) {
if (options.count("FD")) {
throw gpg_error(GPG_ERR_CONFLICT);
}
const QString filePath = QFile::decodeName(options["FILE"].c_str());
if (filePath.isEmpty()) {
throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Empty file path"));
}
const QFileInfo fi(filePath);
if (!fi.isAbsolute()) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed"));
}
if (!fi.isFile()) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only files are allowed in INPUT/OUTPUT FILE"));
} else {
io = Input_or_Output<in>::type::createFromFile(fi.absoluteFilePath(), true);
}
options.erase("FILE");
} else {
throw gpg_error(GPG_ERR_ASS_PARAMETER);
}
if (options.size()) {
throw gpg_error(GPG_ERR_UNKNOWN_OPTION);
}
(conn.*which).push_back(io);
if (binOpt && !in) {
auto out = reinterpret_cast <Output *>(io.get());
out->setBinaryOpt(true);
qCDebug(KLEOPATRA_LOG) << "Configured output for binary data";
}
qCDebug(KLEOPATRA_LOG) << "AssuanServerConnection: added" << io->label();
return assuan_process_done(conn.ctx.get(), 0);
} catch (const GpgME::Exception &e) {
return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().c_str());
} catch (const std::exception &) {
return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_ASS_SYNTAX));
} catch (const gpg_error_t &e) {
return assuan_process_done(conn.ctx.get(), e);
} catch (...) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), "unknown exception caught");
}
}
static gpg_error_t input_handler(assuan_context_t ctx, char *line)
{
return IO_handler<true>(ctx, line, &Private::inputs);
}
static gpg_error_t output_handler(assuan_context_t ctx, char *line)
{
return IO_handler<false>(ctx, line, &Private::outputs);
}
static gpg_error_t message_handler(assuan_context_t ctx, char *line)
{
return IO_handler<true>(ctx, line, &Private::messages);
}
static gpg_error_t file_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
try {
const QFileInfo fi(QFile::decodeName(hexdecode(line).c_str()));
if (!fi.isAbsolute()) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed"));
}
if (!fi.exists()) {
throw gpg_error(GPG_ERR_ENOENT);
}
if (!fi.isReadable() || (fi.isDir() && !fi.isExecutable())) {
throw gpg_error(GPG_ERR_EPERM);
}
conn.files.push_back(fi.absoluteFilePath());
return assuan_process_done(conn.ctx.get(), 0);
} catch (const Exception &e) {
return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().toUtf8().constData());
} catch (const gpg_error_t &e) {
return assuan_process_done(conn.ctx.get(), e);
} catch (...) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("unknown exception caught").toUtf8().constData());
}
}
static bool parse_informative(const char *&begin, GpgME::Protocol &protocol)
{
protocol = GpgME::UnknownProtocol;
bool informative = false;
const char *pos = begin;
while (true) {
while (*pos == ' ' || *pos == '\t') {
++pos;
}
if (qstrnicmp(pos, "--info", strlen("--info")) == 0) {
informative = true;
pos += strlen("--info");
if (*pos == '=') {
++pos;
break;
}
} else if (qstrnicmp(pos, "--protocol=", strlen("--protocol=")) == 0) {
pos += strlen("--protocol=");
if (qstrnicmp(pos, "OpenPGP", strlen("OpenPGP")) == 0) {
protocol = GpgME::OpenPGP;
pos += strlen("OpenPGP");
} else if (qstrnicmp(pos, "CMS", strlen("CMS")) == 0) {
protocol = GpgME::CMS;
pos += strlen("CMS");
} else {
;
}
} else if (qstrncmp(pos, "-- ", strlen("-- ")) == 0) {
pos += 3;
while (*pos == ' ' || *pos == '\t') {
++pos;
}
break;
} else {
break;
}
}
begin = pos;
return informative;
}
template <typename T_memptr, typename T_memptr2>
static gpg_error_t recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false)
{
Q_ASSERT(assuan_get_pointer(ctx));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx));
if (!line || !*line) {
return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG));
}
const char *begin = line;
const char *const end = begin + qstrlen(line);
GpgME::Protocol proto = GpgME::UnknownProtocol;
const bool informative = parse_informative(begin, proto);
if (!(conn.*mp).empty() && informative != (conn.*info))
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_CONFLICT),
i18n("Cannot mix --info with non-info SENDER or RECIPIENT").toUtf8().constData());
KMime::Types::Mailbox mb;
if (!KMime::HeaderParsing::parseMailbox(begin, end, mb))
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG),
i18n("Argument is not a valid RFC-2822 mailbox").toUtf8().constData());
if (begin != end)
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG),
i18n("Garbage after valid RFC-2822 mailbox detected").toUtf8().constData());
(conn.*info) = informative;
(conn.*mp).push_back(mb);
const QString email = mb.addrSpec().asString();
(void)assuan_write_line(conn.ctx.get(), qPrintable(QString::asprintf("# ok, parsed as \"%s\"", qPrintable(email))));
if (sender && !informative) {
return AssuanCommandFactory::_handle(conn.ctx.get(), line, "PREP_SIGN");
} else {
return assuan_process_done(ctx, 0);
}
}
static gpg_error_t recipient_handler(assuan_context_t ctx, char *line)
{
return recipient_sender_handler(&Private::recipients, &Private::informativeRecipients, ctx, line);
}
static gpg_error_t sender_handler(assuan_context_t ctx, char *line)
{
return recipient_sender_handler(&Private::senders, &Private::informativeSenders, ctx, line, true);
}
QByteArray dumpOptions() const
{
QByteArray result;
for (auto it = options.begin(), end = options.end(); it != end; ++it) {
result += it->first.c_str() + it->second.toString().toUtf8() + '\n';
}
return result;
}
static QByteArray dumpStringList(const QStringList &sl)
{
return sl.join(QLatin1Char('\n')).toUtf8();
}
template <typename T_container>
static QByteArray dumpStringList(const T_container &c)
{
QStringList sl;
std::copy(c.begin(), c.end(), std::back_inserter(sl));
return dumpStringList(sl);
}
template <typename T_container>
static QByteArray dumpMailboxes(const T_container &c)
{
QStringList sl;
std::transform(c.begin(), c.end(),
std::back_inserter(sl),
[](typename T_container::const_reference val) {
return val.prettyAddress();
});
return dumpStringList(sl);
}
QByteArray dumpSenders() const
{
return dumpMailboxes(senders);
}
QByteArray dumpRecipients() const
{
return dumpMailboxes(recipients);
}
QByteArray dumpMementos() const
{
QByteArray result;
for (auto it = mementos.begin(), end = mementos.end(); it != end; ++it) {
char buf[2 + 2 * sizeof(void *) + 2];
sprintf(buf, "0x%p\n", (void *)it->second.get());
buf[sizeof(buf) - 1] = '\0';
result += it->first + QByteArray::fromRawData(buf, sizeof buf);
}
return result;
}
QByteArray dumpFiles() const
{
QStringList rv;
rv.reserve(files.size());
std::copy(files.cbegin(), files.cend(), std::back_inserter(rv));
return dumpStringList(rv);
}
void cleanup();
void reset()
{
options.clear();
senders.clear();
informativeSenders = false;
recipients.clear();
informativeRecipients = false;
sessionTitle.clear();
sessionId = 0;
mementos.clear();
files.clear();
std::for_each(inputs.begin(), inputs.end(), std::mem_fn(&Input::finalize));
inputs.clear();
std::for_each(outputs.begin(), outputs.end(), std::mem_fn(&Output::finalize));
outputs.clear();
std::for_each(messages.begin(), messages.end(), std::mem_fn(&Input::finalize));
messages.clear();
bias = GpgME::UnknownProtocol;
}
assuan_fd_t fd;
AssuanContext ctx;
bool closed : 1;
bool cryptoCommandsEnabled : 1;
bool commandWaitingForCryptoCommandsEnabled : 1;
bool currentCommandIsNohup : 1;
bool informativeSenders; // address taken, so no : 1
bool informativeRecipients; // address taken, so no : 1
GpgME::Protocol bias;
QString sessionTitle;
unsigned int sessionId;
std::vector< std::shared_ptr<QSocketNotifier> > notifiers;
std::vector< std::shared_ptr<AssuanCommandFactory> > factories; // sorted: _detail::ByName<std::less>
std::shared_ptr<AssuanCommand> currentCommand;
std::vector< std::shared_ptr<AssuanCommand> > nohupedCommands;
std::map<std::string, QVariant> options;
std::vector<KMime::Types::Mailbox> senders, recipients;
std::vector< std::shared_ptr<Input> > inputs, messages;
std::vector< std::shared_ptr<Output> > outputs;
std::vector<QString> files;
std::map< QByteArray, std::shared_ptr<AssuanCommand::Memento> > mementos;
};
void AssuanServerConnection::Private::cleanup()
{
Q_ASSERT(nohupedCommands.empty());
reset();
currentCommand.reset();
currentCommandIsNohup = false;
commandWaitingForCryptoCommandsEnabled = false;
notifiers.clear();
ctx.reset();
fd = ASSUAN_INVALID_FD;
}
AssuanServerConnection::Private::Private(assuan_fd_t fd_, const std::vector< std::shared_ptr<AssuanCommandFactory> > &factories_, AssuanServerConnection *qq)
: QObject(),
q(qq),
fd(fd_),
closed(false),
cryptoCommandsEnabled(false),
commandWaitingForCryptoCommandsEnabled(false),
currentCommandIsNohup(false),
informativeSenders(false),
informativeRecipients(false),
bias(GpgME::UnknownProtocol),
sessionId(0),
factories(factories_)
{
#ifdef __GLIBCXX__
Q_ASSERT(__gnu_cxx::is_sorted(factories_.begin(), factories_.end(), _detail::ByName<std::less>()));
#endif
if (fd == ASSUAN_INVALID_FD) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), "pre-assuan_init_socket_server_ext");
}
{
assuan_context_t naked_ctx = nullptr;
if (const gpg_error_t err = assuan_new(&naked_ctx)) {
throw Exception(err, "assuan_new");
}
ctx.reset(naked_ctx);
}
if (const gpg_error_t err = assuan_init_socket_server(ctx.get(), fd, INIT_SOCKET_FLAGS))
throw Exception(err, "assuan_init_socket_server_ext");
// for callbacks, associate the context with this connection:
assuan_set_pointer(ctx.get(), this);
FILE *const logFile = Log::instance()->logFile();
assuan_set_log_stream(ctx.get(), logFile ? logFile : stderr);
// register FDs with the event loop:
assuan_fd_t fds[MAX_ACTIVE_FDS];
const int numFDs = assuan_get_active_fds(ctx.get(), FOR_READING, fds, MAX_ACTIVE_FDS);
Q_ASSERT(numFDs != -1); // == 1
if (!numFDs || fds[0] != fd) {
const std::shared_ptr<QSocketNotifier> sn(new QSocketNotifier((intptr_t)fd, QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater));
connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity);
notifiers.push_back(sn);
}
notifiers.reserve(notifiers.size() + numFDs);
for (int i = 0; i < numFDs; ++i) {
const std::shared_ptr<QSocketNotifier> sn(new QSocketNotifier((intptr_t)fds[i], QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater));
connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity);
notifiers.push_back(sn);
}
// register our INPUT/OUTPUT/MESSGAE/FILE handlers:
if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler, ""))
throw Exception(err, "register \"INPUT\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler, ""))
throw Exception(err, "register \"MESSAGE\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler, ""))
throw Exception(err, "register \"OUTPUT\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler, ""))
throw Exception(err, "register \"FILE\" handler");
// register user-defined commands:
for (std::shared_ptr<AssuanCommandFactory> fac : std::as_const(factories))
if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler(), ""))
throw Exception(err, std::string("register \"") + fac->name() + "\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler, ""))
throw Exception(err, "register \"GETINFO\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler, ""))
throw Exception(err, "register \"START_KEYMANAGER\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler, ""))
throw Exception(err, "register \"START_CONFDIALOG\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler, ""))
throw Exception(err, "register \"RECIPIENT\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler, ""))
throw Exception(err, "register \"SENDER\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler, ""))
throw Exception(err, "register \"SESSION\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler, ""))
throw Exception(err, "register \"CAPABILITIES\" handler");
assuan_set_hello_line(ctx.get(), "GPG UI server (Kleopatra/" KLEOPATRA_VERSION_STRING ") ready to serve");
//assuan_set_hello_line( ctx.get(), GPG UI server (qApp->applicationName() + " v" + kapp->applicationVersion() + "ready to serve" )
// some notifiers we're interested in:
if (const gpg_error_t err = assuan_register_reset_notify(ctx.get(), reset_handler)) {
throw Exception(err, "register reset notify");
}
if (const gpg_error_t err = assuan_register_option_handler(ctx.get(), option_handler)) {
throw Exception(err, "register option handler");
}
// and last, we need to call assuan_accept, which doesn't block
// (d/t INIT_SOCKET_FLAGS), but performs vital connection
// establishing handling:
if (const gpg_error_t err = assuan_accept(ctx.get())) {
throw Exception(err, "assuan_accept");
}
}
AssuanServerConnection::Private::~Private()
{
cleanup();
}
AssuanServerConnection::AssuanServerConnection(assuan_fd_t fd, const std::vector< std::shared_ptr<AssuanCommandFactory> > &factories, QObject *p)
: QObject(p), d(new Private(fd, factories, this))
{
}
AssuanServerConnection::~AssuanServerConnection() {}
void AssuanServerConnection::enableCryptoCommands(bool on)
{
if (on == d->cryptoCommandsEnabled) {
return;
}
d->cryptoCommandsEnabled = on;
if (d->commandWaitingForCryptoCommandsEnabled) {
QTimer::singleShot(0, d.get(), &Private::startCommandBottomHalf);
}
}
//
//
// AssuanCommand:
//
//
namespace Kleo
{
class InquiryHandler : public QObject
{
Q_OBJECT
public:
explicit InquiryHandler(const char *keyword_, QObject *p = nullptr)
: QObject(p),
keyword(keyword_)
{
}
static gpg_error_t handler(void *cb_data, gpg_error_t rc, unsigned char *buffer, size_t buflen)
{
Q_ASSERT(cb_data);
auto this_ = static_cast<InquiryHandler *>(cb_data);
Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast<const char *>(buffer), buflen), this_->keyword);
std::free(buffer);
delete this_;
return 0;
}
private:
const char *keyword;
Q_SIGNALS:
void signal(int rc, const QByteArray &data, const QByteArray &keyword);
};
} // namespace Kleo
class AssuanCommand::Private
{
public:
Private()
: informativeRecipients(false),
informativeSenders(false),
bias(GpgME::UnknownProtocol),
done(false),
nohup(false)
{
}
std::map<std::string, QVariant> options;
std::vector< std::shared_ptr<Input> > inputs, messages;
std::vector< std::shared_ptr<Output> > outputs;
std::vector<QString> files;
std::vector<KMime::Types::Mailbox> recipients, senders;
bool informativeRecipients, informativeSenders;
GpgME::Protocol bias;
QString sessionTitle;
unsigned int sessionId;
QByteArray utf8ErrorKeepAlive;
AssuanContext ctx;
bool done;
bool nohup;
};
AssuanCommand::AssuanCommand()
: d(new Private)
{
}
AssuanCommand::~AssuanCommand()
{
}
int AssuanCommand::start()
{
try {
if (const int err = doStart())
if (!d->done) {
done(err);
}
return 0;
} catch (const Exception &e) {
if (!d->done) {
done(e.error_code(), e.message());
}
return 0;
} catch (const GpgME::Exception &e) {
if (!d->done) {
done(e.error(), QString::fromLocal8Bit(e.message().c_str()));
}
return 0;
} catch (const std::exception &e) {
if (!d->done) {
done(makeError(GPG_ERR_INTERNAL), i18n("Caught unexpected exception: %1", QString::fromLocal8Bit(e.what())));
}
return 0;
} catch (...) {
if (!d->done) {
done(makeError(GPG_ERR_INTERNAL), i18n("Caught unknown exception - please report this error to the developers."));
}
return 0;
}
}
void AssuanCommand::canceled()
{
d->done = true;
doCanceled();
}
// static
int AssuanCommand::makeError(int code)
{
return makeGnuPGError(code);
}
bool AssuanCommand::hasOption(const char *opt) const
{
return d->options.count(opt);
}
QVariant AssuanCommand::option(const char *opt) const
{
const auto it = d->options.find(opt);
if (it == d->options.end()) {
return QVariant();
} else {
return it->second;
}
}
const std::map<std::string, QVariant> &AssuanCommand::options() const
{
return d->options;
}
namespace
{
template <typename U, typename V>
std::vector<U> keys(const std::map<U, V> &map)
{
std::vector<U> result;
result.resize(map.size());
for (typename std::map<U, V>::const_iterator it = map.begin(), end = map.end(); it != end; ++it) {
result.push_back(it->first);
}
return result;
}
}
const std::map< QByteArray, std::shared_ptr<AssuanCommand::Memento> > &AssuanCommand::mementos() const
{
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
const AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
return conn.mementos;
}
bool AssuanCommand::hasMemento(const QByteArray &tag) const
{
if (const unsigned int id = sessionId()) {
return SessionDataHandler::instance()->sessionData(id)->mementos.count(tag) || mementos().count(tag);
} else {
return mementos().count(tag);
}
}
std::shared_ptr<AssuanCommand::Memento> AssuanCommand::memento(const QByteArray &tag) const
{
if (const unsigned int id = sessionId()) {
const std::shared_ptr<SessionDataHandler> sdh = SessionDataHandler::instance();
const std::shared_ptr<SessionData> sd = sdh->sessionData(id);
const auto it = sd->mementos.find(tag);
if (it != sd->mementos.end()) {
return it->second;
}
}
const auto it = mementos().find(tag);
if (it == mementos().end()) {
return std::shared_ptr<Memento>();
} else {
return it->second;
}
}
QByteArray AssuanCommand::registerMemento(const std::shared_ptr<Memento> &mem)
{
const QByteArray tag = QByteArray::number(reinterpret_cast<qulonglong>(mem.get()), 36);
return registerMemento(tag, mem);
}
QByteArray AssuanCommand::registerMemento(const QByteArray &tag, const std::shared_ptr<Memento> &mem)
{
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
if (const unsigned int id = sessionId()) {
SessionDataHandler::instance()->sessionData(id)->mementos[tag] = mem;
} else {
conn.mementos[tag] = mem;
}
return tag;
}
void AssuanCommand::removeMemento(const QByteArray &tag)
{
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
conn.mementos.erase(tag);
if (const unsigned int id = sessionId()) {
SessionDataHandler::instance()->sessionData(id)->mementos.erase(tag);
}
}
const std::vector< std::shared_ptr<Input> > &AssuanCommand::inputs() const
{
return d->inputs;
}
const std::vector< std::shared_ptr<Input> > &AssuanCommand::messages() const
{
return d->messages;
}
const std::vector< std::shared_ptr<Output> > &AssuanCommand::outputs() const
{
return d->outputs;
}
QStringList AssuanCommand::fileNames() const
{
QStringList rv;
rv.reserve(d->files.size());
std::copy(d->files.cbegin(), d->files.cend(), std::back_inserter(rv));
return rv;
}
unsigned int AssuanCommand::numFiles() const
{
return d->files.size();
}
void AssuanCommand::sendStatus(const char *keyword, const QString &text)
{
sendStatusEncoded(keyword, text.toUtf8().constData());
}
void AssuanCommand::sendStatusEncoded(const char *keyword, const std::string &text)
{
if (d->nohup) {
return;
}
if (const int err = assuan_write_status(d->ctx.get(), keyword, text.c_str())) {
throw Exception(err, i18n("Cannot send \"%1\" status", QString::fromLatin1(keyword)));
}
}
void AssuanCommand::sendData(const QByteArray &data, bool moreToCome)
{
if (d->nohup) {
return;
}
if (const gpg_error_t err = assuan_send_data(d->ctx.get(), data.constData(), data.size())) {
throw Exception(err, i18n("Cannot send data"));
}
if (!moreToCome)
if (const gpg_error_t err = assuan_send_data(d->ctx.get(), nullptr, 0)) { // flush
throw Exception(err, i18n("Cannot flush data"));
}
}
int AssuanCommand::inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize)
{
Q_ASSERT(keyword);
Q_ASSERT(receiver);
Q_ASSERT(slot);
if (d->nohup) {
return makeError(GPG_ERR_INV_OP);
}
std::unique_ptr<InquiryHandler> ih(new InquiryHandler(keyword, receiver));
receiver->connect(ih.get(), SIGNAL(signal(int,QByteArray,QByteArray)), slot);
if (const gpg_error_t err = assuan_inquire_ext(d->ctx.get(), keyword,
maxSize, InquiryHandler::handler, ih.get())) {
return err;
}
ih.release();
return 0;
}
void AssuanCommand::done(const GpgME::Error &err, const QString &details)
{
if (d->ctx && !d->done && !details.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "Error: " << details;
d->utf8ErrorKeepAlive = details.toUtf8();
if (!d->nohup) {
assuan_set_error(d->ctx.get(), err.encodedError(), d->utf8ErrorKeepAlive.constData());
}
}
done(err);
}
void AssuanCommand::done(const GpgME::Error &err)
{
if (!d->ctx) {
- qCDebug(KLEOPATRA_LOG) << err.asString() << ": called with NULL ctx.";
+ qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called with NULL ctx.";
return;
}
if (d->done) {
- qCDebug(KLEOPATRA_LOG) << err.asString() << ": called twice!";
+ qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called twice!";
return;
}
d->done = true;
std::for_each(d->messages.begin(), d->messages.end(), std::mem_fn(&Input::finalize));
std::for_each(d->inputs.begin(), d->inputs.end(), std::mem_fn(&Input::finalize));
std::for_each(d->outputs.begin(), d->outputs.end(), std::mem_fn(&Output::finalize));
d->messages.clear();
d->inputs.clear();
d->outputs.clear();
d->files.clear();
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
if (d->nohup) {
conn.nohupDone(this);
return;
}
const gpg_error_t rc = assuan_process_done(d->ctx.get(), err.encodedError());
if (gpg_err_code(rc) != GPG_ERR_NO_ERROR)
qFatal("AssuanCommand::done: assuan_process_done returned error %d (%s)",
static_cast<int>(rc), gpg_strerror(rc));
d->utf8ErrorKeepAlive.clear();
conn.commandDone(this);
}
void AssuanCommand::setNohup(bool nohup)
{
d->nohup = nohup;
}
bool AssuanCommand::isNohup() const
{
return d->nohup;
}
bool AssuanCommand::isDone() const
{
return d->done;
}
QString AssuanCommand::sessionTitle() const
{
return d->sessionTitle;
}
unsigned int AssuanCommand::sessionId() const
{
return d->sessionId;
}
bool AssuanCommand::informativeSenders() const
{
return d->informativeSenders;
}
bool AssuanCommand::informativeRecipients() const
{
return d->informativeRecipients;
}
const std::vector<KMime::Types::Mailbox> &AssuanCommand::recipients() const
{
return d->recipients;
}
const std::vector<KMime::Types::Mailbox> &AssuanCommand::senders() const
{
return d->senders;
}
gpg_error_t AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName)
{
Q_ASSERT(assuan_get_pointer(ctx));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx));
try {
const auto it
= std::lower_bound(conn.factories.begin(), conn.factories.end(), commandName, _detail::ByName<std::less>());
kleo_assert(it != conn.factories.end());
kleo_assert(*it);
kleo_assert(qstricmp((*it)->name(), commandName) == 0);
const std::shared_ptr<AssuanCommand> cmd = (*it)->create();
kleo_assert(cmd);
cmd->d->ctx = conn.ctx;
cmd->d->options = conn.options;
cmd->d->inputs.swap(conn.inputs); kleo_assert(conn.inputs.empty());
cmd->d->messages.swap(conn.messages); kleo_assert(conn.messages.empty());
cmd->d->outputs.swap(conn.outputs); kleo_assert(conn.outputs.empty());
cmd->d->files.swap(conn.files); kleo_assert(conn.files.empty());
cmd->d->senders.swap(conn.senders); kleo_assert(conn.senders.empty());
cmd->d->recipients.swap(conn.recipients); kleo_assert(conn.recipients.empty());
cmd->d->informativeRecipients = conn.informativeRecipients;
cmd->d->informativeSenders = conn.informativeSenders;
cmd->d->bias = conn.bias;
cmd->d->sessionTitle = conn.sessionTitle;
cmd->d->sessionId = conn.sessionId;
const std::map<std::string, std::string> cmdline_options = parse_commandline(line);
for (auto it = cmdline_options.begin(), end = cmdline_options.end(); it != end; ++it) {
cmd->d->options[it->first] = QString::fromUtf8(it->second.c_str());
}
bool nohup = false;
if (cmd->d->options.count("nohup")) {
if (!cmd->d->options["nohup"].toString().isEmpty()) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_ASS_PARAMETER), "--nohup takes no argument");
}
nohup = true;
cmd->d->options.erase("nohup");
}
conn.currentCommand = cmd;
conn.currentCommandIsNohup = nohup;
QTimer::singleShot(0, &conn, &AssuanServerConnection::Private::startCommandBottomHalf);
return 0;
} catch (const Exception &e) {
return assuan_process_done_msg(conn.ctx.get(), e.error_code(), e.message());
} catch (const std::exception &e) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what());
} catch (...) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception"));
}
}
int AssuanServerConnection::Private::startCommandBottomHalf()
{
commandWaitingForCryptoCommandsEnabled = currentCommand && !cryptoCommandsEnabled;
if (!cryptoCommandsEnabled) {
return 0;
}
const std::shared_ptr<AssuanCommand> cmd = currentCommand;
if (!cmd) {
return 0;
}
currentCommand.reset();
const bool nohup = currentCommandIsNohup;
currentCommandIsNohup = false;
try {
if (const int err = cmd->start()) {
if (cmd->isDone()) {
return err;
} else {
return assuan_process_done(ctx.get(), err);
}
}
if (cmd->isDone()) {
return 0;
}
if (nohup) {
cmd->setNohup(true);
nohupedCommands.push_back(cmd);
return assuan_process_done_msg(ctx.get(), 0, "Command put in the background to continue executing after connection end.");
} else {
currentCommand = cmd;
return 0;
}
} catch (const Exception &e) {
return assuan_process_done_msg(ctx.get(), e.error_code(), e.message());
} catch (const std::exception &e) {
return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what());
} catch (...) {
return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception"));
}
}
//
//
// AssuanCommand convenience methods
//
//
/*!
Checks the \c --mode parameter.
\returns The parameter as an AssuanCommand::Mode enum value.
If no \c --mode was given, or it's value wasn't recognized, throws
an Kleo::Exception.
*/
AssuanCommand::Mode AssuanCommand::checkMode() const
{
if (!hasOption("mode")) {
throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --mode option missing"));
}
const QString modeString = option("mode").toString().toLower();
if (modeString == QLatin1String("filemanager")) {
return FileManager;
}
if (modeString == QLatin1String("email")) {
return EMail;
}
throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid mode: \"%1\"", modeString));
}
/*!
Checks the \c --protocol parameter.
\returns The parameter as a GpgME::Protocol enum value.
If \c --protocol was given, but has an invalid value, throws an
Kleo::Exception.
If no \c --protocol was given, checks the connection bias, if
available, otherwise, in FileManager mode, returns
GpgME::UnknownProtocol, but if \a mode == \c EMail, throws an
Kleo::Exception instead.
*/
GpgME::Protocol AssuanCommand::checkProtocol(Mode mode, int options) const
{
if (!hasOption("protocol"))
if (d->bias != GpgME::UnknownProtocol) {
return d->bias;
} else if (mode == AssuanCommand::EMail && (options & AllowProtocolMissing) == 0) {
throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --protocol option missing"));
} else {
return GpgME::UnknownProtocol;
}
else if (mode == AssuanCommand::FileManager) {
throw Exception(makeError(GPG_ERR_INV_FLAG), i18n("--protocol is not allowed here"));
}
const QString protocolString = option("protocol").toString().toLower();
if (protocolString == QLatin1String("openpgp")) {
return GpgME::OpenPGP;
}
if (protocolString == QLatin1String("cms")) {
return GpgME::CMS;
}
throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid protocol \"%1\"", protocolString));
}
void AssuanCommand::doApplyWindowID(QWidget *widget) const
{
if (!widget || !hasOption("window-id")) {
return;
}
apply_window_id(widget, option("window-id").toString());
}
WId AssuanCommand::parentWId() const
{
return wid_from_string(option("window-id").toString());
}
#include "assuanserverconnection.moc"
diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp
index 1b9f5c12c..bd83bb50e 100644
--- a/src/view/pgpcardwidget.cpp
+++ b/src/view/pgpcardwidget.cpp
@@ -1,580 +1,580 @@
/* view/pgpcardwiget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "pgpcardwidget.h"
#include "openpgpkeycardwidget.h"
#include "kleopatra_debug.h"
#include "commands/createcsrforcardkeycommand.h"
#include "commands/createopenpgpkeyfromcardkeyscommand.h"
#include "commands/openpgpgeneratecardkeycommand.h"
#include "smartcard/algorithminfo.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/readerstatus.h"
#include "dialogs/gencardkeydialog.h"
#include <Libkleo/Compliance>
#include <Libkleo/GnuPG>
#include <QProgressDialog>
#include <QThread>
#include <QScrollArea>
#include <QInputDialog>
#include <QFileDialog>
#include <QFileInfo>
#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <Libkleo/KeyCache>
#include <Libkleo/Formatting>
#include <gpgme++/data.h>
#include <gpgme++/context.h>
#include <QGpgME/DataProvider>
#include <gpgme++/gpggencardkeyinteractor.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
static QDebug operator<<(QDebug s, const std::string &string)
{
return s << QString::fromStdString(string);
}
#endif
namespace {
class GenKeyThread: public QThread
{
Q_OBJECT
public:
explicit GenKeyThread(const GenCardKeyDialog::KeyParams &params, const std::string &serial):
mSerial(serial),
mParams(params)
{
}
GpgME::Error error()
{
return mErr;
}
std::string bkpFile()
{
return mBkpFile;
}
protected:
void run() override {
// the index of the curves in this list has to match the enum values
// minus 1 of GpgGenCardKeyInteractor::Curve
static const std::vector<std::string> curves = {
"curve25519",
#if GPGMEPP_SUPPORTS_SET_CURVE
"curve448",
"nistp256",
"nistp384",
"nistp521",
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
"secp256k1", // keep it, even if we don't support it in Kleopatra
#endif
};
auto ei = std::make_unique<GpgME::GpgGenCardKeyInteractor>(mSerial);
if (mParams.algorithm.starts_with("rsa")) {
ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA);
ei->setKeySize(QByteArray::fromStdString(mParams.algorithm.substr(3)).toInt());
} else {
ei->setAlgo(GpgME::GpgGenCardKeyInteractor::ECC);
const auto curveIt = std::find(curves.cbegin(), curves.cend(), mParams.algorithm);
if (curveIt != curves.end()) {
#if GPGMEPP_SUPPORTS_SET_CURVE
ei->setCurve(static_cast<GpgME::GpgGenCardKeyInteractor::Curve>(curveIt - curves.cbegin() + 1));
#endif
} else {
qCWarning(KLEOPATRA_LOG) << this << __func__ << "Invalid curve name:" << mParams.algorithm;
mErr = GpgME::Error::fromCode(GPG_ERR_INV_VALUE);
return;
}
}
ei->setNameUtf8(mParams.name.toStdString());
ei->setEmailUtf8(mParams.email.toStdString());
ei->setDoBackup(mParams.backup);
const auto ctx = std::shared_ptr<GpgME::Context> (GpgME::Context::createForProtocol(GpgME::OpenPGP));
ctx->setFlag("extended-edit", "1"); // we want to be able to select all curves
QGpgME::QByteArrayDataProvider dp;
GpgME::Data data(&dp);
mErr = ctx->cardEdit(GpgME::Key(), std::move(ei), data);
mBkpFile = static_cast<GpgME::GpgGenCardKeyInteractor*>(ctx->lastCardEditInteractor())->backupFileName();
}
private:
GpgME::Error mErr;
std::string mSerial;
GenCardKeyDialog::KeyParams mParams;
std::string mBkpFile;
};
} // Namespace
PGPCardWidget::PGPCardWidget(QWidget *parent):
QWidget(parent),
mSerialNumber(new QLabel(this)),
mCardHolderLabel(new QLabel(this)),
mVersionLabel(new QLabel(this)),
mUrlLabel(new QLabel(this)),
mCardIsEmpty(false)
{
// Set up the scroll area
auto myLayout = new QVBoxLayout(this);
myLayout->setContentsMargins(0, 0, 0, 0);
auto area = new QScrollArea;
area->setFrameShape(QFrame::NoFrame);
area->setWidgetResizable(true);
myLayout->addWidget(area);
auto areaWidget = new QWidget;
area->setWidget(areaWidget);
auto areaVLay = new QVBoxLayout(areaWidget);
auto cardInfoGrid = new QGridLayout;
{
int row = 0;
// Version and Serialnumber
cardInfoGrid->addWidget(mVersionLabel, row, 0, 1, 2);
mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
row++;
cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0);
cardInfoGrid->addWidget(mSerialNumber, row, 1);
mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction);
row++;
// Cardholder Row
cardInfoGrid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.",
"Cardholder:")), row, 0);
cardInfoGrid->addWidget(mCardHolderLabel, row, 1);
mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
{
auto button = new QPushButton;
button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit")));
button->setAccessibleName(i18nc("@action:button", "Edit"));
button->setToolTip(i18n("Change"));
cardInfoGrid->addWidget(button, row, 2);
connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested);
}
row++;
// URL Row
cardInfoGrid->addWidget(new QLabel(i18nc("The URL under which a public key that "
"corresponds to a smartcard can be downloaded",
"Pubkey URL:")), row, 0);
cardInfoGrid->addWidget(mUrlLabel, row, 1);
mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
{
auto button = new QPushButton;
button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit")));
button->setAccessibleName(i18nc("@action:button", "Edit"));
button->setToolTip(i18n("Change"));
cardInfoGrid->addWidget(button, row, 2);
connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested);
}
cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1);
}
areaVLay->addLayout(cardInfoGrid);
areaVLay->addWidget(new KSeparator(Qt::Horizontal));
// The keys
areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:"))));
mKeysWidget = new OpenPGPKeyCardWidget{this};
areaVLay->addWidget(mKeysWidget);
connect(mKeysWidget, &OpenPGPKeyCardWidget::createCSRRequested, this, &PGPCardWidget::createCSR);
connect(mKeysWidget, &OpenPGPKeyCardWidget::generateKeyRequested, this, &PGPCardWidget::generateKey);
areaVLay->addWidget(new KSeparator(Qt::Horizontal));
areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:"))));
auto actionLayout = new QHBoxLayout;
{
auto generateButton = new QPushButton(i18n("Generate New Keys"));
generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card."));
actionLayout->addWidget(generateButton);
connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested);
}
{
auto pinButton = new QPushButton(i18n("Change PIN"));
pinButton->setToolTip(i18n("Change the PIN required for using the keys on the smartcard."));
actionLayout->addWidget(pinButton);
connect(pinButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::pinKeyRef()); });
}
{
auto unblockButton = new QPushButton(i18n("Unblock Card"));
unblockButton->setToolTip(i18n("Unblock the smartcard and set a new PIN."));
actionLayout->addWidget(unblockButton);
connect(unblockButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef()); });
}
{
auto pukButton = new QPushButton(i18n("Change Admin PIN"));
pukButton->setToolTip(i18n("Change the PIN required for administrative operations."));
actionLayout->addWidget(pukButton);
connect(pukButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::adminPinKeyRef()); });
}
{
auto resetCodeButton = new QPushButton(i18n("Change Reset Code"));
resetCodeButton->setToolTip(i18n("Change the PIN required to unblock the smartcard and set a new PIN."));
actionLayout->addWidget(resetCodeButton);
connect(resetCodeButton, &QPushButton::clicked,
this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef(), ChangePinCommand::ResetMode); });
}
if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
mKeyForCardKeysButton = new QPushButton(this);
mKeyForCardKeysButton->setText(i18n("Create OpenPGP Key"));
mKeyForCardKeysButton->setToolTip(i18n("Create an OpenPGP key for the keys stored on the card."));
actionLayout->addWidget(mKeyForCardKeysButton);
connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PGPCardWidget::createKeyFromCardKeys);
}
actionLayout->addStretch(-1);
areaVLay->addLayout(actionLayout);
areaVLay->addStretch(1);
}
void PGPCardWidget::setCard(const OpenPGPCard *card)
{
const QString version = card->displayAppVersion();
mIs21 = card->appVersion() >= 0x0201;
const QString manufacturer = QString::fromStdString(card->manufacturer());
const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown");
mVersionLabel->setText(manufacturerIsUnknown ?
i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version) :
i18nc("First placeholder is manufacturer, second placeholder is a version number",
"%1 OpenPGP v%2 card", manufacturer, version));
mSerialNumber->setText(card->displaySerialNumber());
mRealSerial = card->serialNumber();
const auto holder = card->cardHolder();
const auto url = QString::fromStdString(card->pubkeyUrl());
mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder);
mUrl = url;
mUrlLabel->setText(url.isEmpty() ? i18n("not set") :
QStringLiteral("<a href=\"%1\">%1</a>").arg(url.toHtmlEscaped()));
mUrlLabel->setOpenExternalLinks(true);
mKeysWidget->update(card);
mCardIsEmpty = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty()
&& card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty()
&& card->keyFingerprint(OpenPGPCard::pgpAuthKeyRef()).empty();
if (mKeyForCardKeysButton) {
mKeyForCardKeysButton->setEnabled(card->hasSigningKey()
&& card->hasEncryptionKey()
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
}
}
void PGPCardWidget::doChangePin(const std::string &keyRef, ChangePinCommand::ChangePinMode mode)
{
auto cmd = new ChangePinCommand(mRealSerial, OpenPGPCard::AppName, this);
this->setEnabled(false);
connect(cmd, &ChangePinCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->setKeyRef(keyRef);
cmd->setMode(mode);
cmd->start();
}
void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg)
{
const GpgME::Error err = ReaderStatus::switchCardAndApp(mRealSerial, OpenPGPCard::AppName);
if (err) {
return;
}
const auto params = dlg->getKeyParams();
auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog);
progress->setAutoClose(true);
progress->setMinimumDuration(0);
progress->setMaximum(0);
progress->setMinimum(0);
progress->setModal(true);
progress->setCancelButton(nullptr);
progress->setWindowTitle(i18nc("@title:window", "Generating Keys"));
progress->setLabel(new QLabel(i18n("This may take several minutes...")));
auto workerThread = new GenKeyThread(params, mRealSerial);
connect(workerThread, &QThread::finished, this, [this, workerThread, progress] {
progress->accept();
progress->deleteLater();
genKeyDone(workerThread->error(), workerThread->bkpFile());
delete workerThread;
});
workerThread->start();
progress->exec();
}
void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup)
{
if (err) {
KMessageBox::error(this, i18nc("@info",
- "Failed to generate new key: %1", QString::fromLatin1(err.asString())));
+ "Failed to generate new key: %1", Formatting::errorAsString(err)));
return;
}
if (err.isCanceled()) {
return;
}
if (!backup.empty()) {
const auto bkpFile = QString::fromStdString(backup);
QFileInfo fi(bkpFile);
const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"),
fi.fileName(),
QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key")));
if (!target.isEmpty() && !QFile::copy(bkpFile, target)) {
KMessageBox::error(this, i18nc("@info",
"Failed to move backup. The backup key is still stored under: %1", bkpFile));
} else if (!target.isEmpty()) {
QFile::remove(bkpFile);
}
}
KMessageBox::information(this, i18nc("@info",
"Successfully generated a new key for this card."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
void PGPCardWidget::genkeyRequested()
{
const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
if (!pgpCard) {
KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
return;
}
if (!mCardIsEmpty) {
auto ret = KMessageBox::warningContinueCancel(this,
i18n("The existing keys on this card will be <b>deleted</b> "
"and replaced by new keys.") + QStringLiteral("<br/><br/>") +
i18n("It will no longer be possible to decrypt past communication "
"encrypted for the existing key."),
i18n("Secret Key Deletion"),
KStandardGuiItem::guiItem(KStandardGuiItem::Delete),
KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (ret != KMessageBox::Continue) {
return;
}
}
auto dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this);
#if GPGMEPP_SUPPORTS_SET_CURVE
dlg->setSupportedAlgorithms(pgpCard->supportedAlgorithms(), pgpCard->defaultAlgorithm());
#else
std::vector<AlgorithmInfo> algos = {
{ "rsa2048", i18nc("@info", "RSA 2048") },
{ "rsa3072", i18nc("@info", "RSA 3072") },
};
// There is probably a better way to check for capabilities
if (mIs21) {
algos.push_back({"rsa4096", i18nc("@info", "RSA 4096")});
}
const auto supportedAlgos = pgpCard->supportedAlgorithms();
if (std::any_of(supportedAlgos.begin(), supportedAlgos.end(), [](const auto &algo) {
return algo.id == "curve25519";
})) {
algos.push_back({"curve25519", i18nc("@info", "ECC (Curve25519)")});
}
dlg->setSupportedAlgorithms(algos, "rsa2048");
#endif
connect(dlg, &QDialog::accepted, this, [this, dlg] () {
doGenKey(dlg);
dlg->deleteLater();
});
dlg->setModal(true);
dlg->show();
}
void PGPCardWidget::changeNameRequested()
{
QString text = mCardHolderLabel->text();
while (true) {
bool ok = false;
text = QInputDialog::getText(this, i18n("Change cardholder"),
i18n("New name:"), QLineEdit::Normal,
text, &ok, Qt::WindowFlags(),
Qt::ImhLatinOnly);
if (!ok) {
return;
}
// Some additional restrictions imposed by gnupg
if (text.contains(QLatin1Char('<'))) {
KMessageBox::error(this, i18nc("@info",
"The \"<\" character may not be used."));
continue;
}
if (text.contains(QLatin1String(" "))) {
KMessageBox::error(this, i18nc("@info",
"Double spaces are not allowed"));
continue;
}
if (text.size() > 38) {
KMessageBox::error(this, i18nc("@info",
"The size of the name may not exceed 38 characters."));
}
break;
}
auto parts = text.split(QLatin1Char(' '));
const auto lastName = parts.takeLast();
const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<'));
const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
if (!pgpCard) {
KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
return;
}
const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8();
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) {
changeNameResult(err);
});
}
void PGPCardWidget::changeNameResult(const GpgME::Error &err)
{
if (err) {
KMessageBox::error(this, i18nc("@info",
- "Name change failed: %1", QString::fromLatin1(err.asString())));
+ "Name change failed: %1", Formatting::errorAsString(err)));
return;
}
if (!err.isCanceled()) {
KMessageBox::information(this, i18nc("@info",
"Name successfully changed."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
}
void PGPCardWidget::changeUrlRequested()
{
QString text = mUrl;
while (true) {
bool ok = false;
text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"),
i18n("New pubkey URL:"), QLineEdit::Normal,
text, &ok, Qt::WindowFlags(),
Qt::ImhLatinOnly);
if (!ok) {
return;
}
// Some additional restrictions imposed by gnupg
if (text.size() > 254) {
KMessageBox::error(this, i18nc("@info",
"The size of the URL may not exceed 254 characters."));
}
break;
}
const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
if (!pgpCard) {
KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
return;
}
const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8();
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) {
changeUrlResult(err);
});
}
void PGPCardWidget::changeUrlResult(const GpgME::Error &err)
{
if (err) {
KMessageBox::error(this, i18nc("@info",
- "URL change failed: %1", QString::fromLatin1(err.asString())));
+ "URL change failed: %1", Formatting::errorAsString(err)));
return;
}
if (!err.isCanceled()) {
KMessageBox::information(this, i18nc("@info",
"URL successfully changed."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
}
void PGPCardWidget::createKeyFromCardKeys()
{
auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mRealSerial, OpenPGPCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PGPCardWidget::createCSR(const std::string &keyref)
{
auto cmd = new CreateCSRForCardKeyCommand(keyref, mRealSerial, OpenPGPCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateCSRForCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PGPCardWidget::generateKey(const std::string &keyref)
{
auto cmd = new OpenPGPGenerateCardKeyCommand(keyref, mRealSerial, this);
this->setEnabled(false);
connect(cmd, &OpenPGPGenerateCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
#include "pgpcardwidget.moc"

File Metadata

Mime Type
text/x-diff
Expires
Mon, May 12, 6:38 PM (1 d, 18 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
2a/db/6e8636745825dce2c3c03caa4142

Event Timeline