Page MenuHome GnuPG

No OneTemporary

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2928ce563..6d81a5028 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,245 +1,245 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(RELEASE_SERVICE_VERSION_MAJOR "25")
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 "4")
set(KLEOPATRA_VERSION_MINOR "0")
set(KLEOPATRA_VERSION_MICRO "0")
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()
if (NOT KLEOPATRA_APPLICATION_NAME)
# This is used to allow multiple flavors of Kleopatra to run at the same time on Windows
set(KLEOPATRA_APPLICATION_NAME "kleopatra")
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 "6.11.0")
set(KIDENTITYMANAGEMENT_VERSION "6.4.40")
set(KMAILTRANSPORT_VERSION "6.4.40")
set(AKONADI_MIME_VERSION "6.4.40")
set(KMIME_VERSION "6.4.40")
-set(LIBKLEO_VERSION "6.4.40")
+set(LIBKLEO_VERSION "6.4.41")
set(QT_REQUIRED_VERSION "6.7.0")
set(MIMETREEPARSER_VERSION "6.4.40")
set(GPGME_REQUIRED_VERSION "1.23.2")
set(LIBASSUAN_REQUIRED_VERSION "2.4.2")
set(GPG_ERROR_REQUIRED_VERSION "1.36")
if (WIN32)
set(KF6_WANT_VERSION ${KF_MIN_VERSION})
set(KMIME_WANT_VERSION ${KMIME_VERSION})
else ()
set(KF6_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 ${KF6_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(CheckFunctionExists)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMFeatureSummary)
include(KDEClangFormat)
include(KDEGitCommitHooks)
# Find KF6 packages
find_package(KF6 ${KF6_WANT_VERSION}
REQUIRED COMPONENTS
Codecs
ColorScheme
Config
CoreAddons
Crash
GuiAddons
I18n
IconThemes
ItemModels
KIO
WidgetsAddons
WindowSystem
XmlGui
OPTIONAL_COMPONENTS
DocTools
)
set_package_properties(KF6DocTools 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(KF6DBusAddons ${KF6_WANT_VERSION} CONFIG)
set_package_properties(KF6DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus"
PURPOSE "DBus session integration"
TYPE OPTIONAL)
else()
find_package(KF6DBusAddons ${KF6_WANT_VERSION} CONFIG REQUIRED)
set(_kleopatra_dbusaddons_libs KF6::DBusAddons)
endif()
set(HAVE_QDBUS ${Qt6DBus_FOUND})
find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
set(QGPGME_NAME "QGpgmeQt6")
find_package(${QGPGME_NAME} ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.24.0")
set(QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO 1)
set(QGPGME_IMPORT_JOB_SUPPORTS_IMPORT_OPTIONS 1)
set(QGPGME_SUPPORTS_PROCESS_ALL_SIGNATURES 1)
endif()
find_package(KPim6Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KPim6Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED)
find_package(KPim6IdentityManagementCore ${KIDENTITYMANAGEMENT_VERSION} CONFIG)
find_package(KPim6MailTransport ${KMAILTRANSPORT_VERSION} CONFIG)
find_package(KPim6AkonadiMime ${AKONADI_MIME_VERSION} CONFIG)
find_package(KPim6MimeTreeParserWidgets ${MIMETREEPARSER_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${LIBKLEO_MODULE_PATH} ${CMAKE_MODULE_PATH})
find_package(Qt6 ${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(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.in ${CMAKE_CURRENT_BINARY_DIR}/version-kleopatra.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kleopatra.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-kleopatra.h)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
add_definitions(-DQT_NO_CONTEXTLESS_CONNECT)
ecm_set_disabled_deprecation_versions(QT 6.9.0 KF 6.12.0)
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(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(KF6DocTools_FOUND)
kdoctools_install(po)
add_subdirectory(doc)
endif()
ecm_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})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/src/commands/certificatetopivcardcommand.cpp b/src/commands/certificatetopivcardcommand.cpp
index 51b3caf8a..fcd55188a 100644
--- a/src/commands/certificatetopivcardcommand.cpp
+++ b/src/commands/certificatetopivcardcommand.cpp
@@ -1,273 +1,272 @@
/*
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 "smartcard/utils.h"
#include "utils/writecertassuantransaction.h"
#include <Libkleo/Compat>
-#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, QWidget *parent);
~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, QWidget *parent)
: CardCommand::Private(qq, serialno, parent)
, 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() && Kleo::keyHasSign(certificate))
|| (cardSlot == PIVCard::cardAuthenticationKeyRef() && Kleo::keyHasSign(certificate))
|| (cardSlot == PIVCard::digitalSignatureKeyRef() && Kleo::keyHasSign(certificate))
|| (cardSlot == PIVCard::keyManagementKeyRef() && Kleo::keyHasEncrypt(certificate))) {
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::prettyDN(certificate.userID(0).id()),
Formatting::complianceStringShort(certificate),
Formatting::creationDateString(certificate));
const QString slotName = cardKeyDisplayName(cardSlot);
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>",
!slotName.isEmpty() ? slotName : QString::fromStdString(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", 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());
cmd->setAutoResetCardToOpenPGP(false);
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, QWidget *parent)
: CardCommand(new Private(this, cardSlot, serialno, parent))
{
}
CertificateToPIVCardCommand::~CertificateToPIVCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::~CertificateToPIVCardCommand()";
}
void CertificateToPIVCardCommand::certificateToPIVCardDone(const Error &err)
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::certificateToPIVCardDone():" << 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", 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
#include "moc_certificatetopivcardcommand.cpp"
diff --git a/src/dialogs/trustchainwidget.cpp b/src/dialogs/trustchainwidget.cpp
index 2d1cb4b3e..69a191561 100644
--- a/src/dialogs/trustchainwidget.cpp
+++ b/src/dialogs/trustchainwidget.cpp
@@ -1,96 +1,96 @@
/* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "trustchainwidget.h"
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <gpgme++/key.h>
-#include <Libkleo/Dn>
+#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
class TrustChainWidget::Private
{
TrustChainWidget *const q;
public:
Private(TrustChainWidget *qq)
: q(qq)
, ui{qq}
{
}
GpgME::Key key;
struct UI {
QTreeWidget *treeWidget;
UI(QWidget *widget)
{
auto mainLayout = new QVBoxLayout{widget};
mainLayout->setContentsMargins({});
treeWidget = new QTreeWidget{widget};
// Breeze draws no frame for scroll areas that are the only widget in a layout...unless we force it
treeWidget->setProperty("_breeze_force_frame", true);
treeWidget->setHeaderHidden(true);
mainLayout->addWidget(treeWidget);
}
} ui;
};
TrustChainWidget::TrustChainWidget(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
}
TrustChainWidget::~TrustChainWidget()
{
}
void TrustChainWidget::setKey(const GpgME::Key &key)
{
if (key.protocol() != GpgME::CMS) {
qCDebug(KLEOPATRA_LOG) << "Trust chain is only supported for CMS keys";
return;
}
d->key = key;
d->ui.treeWidget->clear();
const auto chain = Kleo::KeyCache::instance()->findIssuers(key, Kleo::KeyCache::RecursiveSearch | Kleo::KeyCache::IncludeSubject);
if (chain.empty()) {
return;
}
QTreeWidgetItem *last = nullptr;
if (!chain.back().isRoot()) {
last = new QTreeWidgetItem(d->ui.treeWidget);
- last->setText(0, i18n("Issuer Certificate Not Found (%1)", Kleo::DN(chain.back().issuerName()).prettyDN()));
+ last->setText(0, i18n("Issuer Certificate Not Found (%1)", Kleo::Formatting::prettyDN(chain.back().issuerName())));
const QBrush &fg = d->ui.treeWidget->palette().brush(QPalette::Disabled, QPalette::WindowText);
last->setForeground(0, fg);
}
for (auto it = chain.rbegin(), end = chain.rend(); it != end; ++it) {
last = last ? new QTreeWidgetItem(last) : new QTreeWidgetItem(d->ui.treeWidget);
- last->setText(0, Kleo::DN(it->userID(0).id()).prettyDN());
+ last->setText(0, Kleo::Formatting::prettyDN(it->userID(0).id()));
}
d->ui.treeWidget->expandAll();
}
GpgME::Key TrustChainWidget::key() const
{
return d->key;
}
#include "moc_trustchainwidget.cpp"
diff --git a/src/view/cardkeysview.cpp b/src/view/cardkeysview.cpp
index 3e7e6dd6a..eee754f7b 100644
--- a/src/view/cardkeysview.cpp
+++ b/src/view/cardkeysview.cpp
@@ -1,755 +1,754 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2024 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "cardkeysview.h"
#include <kleopatra_debug.h>
#include <settings.h>
#include <commands/detailscommand.h>
#include <smartcard/card.h>
#include <smartcard/pivcard.h>
#include <smartcard/readerstatus.h>
#include <smartcard/utils.h>
#include <utils/gui-helper.h>
#include <view/progressoverlay.h>
#include <view/smartcardactions.h>
#include <Libkleo/Compliance>
#include <Libkleo/Debug>
-#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/KeyHelpers>
#include <Libkleo/KeyList>
#include <Libkleo/SystemInfo>
#include <Libkleo/TreeWidget>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QFont>
#include <QHeaderView>
#include <QLabel>
#include <QMenu>
#include <QToolButton>
#include <QVBoxLayout>
#include <gpgme++/context.h>
#include <gpgme++/engineinfo.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <algorithm>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::SmartCard;
using namespace Kleo::Commands;
using namespace Qt::Literals::StringLiterals;
static int toolTipOptions()
{
using namespace Kleo::Formatting;
static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage;
static const int ownerFlags = Subject | UserIDs | OwnerTrust;
static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint;
const Settings settings;
int flags = KeyID;
flags |= settings.showValidity() ? validityFlags : 0;
flags |= settings.showOwnerInformation() ? ownerFlags : 0;
flags |= settings.showCertificateDetails() ? detailsFlags : 0;
return flags;
}
namespace
{
enum ColumnIndex {
Slot,
Certificate,
KeyProtocol,
Fingerprint,
Created,
Usage,
Algorithm,
KeyGrip,
Actions, // keep this as last column
};
}
static const int CardKeysWidgetItemType = QTreeWidgetItem::UserType;
class CardKeysWidgetItem : public QTreeWidgetItem
{
public:
CardKeysWidgetItem(int slotIndex, const std::string &keyRef)
: QTreeWidgetItem{CardKeysWidgetItemType}
, mSlotIndex{slotIndex}
, mKeyRef{keyRef}
{
}
~CardKeysWidgetItem() override = default;
int slotIndex() const
{
return mSlotIndex;
}
const std::string &keyRef() const
{
return mKeyRef;
}
void setSubkey(const Subkey &subkey)
{
mSubkey = subkey;
}
const Subkey &subkey() const
{
return mSubkey;
}
private:
int mSlotIndex;
std::string mKeyRef;
Subkey mSubkey;
};
static QString cardKeyUsageDisplayName(char c)
{
switch (c) {
case 'e':
return i18n("encrypt");
case 's':
return i18n("sign");
case 'c':
return i18n("certify");
case 'a':
return i18n("authenticate");
default:
return {};
};
}
static QStringList cardKeyUsageDisplayNames(const std::string &usage)
{
QStringList result;
if (usage == "-") {
// special case (e.g. for some NetKey keys)
return result;
}
result.reserve(usage.size());
std::ranges::transform(usage, std::back_inserter(result), &cardKeyUsageDisplayName);
return result;
}
static std::vector<CardKeysWidgetItem *> getItems(const TreeWidget *treeWidget, int slotIndex)
{
std::vector<CardKeysWidgetItem *> items;
for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) {
auto item = static_cast<CardKeysWidgetItem *>(treeWidget->topLevelItem(i));
if (item->slotIndex() == slotIndex) {
items.push_back(item);
} else if (item->slotIndex() > slotIndex) {
// the items are sorted by slot index so that we do not have to look further
break;
}
}
return items;
}
static void updateTreeWidgetItem(CardKeysWidgetItem *item, const KeyPairInfo &keyInfo, const Subkey &subkey)
{
static const QFont monospaceFont{u"monospace"_s};
Q_ASSERT(item);
const auto key = subkey.parent();
// slot
const QString slotName = cardKeyDisplayName(keyInfo.keyRef);
if (!slotName.isEmpty()) {
item->setData(Slot, Qt::DisplayRole, slotName);
item->setData(Slot, Qt::ToolTipRole, i18nc("@info:tooltip", "Card slot ID: %1", QString::fromStdString(keyInfo.keyRef)));
} else {
item->setData(Slot, Qt::DisplayRole, QString::fromStdString(keyInfo.keyRef));
}
// key grip
if (keyInfo.grip.empty()) {
item->setData(KeyGrip, Qt::DisplayRole, u"-"_s);
item->setData(KeyGrip, Qt::AccessibleTextRole, QVariant{});
} else {
item->setData(KeyGrip, Qt::DisplayRole, QString::fromStdString(keyInfo.grip));
item->setData(KeyGrip, Qt::AccessibleTextRole, Formatting::accessibleHexID(keyInfo.grip.c_str()));
}
// usage
auto usages = cardKeyUsageDisplayNames(keyInfo.usage);
if (usages.empty()) {
item->setData(Usage, Qt::DisplayRole, QString::fromStdString(keyInfo.usage));
item->setData(Usage, Qt::AccessibleTextRole, i18nc("@info entry in Usage column of a smart card key", "none"));
} else {
item->setData(Usage, Qt::DisplayRole, usages.join(i18nc("Separator between words in a list", ", ")));
// we don't have to set/overwrite data for Qt::AccessibleTextRole because keyInfo.usage never changes
}
// created
if (keyInfo.grip.empty()) {
item->setData(Created, Qt::DisplayRole, u"-"_s);
item->setData(Created, Qt::AccessibleTextRole, QVariant{});
} else if (keyInfo.keyTime.isValid()) {
item->setData(Created, Qt::DisplayRole, Formatting::dateString(keyInfo.keyTime.date()));
item->setData(Created, Qt::AccessibleTextRole, Formatting::accessibleDate(keyInfo.keyTime.date()));
} else {
item->setData(Created, Qt::DisplayRole, u"?"_s);
item->setData(Created, Qt::AccessibleTextRole, i18nc("@info date is unknown", "unknown"));
}
// algorithm
if (keyInfo.grip.empty()) {
item->setData(Algorithm, Qt::DisplayRole, u"-"_s);
item->setData(Algorithm, Qt::AccessibleTextRole, QVariant{});
} else if (keyInfo.algorithm.empty()) {
item->setData(Algorithm, Qt::DisplayRole, u"?"_s);
item->setData(Algorithm, Qt::AccessibleTextRole, i18nc("@info unknown key algorithm", "unknown"));
} else {
item->setData(Algorithm, Qt::DisplayRole, QString::fromStdString(keyInfo.algorithm));
item->setData(Algorithm, Qt::AccessibleTextRole, QVariant{});
}
item->setSubkey(subkey);
if (subkey.isNull()) {
// fingerprint
item->setData(Fingerprint, Qt::DisplayRole, QString{});
item->setData(Fingerprint, Qt::AccessibleTextRole, QVariant{});
// certificate
item->setData(Certificate, Qt::DisplayRole, keyInfo.grip.empty() ? i18nc("@info", "no key") : i18nc("@info", "no associated certificate"));
item->setData(Certificate, Qt::ToolTipRole, QString{});
// protocol
item->setData(KeyProtocol, Qt::DisplayRole, QString{});
} else {
// fingerprint
item->setData(Fingerprint, Qt::DisplayRole, Formatting::prettyID(subkey.fingerprint()));
item->setData(Fingerprint, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.fingerprint()));
item->setData(Fingerprint, Kleo::ClipboardRole, QString::fromLatin1(subkey.fingerprint()));
// certificate
if (key.protocol() == GpgME::OpenPGP) {
item->setData(Certificate, Qt::DisplayRole, Formatting::prettyUserID(key.userID(0)));
} else {
- item->setData(Certificate, Qt::DisplayRole, DN(key.userID(0).id()).prettyDN());
+ item->setData(Certificate, Qt::DisplayRole, Formatting::prettyDN(key.userID(0).id()));
}
item->setData(Certificate, Qt::ToolTipRole, Formatting::toolTip(key, toolTipOptions()));
// protocol
item->setData(KeyProtocol, Qt::DisplayRole, Formatting::type(key));
}
const auto keyFilters = KeyFilterManager::instance();
for (int col = 0; col < ColumnIndex::Actions; ++col) {
if ((col == Certificate) && subkey.isNull()) {
QFont italicFont;
italicFont.setItalic(true);
item->setFont(col, italicFont);
} else {
item->setFont(col, keyFilters->font(key, (col == KeyGrip || col == Fingerprint) ? monospaceFont : QFont{}));
}
if (!SystemInfo::isHighContrastModeActive()) {
if (auto bgColor = keyFilters->bgColor(key); bgColor.isValid()) {
item->setBackground(col, bgColor);
} else {
item->setBackground(col, {});
}
if (auto fgColor = keyFilters->fgColor(key); fgColor.isValid()) {
item->setForeground(col, fgColor);
} else {
item->setForeground(col, {});
}
}
}
}
static std::vector<QAction *> actionsForCardSlot(SmartCard::AppType appType)
{
std::vector<QString> actions;
switch (appType) {
case AppType::NetKeyApp:
actions = {u"card_slot_show_certificate_details"_s};
if (!(engineInfo(GpgME::GpgSMEngine).engineVersion() < "2.2.26")) { // see https://dev.gnupg.org/T5184
actions.push_back(u"card_slot_create_csr"_s);
}
break;
case AppType::P15App:
actions = {u"card_slot_show_certificate_details"_s};
break;
case AppType::OpenPGPApp:
actions = {u"card_slot_show_certificate_details"_s};
if (!DeVSCompliance::isActive()) {
actions.push_back(u"card_slot_generate_key"_s);
}
actions.push_back(u"card_slot_create_csr"_s);
break;
case AppType::PIVApp: {
actions = {
u"card_slot_show_certificate_details"_s,
u"card_slot_generate_key"_s,
u"card_slot_write_key"_s,
u"card_slot_write_certificate"_s,
u"card_slot_read_certificate"_s,
u"card_slot_create_csr"_s,
};
break;
}
case AppType::NoApp:
break;
};
return SmartCardActions::instance()->actions(actions);
}
static QAction *updateAction(QAction *action, const CardKeysWidgetItem *item, const Card *card)
{
if (action->objectName() == "card_slot_show_certificate_details"_L1) {
action->setEnabled(!item->subkey().isNull());
return action;
}
switch (card->appType()) {
case AppType::PIVApp: {
if (action->objectName() == "card_slot_write_key"_L1) {
action->setEnabled(item->keyRef() == PIVCard::cardAuthenticationKeyRef() || item->keyRef() == PIVCard::keyManagementKeyRef());
} else if (action->objectName() == "card_slot_write_certificate"_L1) {
action->setEnabled(item->subkey().parent().protocol() == GpgME::CMS);
} else if (action->objectName() == "card_slot_read_certificate"_L1) {
action->setEnabled(!card->certificateData(item->keyRef()).empty());
} else if (action->objectName() == "card_slot_create_csr"_L1) {
const auto keyInfo = card->keyInfo(item->keyRef());
// for PIV trying to create a CSR for the authentication key fails
action->setEnabled((keyInfo.canSign() || keyInfo.canEncrypt()) //
&& !keyInfo.grip.empty() //
&& DeVSCompliance::algorithmIsCompliant(keyInfo.algorithm));
}
break;
}
case AppType::OpenPGPApp: {
if (action->objectName() == "card_slot_create_csr"_L1) {
const auto keyInfo = card->keyInfo(item->keyRef());
// trying to create a CSR for the encryption key fails (signing the request fails with "Invalid ID")
action->setEnabled((keyInfo.canCertify() || keyInfo.canSign() || keyInfo.canAuthenticate()) //
&& !keyInfo.grip.empty() //
&& DeVSCompliance::algorithmIsCompliant(keyInfo.algorithm));
}
break;
}
case AppType::NetKeyApp: {
if (action->objectName() == "card_slot_create_csr"_L1) {
const auto keyInfo = card->keyInfo(item->keyRef());
// SigG certificates for qualified signatures (with keyRef "NKS-SIGG.*") are issued with the physical cards;
// it's not possible to request a certificate for them; therefore, we only enable it for NKS-NKS3.* keys
action->setEnabled(item->keyRef().starts_with("NKS-NKS3.") //
&& keyInfo.canSign() //
&& !keyInfo.grip.empty() //
&& DeVSCompliance::algorithmIsCompliant(keyInfo.algorithm));
}
break;
}
case AppType::P15App:
// nothing to do
break;
case AppType::NoApp:
// cannot happen
break;
};
return action;
}
static bool canImportCertificates(const Card *card, const std::vector<std::string> &keyRefsWithoutSMimeCertificate)
{
switch (card->appType()) {
case AppType::OpenPGPApp:
// no S/MIME certificates to learn from OpenPGP cards
return false;
case AppType::NetKeyApp:
return !keyRefsWithoutSMimeCertificate.empty();
case AppType::P15App:
return Settings().autoLoadP15Certs() && !keyRefsWithoutSMimeCertificate.empty();
case AppType::PIVApp:
// check whether there are S/MIME certificates for the given card slots
return std::ranges::any_of(keyRefsWithoutSMimeCertificate, [card](const auto &keyRef) {
return !card->certificateData(keyRef).empty();
});
case AppType::NoApp:
break;
}
return false;
}
static inline int compareByProtocolAndFingerprint(const Subkey &a, const Subkey &b)
{
if (a.parent().protocol() < b.parent().protocol()) {
return -1;
}
if (a.parent().protocol() > b.parent().protocol()) {
return 1;
}
return qstrcmp(a.fingerprint(), b.fingerprint());
}
static auto getSortedSubkeys(const std::string &keyGrip)
{
auto subkeys = KeyCache::instance()->findSubkeysByKeyGrip(keyGrip);
// sort subkeys by protocol and fingerprint to ensure a stable list order
auto lessByProtocolAndFingerprint = [](const Subkey &a, const Subkey &b) {
return compareByProtocolAndFingerprint(a, b) < 0;
};
std::sort(subkeys.begin(), subkeys.end(), lessByProtocolAndFingerprint);
return subkeys;
}
CardKeysView::CardKeysView(QWidget *parent, Options options)
: QWidget{parent}
, mOptions{options}
{
auto mainLayout = new QVBoxLayout{this};
mainLayout->setContentsMargins({});
// The certificate view
mTreeWidget = new TreeWidget{this};
mTreeWidget->setAccessibleName(i18nc("@title", "card keys and certificates"));
mTreeWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
mTreeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
mTreeWidget->setRootIsDecorated(false);
mTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
mTreeWidget->setHeaderLabels({
i18nc("@title:column Name or ID of a storage slot for a key on a smart card", "Card Slot"),
i18nc("@title:column", "User ID"),
i18nc("@title:column", "Protocol"),
i18nc("@title:column", "Fingerprint"),
i18nc("@title:column", "Created"),
i18nc("@title:column", "Usage"),
i18nc("@title:column", "Algorithm"),
i18nc("@title:column", "Keygrip"),
i18nc("@title:column", "Actions"),
});
Q_ASSERT(mTreeWidget->columnCount() == ColumnIndex::Actions + 1);
mTreeWidget->header()->setStretchLastSection(false); // the Actions column shouldn't stretch
mainLayout->addWidget(mTreeWidget);
connect(mTreeWidget, &QTreeWidget::currentItemChanged, this, [this]() {
Q_EMIT currentCardSlotChanged();
});
connect(mTreeWidget, &QWidget::customContextMenuRequested, this, [this](const auto &pos) {
const auto item = static_cast<const CardKeysWidgetItem *>(mTreeWidget->itemAt(pos));
if (!item) {
return;
}
auto menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose, true);
for (auto action : actionsForCardSlot(mCard->appType())) {
menu->addAction(updateAction(SmartCardActions::createProxyAction(action, menu), item, mCard.get()));
}
menu->popup(mTreeWidget->viewport()->mapToGlobal(pos));
});
if (auto action = SmartCardActions::instance()->action(u"card_slot_show_certificate_details"_s)) {
connect(mTreeWidget, &QAbstractItemView::doubleClicked, action, &QAction::trigger);
}
mTreeViewOverlay = new ProgressOverlay{mTreeWidget, this};
mTreeViewOverlay->hide();
connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this]() {
updateKeyList(IgnoreMissingCertificates);
});
}
CardKeysView::~CardKeysView() = default;
void CardKeysView::setCard(const std::shared_ptr<const Card> &card)
{
mCard = card;
updateKeyList(LearnMissingCertificates);
}
std::string CardKeysView::currentCardSlot() const
{
if (const CardKeysWidgetItem *current = static_cast<CardKeysWidgetItem *>(mTreeWidget->currentItem())) {
return current->keyRef();
}
return {};
}
Key CardKeysView::currentCertificate() const
{
if (const CardKeysWidgetItem *current = static_cast<CardKeysWidgetItem *>(mTreeWidget->currentItem())) {
return current->subkey().parent();
}
qCDebug(KLEOPATRA_LOG) << __func__ << "- no current item";
return {};
}
bool CardKeysView::eventFilter(QObject *obj, QEvent *event)
{
if ((event->type() == QEvent::FocusOut) //
&& (obj == mTreeWidget->itemWidget(mTreeWidget->currentItem(), Actions))) {
// workaround for missing update when last actions button loses focus
mTreeWidget->viewport()->update();
}
return QWidget::eventFilter(obj, event);
}
void CardKeysView::updateKeyList(UpdateKeyListOptions options)
{
qCDebug(KLEOPATRA_LOG) << __func__;
const bool firstSetUp = (mTreeWidget->topLevelItemCount() == 0);
if (!mCard) {
// ignore KeyCache::keysMayHaveChanged signal until the card has been set
return;
}
std::vector<std::string> keyRefsWithoutSMimeCertificate;
const auto cardKeyInfos = mCard->keyInfos();
mCertificates.clear();
mCertificates.reserve(cardKeyInfos.size());
for (int slotIndex = 0; slotIndex < int(cardKeyInfos.size()); ++slotIndex) {
const auto &keyInfo = cardKeyInfos[slotIndex];
bool haveFoundSMimeCertificate = false;
const auto subkeys = getSortedSubkeys(keyInfo.grip);
auto items = getItems(mTreeWidget, slotIndex);
if (subkeys.empty()) {
if (items.empty()) {
Q_ASSERT(firstSetUp);
insertTreeWidgetItem(slotIndex, keyInfo, Subkey{});
} else {
auto firstItem = items.front();
updateTreeWidgetItem(firstItem, keyInfo, Subkey{});
if (auto button = qobject_cast<QToolButton *>(mTreeWidget->itemWidget(firstItem, Actions))) {
if (button->defaultAction()) {
updateAction(button->defaultAction(), firstItem, mCard.get());
}
}
for (int i = 1; i < int(items.size()); ++i) {
auto item = items.at(i);
qCDebug(KLEOPATRA_LOG) << __func__ << "deleting item - slot:" << item->slotIndex() << "certificate:" << item->subkey().parent();
delete item;
}
}
} else {
if (items.empty()) {
Q_ASSERT(firstSetUp);
for (const auto &subkey : subkeys) {
insertTreeWidgetItem(slotIndex, keyInfo, subkey);
}
} else if (items.front()->subkey().isNull()) {
// the second most simple case: slot with no associated subkeys -> slot with one or more associated subkeys
Q_ASSERT(items.size() == 1);
auto firstItem = items.front();
updateTreeWidgetItem(firstItem, keyInfo, subkeys.front());
if (auto button = qobject_cast<QToolButton *>(mTreeWidget->itemWidget(firstItem, Actions))) {
if (button->defaultAction()) {
updateAction(button->defaultAction(), firstItem, mCard.get());
}
}
const int itemIndex = mTreeWidget->indexOfTopLevelItem(firstItem);
for (int i = 1; i < int(subkeys.size()); ++i) {
insertTreeWidgetItem(slotIndex, keyInfo, subkeys.at(i), itemIndex + i);
}
} else {
// the complicated case; we make use of the known order of the existing items and subkeys
int i = 0;
int s = 0;
while (i < int(items.size()) && s < int(subkeys.size())) {
auto item = items.at(i);
const Subkey &subkey = subkeys.at(s);
const int itemVsSubkey = compareByProtocolAndFingerprint(item->subkey(), subkey);
if (itemVsSubkey < 0) {
// this subkey is gone
qCDebug(KLEOPATRA_LOG) << __func__ << "deleting item - slot:" << item->slotIndex() << "certificate:" << item->subkey().parent();
delete item;
++i;
} else if (itemVsSubkey == 0) {
updateTreeWidgetItem(item, keyInfo, subkey);
++i;
++s;
} else {
// this subkey is new; insert it before the current item
const int itemIndex = mTreeWidget->indexOfTopLevelItem(item);
insertTreeWidgetItem(slotIndex, keyInfo, subkey, itemIndex);
++s;
}
}
for (; i < int(items.size()); ++i) {
auto item = items.at(i);
qCDebug(KLEOPATRA_LOG) << __func__ << "deleting item - slot:" << item->slotIndex() << "certificate:" << item->subkey().parent();
delete item;
}
// insert remaining new subkeys after last item for slotIndex
int insertIndex = 0;
while ((insertIndex < mTreeWidget->topLevelItemCount()) //
&& (static_cast<CardKeysWidgetItem *>(mTreeWidget->topLevelItem(insertIndex))->slotIndex() <= slotIndex)) {
++insertIndex;
}
insertIndex -= s;
for (; s < int(subkeys.size()); ++s) {
insertTreeWidgetItem(slotIndex, keyInfo, subkeys.at(s), insertIndex + s);
}
}
for (const auto &subkey : subkeys) {
if (subkey.parent().protocol() == GpgME::CMS) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Found S/MIME certificate for card key" << keyInfo.grip << "in cache:" << subkey.parent();
haveFoundSMimeCertificate = true;
mCertificates.push_back(subkey.parent());
}
}
}
if (!keyInfo.grip.empty() && !haveFoundSMimeCertificate) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Did not find an S/MIME certificates for card key" << keyInfo.grip << "in cache";
keyRefsWithoutSMimeCertificate.push_back(keyInfo.keyRef);
}
}
if (firstSetUp && !mTreeWidget->restoreColumnLayout(u"CardKeysView-"_s + QString::fromStdString(mCard->appName()))) {
mTreeWidget->hideColumn(KeyProtocol);
mTreeWidget->hideColumn(KeyGrip);
if (!(mOptions & ShowCreated)) {
mTreeWidget->hideColumn(Created);
}
mTreeWidget->resizeToContentsLimited();
}
ensureCertificatesAreValidated();
if ((options == LearnMissingCertificates) && canImportCertificates(mCard.get(), keyRefsWithoutSMimeCertificate)) {
// the card contains keys we don't know; try to learn them from the card
learnCard();
}
}
void CardKeysView::insertTreeWidgetItem(int slotIndex, const KeyPairInfo &keyInfo, const Subkey &subkey, int index)
{
qCDebug(KLEOPATRA_LOG) << __func__ << "slot:" << slotIndex << "certificate:" << subkey.parent() << "index:" << index;
if (index == -1) {
index = mTreeWidget->topLevelItemCount();
}
auto item = new CardKeysWidgetItem{slotIndex, keyInfo.keyRef};
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren);
updateTreeWidgetItem(item, keyInfo, subkey);
mTreeWidget->insertTopLevelItem(index, item);
auto actionsButton = addActionsButton(item, mCard->appType());
if (index == 0) {
forceSetTabOrder(mTreeWidget, actionsButton);
} else {
auto prevActionsButton = mTreeWidget->itemWidget(mTreeWidget->topLevelItem(index - 1), Actions);
forceSetTabOrder(prevActionsButton, actionsButton);
}
actionsButton->installEventFilter(this);
}
QToolButton *CardKeysView::addActionsButton(CardKeysWidgetItem *item, SmartCard::AppType appType)
{
const auto actions = actionsForCardSlot(appType);
auto button = new QToolButton;
if (actions.size() == 1) {
button->setDefaultAction(updateAction(SmartCardActions::createProxyAction(actions.front(), button), item, mCard.get()));
// ensure that current item is set to the right item before the action is triggered;
// interestingly, focus is given to the tree widget instead of the clicked button so that
// the event filtering of QAbstractItemView doesn't take care of this
connect(button, &QAbstractButton::pressed, mTreeWidget, [this, item]() {
mTreeWidget->setCurrentItem(item, Actions);
});
} else {
button->setPopupMode(QToolButton::InstantPopup);
button->setIcon(QIcon::fromTheme(QStringLiteral("application-menu")));
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
button->setAccessibleName(i18nc("@action:button", "Actions"));
button->setToolTip(i18nc("@info", "Show actions available for this smart card slot"));
// show the menu *after* the clicked item is set as current item to ensure correct action states
connect(button, &QAbstractButton::pressed, mTreeWidget, [this, item, button, appType]() {
mTreeWidget->setCurrentItem(item, Actions);
QMenu menu{button};
for (auto action : actionsForCardSlot(appType)) {
menu.addAction(updateAction(SmartCardActions::createProxyAction(action, &menu), item, mCard.get()));
}
button->setMenu(&menu);
button->showMenu();
button->setMenu(nullptr);
});
}
mTreeWidget->setItemWidget(item, Actions, button);
return button;
}
void CardKeysView::ensureCertificatesAreValidated()
{
if (mCertificates.empty()) {
return;
}
std::vector<GpgME::Key> certificatesToValidate;
certificatesToValidate.reserve(mCertificates.size());
std::ranges::copy_if(mCertificates, std::back_inserter(certificatesToValidate), [this](const auto &cert) {
// don't bother validating certificates that have expired or are otherwise invalid
return !cert.isBad() && !mValidatedCertificates.contains(cert);
});
if (!certificatesToValidate.empty()) {
startCertificateValidation(certificatesToValidate);
mValidatedCertificates.insert(certificatesToValidate.cbegin(), certificatesToValidate.cend());
}
}
void CardKeysView::startCertificateValidation(const std::vector<GpgME::Key> &certificates)
{
qCDebug(KLEOPATRA_LOG) << __func__ << "Validating certificates" << certificates;
auto job = std::unique_ptr<QGpgME::KeyListJob>{QGpgME::smime()->keyListJob(false, true, true)};
auto ctx = QGpgME::Job::context(job.get());
ctx->addKeyListMode(GpgME::WithSecret);
connect(job.get(), &QGpgME::KeyListJob::result, this, &CardKeysView::certificateValidationDone);
job->start(Kleo::getFingerprints(certificates));
job.release();
}
void CardKeysView::certificateValidationDone(const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &validatedCertificates)
{
qCDebug(KLEOPATRA_LOG) << __func__ << "certificates:" << validatedCertificates;
if (result.error()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Validating certificates failed:" << result.error();
return;
}
// replace the current certificates with the validated certificates
for (const auto &validatedCert : validatedCertificates) {
const auto fpr = validatedCert.primaryFingerprint();
const auto it = std::find_if(mCertificates.begin(), mCertificates.end(), [fpr](const auto &cert) {
return !qstrcmp(fpr, cert.primaryFingerprint());
});
if (it != mCertificates.end()) {
*it = validatedCert;
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "Didn't find validated certificate in certificate list:" << validatedCert;
}
}
updateKeyList(IgnoreMissingCertificates);
}
void CardKeysView::learnCard()
{
qCDebug(KLEOPATRA_LOG) << __func__;
mTreeViewOverlay->setText(i18nc("@info", "Reading certificates from smart card ..."));
mTreeViewOverlay->showOverlay();
ReaderStatus::mutableInstance()->learnCard(mCard->serialNumber(), mCard->appName());
connect(ReaderStatus::instance(), &ReaderStatus::cardLearned, this, [this](const std::string &serialNumber, const std::string &appName) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::cardLearned" << appName << serialNumber;
if (serialNumber == mCard->serialNumber() && appName == mCard->appName()) {
mTreeViewOverlay->hideOverlay();
}
});
}
#include "moc_cardkeysview.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 12:23 PM (1 d, 3 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
6f/0c/d020781c35af0208aabbce9a9b25

Event Timeline