Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34110766
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
51 KB
Subscribers
None
View Options
diff --git a/src/dialogs/useridswidget.cpp b/src/dialogs/useridswidget.cpp
index bcb540bc4..fc26e7b9e 100644
--- a/src/dialogs/useridswidget.cpp
+++ b/src/dialogs/useridswidget.cpp
@@ -1,538 +1,539 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "useridswidget.h"
#include "commands/adduseridcommand.h"
#include "commands/certifycertificatecommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/revokeuseridcommand.h"
#include "commands/setprimaryuseridcommand.h"
#ifdef MAILAKONADI_ENABLED
#include "commands/exportopenpgpcerttoprovidercommand.h"
#endif // MAILAKONADI_ENABLED
#include "utils/tags.h"
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/TreeView>
#include <Libkleo/TreeWidget>
#include <Libkleo/UserIDListModel>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <QDateTime>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QMenu>
#include <QPushButton>
#include <QVBoxLayout>
+#include <QGpgME/Debug>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme++/tofuinfo.h>
#include "kleopatra_debug.h"
using namespace Kleo;
namespace
{
std::vector<GpgME::UserID> selectedUserIDs(const QTreeWidget *treeWidget)
{
if (!treeWidget) {
return {};
}
std::vector<GpgME::UserID> userIDs;
const auto selected = treeWidget->selectedItems();
std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) {
return item->data(0, Qt::UserRole).value<GpgME::UserID>();
});
return userIDs;
}
static QPushButton *addActionButton(QLayout *buttonBox, QAction *action)
{
if (!action) {
return nullptr;
}
auto button = new QPushButton(buttonBox->parentWidget());
button->setText(action->text());
buttonBox->addWidget(button);
button->setEnabled(action->isEnabled());
QObject::connect(action, &QAction::changed, button, [action, button]() {
button->setEnabled(action->isEnabled());
});
QObject::connect(button, &QPushButton::clicked, action, &QAction::trigger);
return button;
}
}
class UserIdsWidget::Private
{
public:
enum Columns {
Name,
Email,
TrustLevel,
Origin,
Tags,
};
Private(UserIdsWidget *qq)
: q{qq}
{
}
TreeWidget *userIDTable = nullptr;
QPushButton *addUserIDBtn = nullptr;
QPushButton *revokeUserIDBtn = nullptr;
QPushButton *certifyBtn = nullptr;
QPushButton *revokeCertificationsBtn = nullptr;
QAction *setPrimaryUserIDAction = nullptr;
QAction *certifyAction = nullptr;
QAction *revokeCertificationsAction = nullptr;
GpgME::Key key;
bool updateInProgress = false;
QPushButton *moreButton = nullptr;
QHBoxLayout *buttonRow = nullptr;
QString trustLevelText(const GpgME::UserID &uid) const;
QIcon trustLevelIcon(const GpgME::UserID &uid) const;
QString tofuTooltipString(const GpgME::UserID &uid) const;
void setUpUserIDTable();
void addUserID();
void updateUserIDActions();
void setPrimaryUserID(const GpgME::UserID &uid = {});
void certifyUserIDs();
void revokeCertifications();
void revokeUserID(const GpgME::UserID &uid);
void revokeSelectedUserID();
void userIDTableContextMenuRequested(const QPoint &p);
private:
UserIdsWidget *const q;
};
UserIdsWidget::UserIdsWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
auto userIDsLayout = new QVBoxLayout{this};
userIDsLayout->setContentsMargins({});
d->userIDTable = new TreeWidget{parent};
d->userIDTable->setAccessibleName(i18n("User IDs"));
QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem();
__qtreewidgetitem->setText(0, QString::fromUtf8("1"));
d->userIDTable->setHeaderItem(__qtreewidgetitem);
d->userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
d->userIDTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
d->userIDTable->setRootIsDecorated(false);
d->userIDTable->setUniformRowHeights(true);
d->userIDTable->setAllColumnsShowFocus(false);
userIDsLayout->addWidget(d->userIDTable);
d->buttonRow = new QHBoxLayout;
d->addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent);
d->buttonRow->addWidget(d->addUserIDBtn);
d->revokeUserIDBtn = new QPushButton(i18nc("@action:button", "Revoke User ID"), parent);
d->buttonRow->addWidget(d->revokeUserIDBtn);
d->setPrimaryUserIDAction = new QAction({}, i18nc("@action:button", "Flag as Primary"));
d->setPrimaryUserIDAction->setToolTip(i18nc("@info:tooltip", "Flag the selected user ID as the primary user ID of this key."));
d->certifyAction = new QAction({}, i18nc("@action:button", "Certify User IDs"));
d->revokeCertificationsAction = new QAction({}, i18nc("@action:button", "Revoke Certifications"));
d->certifyBtn = addActionButton(d->buttonRow, d->certifyAction);
d->revokeCertificationsBtn = addActionButton(d->buttonRow, d->revokeCertificationsAction);
d->moreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-menu")), {});
d->moreButton->setToolTip(i18nc("@info:tooltip", "Show more options"));
d->buttonRow->addWidget(d->moreButton);
connect(d->moreButton, &QPushButton::clicked, this, [this]() {
auto menu = new QMenu(this);
menu->addAction(d->setPrimaryUserIDAction);
menu->addAction(d->certifyAction);
menu->addAction(d->revokeCertificationsAction);
menu->popup(d->moreButton->mapToGlobal(QPoint()));
});
d->buttonRow->addStretch(1);
userIDsLayout->addLayout(d->buttonRow);
setLayout(userIDsLayout);
connect(d->addUserIDBtn, &QPushButton::clicked, this, [this]() {
d->addUserID();
});
connect(d->userIDTable, &QTreeWidget::itemSelectionChanged, this, [this]() {
d->updateUserIDActions();
});
connect(d->setPrimaryUserIDAction, &QAction::triggered, this, [this]() {
d->setPrimaryUserID();
});
connect(d->certifyAction, &QAction::triggered, this, [this]() {
d->certifyUserIDs();
});
connect(d->revokeCertificationsAction, &QAction::triggered, this, [this]() {
d->revokeCertifications();
});
connect(d->revokeUserIDBtn, &QPushButton::clicked, this, [this]() {
d->revokeSelectedUserID();
});
d->userIDTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(d->userIDTable, &QAbstractItemView::customContextMenuRequested, this, [this](const QPoint &p) {
d->userIDTableContextMenuRequested(p);
});
}
void UserIdsWidget::Private::updateUserIDActions()
{
const auto userIDs = selectedUserIDs(userIDTable);
const auto singleUserID = userIDs.size() == 1 ? userIDs.front() : GpgME::UserID{};
const bool isPrimaryUserID = !singleUserID.isNull() && (userIDTable->selectedItems().front() == userIDTable->topLevelItem(0));
setPrimaryUserIDAction->setEnabled(!singleUserID.isNull() //
&& !isPrimaryUserID //
&& !Kleo::isRevokedOrExpired(singleUserID) //
&& canBeUsedForSecretKeyOperations(key));
revokeUserIDBtn->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID));
}
UserIdsWidget::~UserIdsWidget() = default;
GpgME::Key UserIdsWidget::key() const
{
return d->key;
}
void UserIdsWidget::setKey(const GpgME::Key &key)
{
d->key = key;
d->setUpUserIDTable();
const bool isOwnKey = key.hasSecret();
const auto isLocalKey = !isRemoteKey(key);
const auto keyCanBeCertified = Kleo::canBeCertified(key);
const auto userCanSignUserIDs = userHasCertificationKey();
d->addUserIDBtn->setVisible(isOwnKey);
d->addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key));
d->setPrimaryUserIDAction->setVisible(isOwnKey);
d->setPrimaryUserIDAction->setEnabled(false); // requires a selected user ID
d->certifyAction->setVisible(true); // always visible (for OpenPGP keys)
d->certifyBtn->setVisible(!isOwnKey);
d->revokeCertificationsBtn->setVisible(!isOwnKey);
d->moreButton->setVisible(isOwnKey);
d->certifyAction->setEnabled(isLocalKey && keyCanBeCertified && userCanSignUserIDs);
d->revokeCertificationsAction->setVisible(Kleo::Commands::RevokeCertificationCommand::isSupported());
d->revokeCertificationsAction->setEnabled(userCanSignUserIDs && isLocalKey);
d->revokeUserIDBtn->setVisible(isOwnKey);
d->revokeUserIDBtn->setEnabled(false); // requires a selected user ID
}
void UserIdsWidget::Private::setUpUserIDTable()
{
userIDTable->clear();
QStringList headers = {
i18n("Name"),
i18n("Email"),
i18n("Trust Level"),
i18n("Origin"),
i18n("Tags"),
};
userIDTable->setColumnCount(headers.count());
userIDTable->setColumnWidth(Name, 200);
userIDTable->setColumnWidth(Email, 200);
userIDTable->setHeaderLabels(headers);
const auto uids = key.userIDs();
for (unsigned int i = 0; i < uids.size(); ++i) {
const auto &uid = uids[i];
auto item = new QTreeWidgetItem;
const QString toolTip = tofuTooltipString(uid);
item->setData(Name, Qt::UserRole, QVariant::fromValue(uid));
auto pMail = Kleo::Formatting::prettyEMail(uid);
auto pName = Kleo::Formatting::prettyName(uid);
item->setData(Name, Qt::DisplayRole, pName);
item->setData(Name, Qt::ToolTipRole, toolTip);
item->setData(Email, Qt::DisplayRole, pMail);
item->setData(Email, Qt::ToolTipRole, toolTip);
item->setData(Email, Qt::AccessibleTextRole, pMail.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : pMail);
item->setData(TrustLevel, Qt::DecorationRole, trustLevelIcon(uid));
item->setData(TrustLevel, Qt::DisplayRole, trustLevelText(uid));
item->setData(TrustLevel, Qt::ToolTipRole, toolTip);
item->setData(Origin, Qt::DisplayRole, Formatting::origin(uid.origin()));
GpgME::Error err;
QStringList tagList;
for (const auto &tag : uid.remarks(Tags::tagKeys(), err)) {
if (err) {
qCWarning(KLEOPATRA_LOG) << "Getting remarks for user ID" << uid.id() << "failed:" << err;
}
tagList << QString::fromStdString(tag);
}
qCDebug(KLEOPATRA_LOG) << "tagList:" << tagList;
const auto tags = tagList.join(QStringLiteral("; "));
item->setData(Tags, Qt::DisplayRole, tags);
item->setData(Tags, Qt::ToolTipRole, toolTip);
userIDTable->addTopLevelItem(item);
}
if (!userIDTable->restoreColumnLayout(QStringLiteral("UserIDTable"))) {
userIDTable->hideColumn(Tags);
userIDTable->hideColumn(Origin);
}
userIDTable->resizeToContentsLimited();
}
QString UserIdsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const
{
const auto tofu = uid.tofuInfo();
if (tofu.isNull()) {
return QString();
}
QString html = QStringLiteral("<table border=\"0\" cell-padding=\"5\">");
const auto appendRow = [&html](const QString &lbl, const QString &val) {
html += QStringLiteral(
"<tr>"
"<th style=\"text-align: right; padding-right: 5px; white-space: nowrap;\">%1:</th>"
"<td style=\"white-space: nowrap;\">%2</td>"
"</tr>")
.arg(lbl, val);
};
const auto appendHeader = [this, &html](const QString &hdr) {
html += QStringLiteral("<tr><th colspan=\"2\" style=\"background-color: %1; color: %2\">%3</th></tr>")
.arg(q->palette().highlight().color().name(), q->palette().highlightedText().color().name(), hdr);
};
const auto dateTime = [](long ts) {
QLocale l;
return ts == 0 ? i18n("never") : l.toString(QDateTime::fromSecsSinceEpoch(ts), QLocale::ShortFormat);
};
appendHeader(i18n("Signing"));
appendRow(i18n("First message"), dateTime(tofu.signFirst()));
appendRow(i18n("Last message"), dateTime(tofu.signLast()));
appendRow(i18n("Message count"), QString::number(tofu.signCount()));
appendHeader(i18n("Encryption"));
appendRow(i18n("First message"), dateTime(tofu.encrFirst()));
appendRow(i18n("Last message"), dateTime(tofu.encrLast()));
appendRow(i18n("Message count"), QString::number(tofu.encrCount()));
html += QStringLiteral("</table>");
// Make sure the tooltip string is different for each UserID, even if the
// data are the same, otherwise the tooltip is not updated and moved when
// user moves mouse from one row to another.
html += QStringLiteral("<!-- %1 //-->").arg(QString::fromUtf8(uid.id()));
return html;
}
QIcon UserIdsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const
{
if (updateInProgress) {
return QIcon::fromTheme(QStringLiteral("emblem-question"));
}
switch (uid.validity()) {
case GpgME::UserID::Unknown:
case GpgME::UserID::Undefined:
return QIcon::fromTheme(QStringLiteral("emblem-question"));
case GpgME::UserID::Never:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case GpgME::UserID::Marginal:
return QIcon::fromTheme(QStringLiteral("emblem-warning"));
case GpgME::UserID::Full:
case GpgME::UserID::Ultimate:
return QIcon::fromTheme(QStringLiteral("emblem-success"));
}
return {};
}
QString UserIdsWidget::Private::trustLevelText(const GpgME::UserID &uid) const
{
return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid);
}
void UserIdsWidget::Private::addUserID()
{
auto cmd = new Kleo::Commands::AddUserIDCommand(key);
QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished, q, [this]() {
addUserIDBtn->setEnabled(true);
Q_EMIT q->updateKey();
});
addUserIDBtn->setEnabled(false);
cmd->start();
}
void UserIdsWidget::Private::setPrimaryUserID(const GpgME::UserID &uid)
{
auto userId = uid;
if (userId.isNull()) {
const auto userIDs = selectedUserIDs(userIDTable);
if (userIDs.size() != 1) {
return;
}
userId = userIDs.front();
}
auto cmd = new Kleo::Commands::SetPrimaryUserIDCommand(userId);
connect(cmd, &Kleo::Commands::SetPrimaryUserIDCommand::finished, q, [this]() {
userIDTable->setEnabled(true);
// the Flag As Primary button will be updated by the key update
Q_EMIT q->updateKey();
});
userIDTable->setEnabled(false);
setPrimaryUserIDAction->setEnabled(false);
cmd->start();
}
void UserIdsWidget::Private::certifyUserIDs()
{
const auto userIDs = selectedUserIDs(userIDTable);
auto cmd = userIDs.empty() ? new Kleo::Commands::CertifyCertificateCommand{key} //
: new Kleo::Commands::CertifyCertificateCommand{userIDs};
connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() {
Q_EMIT q->updateKey();
certifyAction->setEnabled(true);
});
certifyAction->setEnabled(false);
cmd->start();
}
void UserIdsWidget::Private::revokeCertifications()
{
const auto userIDs = selectedUserIDs(userIDTable);
auto cmd = userIDs.empty() ? new Kleo::Commands::RevokeCertificationCommand{key} //
: new Kleo::Commands::RevokeCertificationCommand{userIDs};
connect(cmd, &Kleo::Command::finished, q, [this]() {
Q_EMIT q->updateKey();
revokeCertificationsAction->setEnabled(true);
});
revokeCertificationsAction->setEnabled(false);
cmd->start();
}
void UserIdsWidget::Private::revokeUserID(const GpgME::UserID &userId)
{
const QString message =
xi18nc("@info", "<para>Do you really want to revoke the user ID<nl/><emphasis>%1</emphasis> ?</para>", QString::fromUtf8(userId.id()));
auto confirmButton = KStandardGuiItem::ok();
confirmButton.setText(i18nc("@action:button", "Revoke User ID"));
confirmButton.setToolTip({});
const auto choice = KMessageBox::questionTwoActions(q->window(),
message,
i18nc("@title:window", "Confirm Revocation"),
confirmButton,
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::WindowModal);
if (choice != KMessageBox::ButtonCode::PrimaryAction) {
return;
}
auto cmd = new Commands::RevokeUserIDCommand(userId);
cmd->setParentWidget(q);
connect(cmd, &Command::finished, q, [this]() {
userIDTable->setEnabled(true);
// the Revoke User ID button will be updated by the key update
Q_EMIT q->updateKey();
});
userIDTable->setEnabled(false);
revokeUserIDBtn->setEnabled(false);
cmd->start();
}
void UserIdsWidget::Private::revokeSelectedUserID()
{
const auto userIDs = selectedUserIDs(userIDTable);
if (userIDs.size() != 1) {
return;
}
revokeUserID(userIDs.front());
}
void UserIdsWidget::Private::userIDTableContextMenuRequested(const QPoint &p)
{
const auto userIDs = selectedUserIDs(userIDTable);
const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{};
const bool isPrimaryUserID = !singleUserID.isNull() && (userIDTable->selectedItems().front() == userIDTable->topLevelItem(0));
const bool canSignUserIDs = userHasCertificationKey();
const auto isLocalKey = !isRemoteKey(key);
const auto keyCanBeCertified = Kleo::canBeCertified(key);
auto menu = new QMenu(q);
if (key.hasSecret()) {
auto action =
menu->addAction(QIcon::fromTheme(QStringLiteral("favorite")), i18nc("@action:inmenu", "Flag as Primary User ID"), q, [this, singleUserID]() {
setPrimaryUserID(singleUserID);
});
action->setEnabled(!singleUserID.isNull() //
&& !isPrimaryUserID //
&& !Kleo::isRevokedOrExpired(singleUserID) //
&& canBeUsedForSecretKeyOperations(key));
}
{
const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...")
: i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size());
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), actionText, q, [this]() {
certifyUserIDs();
});
action->setEnabled(isLocalKey && keyCanBeCertified && canSignUserIDs);
}
if (Kleo::Commands::RevokeCertificationCommand::isSupported()) {
const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Revoke Certifications...")
: i18ncp("@action:inmenu", "Revoke Certification...", "Revoke Certifications...", userIDs.size());
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), actionText, q, [this]() {
revokeCertifications();
});
action->setEnabled(isLocalKey && canSignUserIDs);
}
#ifdef MAILAKONADI_ENABLED
if (key.hasSecret()) {
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")),
i18nc("@action:inmenu", "Publish at Mail Provider ..."),
q,
[this, singleUserID]() {
auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(singleUserID);
userIDTable->setEnabled(false);
connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished, q, [this]() {
userIDTable->setEnabled(true);
});
cmd->start();
});
action->setEnabled(!singleUserID.isNull());
}
#endif // MAILAKONADI_ENABLED
{
auto action =
menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action:inmenu", "Revoke User ID"), q, [this, singleUserID]() {
revokeUserID(singleUserID);
});
action->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID));
}
connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
menu->popup(userIDTable->viewport()->mapToGlobal(p));
}
void UserIdsWidget::setUpdateInProgress(bool updateInProgress)
{
d->updateInProgress = updateInProgress;
}
#include "moc_useridswidget.cpp"
diff --git a/src/view/cardkeysview.cpp b/src/view/cardkeysview.cpp
index eee754f7b..1f5a233aa 100644
--- a/src/view/cardkeysview.cpp
+++ b/src/view/cardkeysview.cpp
@@ -1,754 +1,755 @@
/*
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/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/Debug>
#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, 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
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Dec 5, 9:28 AM (1 d, 15 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
22/dc/4348c060137116b291becbf07404
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment