Page MenuHome GnuPG

No OneTemporary

diff --git a/src/view/cardkeysview.cpp b/src/view/cardkeysview.cpp
index 4d5f56cf8..a85ddc8f9 100644
--- a/src/view/cardkeysview.cpp
+++ b/src/view/cardkeysview.cpp
@@ -1,592 +1,604 @@
/* view/cardkeysview.cpp
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 <tooltippreferences.h>
#include <kleopatra_debug.h>
#include <commands/detailscommand.h>
#include <smartcard/card.h>
#include <smartcard/readerstatus.h>
#include <utils/gui-helper.h>
#include <view/progressoverlay.h>
#include <view/smartcardactions.h>
#include <Libkleo/Debug>
#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/TreeWidget>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QHeaderView>
#include <QLabel>
#include <QMenu>
#include <QToolButton>
#include <QVBoxLayout>
#include <gpgme++/context.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 TooltipPreferences prefs;
int flags = KeyID;
flags |= prefs.showValidity() ? validityFlags : 0;
flags |= prefs.showOwnerInformation() ? ownerFlags : 0;
flags |= prefs.showCertificateDetails() ? detailsFlags : 0;
return flags;
}
namespace
{
enum ColumnIndex {
Slot,
KeyGrip,
Usage,
Created,
Fingerprint,
Certificate,
Actions,
};
}
namespace
{
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("encryption");
case 's':
return i18n("signatures");
case 'c':
return i18n("certification");
case 'a':
return i18n("authentication");
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, CardKeysView::Options options)
{
Q_ASSERT(item);
// slot
item->setData(Slot, Qt::DisplayRole, QString::number(item->slotIndex() + 1));
// 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 (!(options & CardKeysView::NoCreated)) {
item->setData(Created, Qt::DisplayRole, QString::fromStdString(keyInfo.keyTime));
}
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, QString{});
item->setData(Certificate, Qt::ToolTipRole, QString{});
} else {
// fingerprint
item->setData(Fingerprint, Qt::DisplayRole, Formatting::prettyID(subkey.fingerprint()));
item->setData(Fingerprint, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.fingerprint()));
// certificate
if (subkey.parent().protocol() == GpgME::OpenPGP) {
item->setData(Certificate, Qt::DisplayRole, Formatting::prettyUserID(subkey.parent().userID(0)));
} else {
item->setData(Certificate, Qt::DisplayRole, DN(subkey.parent().userID(0).id()).prettyDN());
}
item->setData(Certificate, Qt::ToolTipRole, Formatting::toolTip(subkey.parent(), toolTipOptions()));
}
}
static std::vector<QAction *> actionsForCardSlot(SmartCard::AppType appType)
{
switch (appType) {
case AppType::NetKeyApp:
case AppType::P15App:
return SmartCardActions::instance()->actions({u"card_slot_show_certificate_details"_s});
case AppType::OpenPGPApp:
+ break;
case AppType::PIVApp:
+ return SmartCardActions::instance()->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,
+ });
case AppType::NoApp:
break;
};
return {};
}
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:
case AppType::P15App:
return !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);
// set a smaller default column size (default for most styles is 100) so that the Actions column doesn't get too wide by default
mTreeWidget->header()->setDefaultSectionSize(20);
mTreeWidget->setHeaderLabels({
i18nc("@title:column Key slot of a smart card", "Slot"),
i18nc("@title:column", "Keygrip"),
i18nc("@title:column", "Usage"),
i18nc("@title:column", "Created"),
i18nc("@title:column", "Fingerprint"),
i18nc("@title:column", "Certificate"),
i18nc("@title:column", "Actions"),
});
if (mOptions & NoCreated) {
mTreeWidget->forceColumnHidden(Created);
}
mainLayout->addWidget(mTreeWidget);
+ connect(mTreeWidget, &QTreeWidget::currentItemChanged, this, [this]() {
+ Q_EMIT currentCardSlotChanged();
+ });
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(nullptr);
});
}
CardKeysView::~CardKeysView() = default;
void CardKeysView::setCard(const Card *card)
{
mSerialNumber = card->serialNumber();
mAppName = card->appName();
mAppType = card->appType();
updateKeyList(card);
}
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(const Card *card)
{
qCDebug(KLEOPATRA_LOG) << __func__;
const bool firstSetUp = (mTreeWidget->topLevelItemCount() == 0);
if (mSerialNumber.empty()) {
// ignore KeyCache::keysMayHaveChanged signal until the card has been set
return;
}
const auto cardRefHolder = card ? std::shared_ptr<Card>{} : ReaderStatus::instance()->getCard(mSerialNumber, mAppName);
if (!card) {
card = cardRefHolder.get();
}
if (!card) {
qCDebug(KLEOPATRA_LOG) << "Failed to find the" << mAppName << "smart card with the serial number" << mSerialNumber;
return;
}
std::vector<std::string> keyRefsWithoutSMimeCertificate;
const auto cardKeyInfos = card->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(card, slotIndex, keyInfo, Subkey{});
} else {
updateTreeWidgetItem(items.front(), keyInfo, Subkey{}, mOptions);
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(card, 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);
updateTreeWidgetItem(items.front(), keyInfo, subkeys.front(), mOptions);
const int itemIndex = mTreeWidget->indexOfTopLevelItem(items.front());
for (int i = 1; i < int(subkeys.size()); ++i) {
insertTreeWidgetItem(card, 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, mOptions);
++i;
++s;
} else {
// this subkey is new; insert it before the current item
const int itemIndex = mTreeWidget->indexOfTopLevelItem(item);
insertTreeWidgetItem(card, 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(card, 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(mAppName))) {
mTreeWidget->header()->resizeSections(QHeaderView::ResizeToContents);
}
ensureCertificatesAreValidated();
if (firstSetUp && canImportCertificates(card, keyRefsWithoutSMimeCertificate)) {
// the card contains keys we don't know; try to learn them from the card
learnCard();
}
}
void CardKeysView::insertTreeWidgetItem(const Card *card, 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, mOptions);
mTreeWidget->insertTopLevelItem(index, item);
auto actionsButton = addActionsButton(item, card->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(QTreeWidgetItem *item, SmartCard::AppType appType)
{
const auto actions = actionsForCardSlot(appType);
auto button = new QToolButton;
if (actions.size() == 1) {
button->setDefaultAction(actions.front());
// 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(action);
}
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();
}
void CardKeysView::learnCard()
{
qCDebug(KLEOPATRA_LOG) << __func__;
mTreeViewOverlay->setText(i18nc("@info", "Reading certificates from smart card ..."));
mTreeViewOverlay->showOverlay();
ReaderStatus::mutableInstance()->learnCards(GpgME::CMS);
connect(ReaderStatus::instance(), &ReaderStatus::cardsLearned, this, [this]() {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::cardsLearned";
mTreeViewOverlay->hideOverlay();
});
}
#include "moc_cardkeysview.cpp"
diff --git a/src/view/cardkeysview.h b/src/view/cardkeysview.h
index 042851e58..91876d6fd 100644
--- a/src/view/cardkeysview.h
+++ b/src/view/cardkeysview.h
@@ -1,92 +1,95 @@
/* view/cardkeysview.h
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
*/
#pragma once
#include <Libkleo/Predicates>
#include <QHash>
#include <QWidget>
#include <set>
#include <string>
#include <vector>
class QAction;
class QToolButton;
class QTreeWidgetItem;
namespace GpgME
{
class Key;
class KeyListResult;
}
namespace Kleo
{
class ProgressOverlay;
class TreeWidget;
namespace SmartCard
{
enum class AppType;
class Card;
struct KeyPairInfo;
}
class CardKeysView : public QWidget
{
Q_OBJECT
public:
enum Option {
// clang-format off
NoOptions = 0x0000,
NoCreated = 0x0001, // no "Created" column
// clang-format on
};
Q_DECLARE_FLAGS(Options, Option)
explicit CardKeysView(QWidget *parent, Options options = NoOptions);
~CardKeysView() override;
void setCard(const SmartCard::Card *card);
std::string currentCardSlot() const;
GpgME::Key currentCertificate() const;
+Q_SIGNALS:
+ void currentCardSlotChanged() const;
+
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
void updateKeyList(const SmartCard::Card *card = nullptr);
void
insertTreeWidgetItem(const SmartCard::Card *card, int slotIndex, const SmartCard::KeyPairInfo &keyInfo, const GpgME::Subkey &subkey, int treeIndex = -1);
QToolButton *addActionsButton(QTreeWidgetItem *item, SmartCard::AppType cardType);
void ensureCertificatesAreValidated();
void startCertificateValidation(const std::vector<GpgME::Key> &certificates);
void certificateValidationDone(const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys);
void learnCard();
private:
Options mOptions;
std::string mSerialNumber;
std::string mAppName;
Kleo::SmartCard::AppType mAppType;
std::vector<GpgME::Key> mCertificates; // only S/MIME certificates
using KeySet = std::set<GpgME::Key, _detail::ByFingerprint<std::less>>;
KeySet mValidatedCertificates;
TreeWidget *mTreeWidget = nullptr;
ProgressOverlay *mTreeViewOverlay = nullptr;
};
} // namespace Kleo
Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::CardKeysView::Options)
diff --git a/src/view/netkeywidget.cpp b/src/view/netkeywidget.cpp
index fdf4c1afb..2c6d6b109 100644
--- a/src/view/netkeywidget.cpp
+++ b/src/view/netkeywidget.cpp
@@ -1,242 +1,243 @@
/* view/netkeywidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "netkeywidget.h"
#include "cardkeysview.h"
#include "kleopatraapplication.h"
#include "nullpinwidget.h"
#include "systrayicon.h"
#include "kleopatra_debug.h"
#include "smartcard/netkeycard.h"
#include "smartcard/readerstatus.h"
#include "commands/changepincommand.h"
#include "commands/createcsrforcardkeycommand.h"
#include "commands/createopenpgpkeyfromcardkeyscommand.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Compliance>
#include <Libkleo/Debug>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/KeyListModel>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <gpgme++/engineinfo.h>
using namespace Kleo;
using namespace Kleo::SmartCard;
using namespace Kleo::Commands;
NetKeyWidget::NetKeyWidget(QWidget *parent)
: SmartCardWidget(parent)
{
mNullPinWidget = new NullPinWidget{this};
mContentLayout->addWidget(mNullPinWidget);
mErrorLabel = new QLabel{this};
mErrorLabel->setVisible(false);
mContentLayout->addWidget(mErrorLabel);
mCardKeysView = new CardKeysView{this, CardKeysView::NoCreated};
mContentLayout->addWidget(mCardKeysView, 1);
+ connect(mCardKeysView, &CardKeysView::currentCardSlotChanged, this, &SmartCardWidget::updateActions);
// The action area
auto actionLayout = new QHBoxLayout();
if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
mKeyForCardKeysButton = new QPushButton(this);
mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key"));
mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card."));
actionLayout->addWidget(mKeyForCardKeysButton);
connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &NetKeyWidget::createKeyFromCardKeys);
}
if (!(engineInfo(GpgME::GpgSMEngine).engineVersion() < "2.2.26")) { // see https://dev.gnupg.org/T5184
mCreateCSRButton = new QPushButton(this);
mCreateCSRButton->setText(i18nc("@action:button", "Create CSR"));
mCreateCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for a key stored on the card."));
mCreateCSRButton->setEnabled(false);
actionLayout->addWidget(mCreateCSRButton);
connect(mCreateCSRButton, &QPushButton::clicked, this, [this]() {
createCSR();
});
}
mChangeNKSPINBtn = new QPushButton{this};
mChangeNKSPINBtn->setText(i18nc("@action:button NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN"));
mChangeSigGPINBtn = new QPushButton{this};
mChangeSigGPINBtn->setText(i18nc("@action:button SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN"));
connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this]() {
doChangePin(NetKeyCard::nksPinKeyRef());
});
connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this]() {
doChangePin(NetKeyCard::sigGPinKeyRef());
});
actionLayout->addWidget(mChangeNKSPINBtn);
actionLayout->addWidget(mChangeSigGPINBtn);
actionLayout->addStretch(1);
mContentLayout->addLayout(actionLayout);
}
NetKeyWidget::~NetKeyWidget() = default;
namespace
{
std::vector<KeyPairInfo> getKeysSuitableForCSRCreation(const NetKeyCard *netKeyCard)
{
if (netKeyCard->hasNKSNullPin()) {
return {};
}
std::vector<KeyPairInfo> keys;
Kleo::copy_if(netKeyCard->keyInfos(), std::back_inserter(keys), [](const auto &keyInfo) {
if (keyInfo.keyRef.substr(0, 9) == "NKS-SIGG.") {
// SigG certificates for qualified signatures are issued with the physical cards;
// it's not possible to request a certificate for them
return false;
}
return keyInfo.canSign() //
&& (keyInfo.keyRef.substr(0, 9) == "NKS-NKS3.") //
&& DeVSCompliance::algorithmIsCompliant(keyInfo.algorithm);
});
return keys;
}
}
void NetKeyWidget::setCard(const NetKeyCard *card)
{
SmartCardWidget::setCard(card);
mNullPinWidget->setSerialNumber(serialNumber());
/* According to users of NetKey Cards it is fairly uncommon
* to use SigG Certificates at all. So it should be optional to set the pins. */
mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/);
mNullPinWidget->setSigGVisible(false /*card->hasSigGNullPin()*/);
mNullPinWidget->setNKSVisible(card->hasNKSNullPin());
mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin());
if (card->hasSigGNullPin()) {
mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Set SigG PIN"));
} else {
mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN"));
}
const auto errMsg = card->errorMsg();
if (!errMsg.isEmpty()) {
mErrorLabel->setText(QStringLiteral("<b>%1:</b> %2").arg(i18n("Error"), errMsg));
mErrorLabel->setVisible(true);
} else {
mErrorLabel->setVisible(false);
}
if (mKeyForCardKeysButton) {
mKeyForCardKeysButton->setEnabled(!card->hasNKSNullPin() && card->hasSigningKey() && card->hasEncryptionKey()
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
}
if (mCreateCSRButton) {
mCreateCSRButton->setEnabled(!getKeysSuitableForCSRCreation(card).empty());
}
mCardKeysView->setCard(card);
}
void NetKeyWidget::doChangePin(const std::string &keyRef)
{
const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(serialNumber());
if (!netKeyCard) {
KMessageBox::error(this, i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber())));
return;
}
auto cmd = new ChangePinCommand(serialNumber(), NetKeyCard::AppName, this);
this->setEnabled(false);
connect(cmd, &ChangePinCommand::finished, this, [this]() {
this->setEnabled(true);
});
cmd->setKeyRef(keyRef);
if ((keyRef == NetKeyCard::nksPinKeyRef() && netKeyCard->hasNKSNullPin()) //
|| (keyRef == NetKeyCard::sigGPinKeyRef() && netKeyCard->hasSigGNullPin())) {
cmd->setMode(ChangePinCommand::NullPinMode);
}
cmd->start();
}
void NetKeyWidget::createKeyFromCardKeys()
{
auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(serialNumber(), NetKeyCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
namespace
{
std::string getKeyRef(const std::vector<KeyPairInfo> &keys, QWidget *parent)
{
QStringList options;
for (const auto &key : keys) {
options << QStringLiteral("%1 - %2").arg(QString::fromStdString(key.keyRef), QString::fromStdString(key.grip));
}
bool ok;
const QString choice = QInputDialog::getItem(parent,
i18n("Select Key"),
i18n("Please select the key you want to create a certificate signing request for:"),
options,
/* current= */ 0,
/* editable= */ false,
&ok);
return ok ? keys[options.indexOf(choice)].keyRef : std::string();
}
}
void NetKeyWidget::createCSR()
{
const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(serialNumber());
if (!netKeyCard) {
KMessageBox::error(this, i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber())));
return;
}
const auto suitableKeys = getKeysSuitableForCSRCreation(netKeyCard.get());
if (suitableKeys.empty()) {
KMessageBox::error(this, i18n("Sorry! No keys suitable for creating a certificate signing request found on the smartcard."));
return;
}
const auto keyRef = getKeyRef(suitableKeys, this);
if (keyRef.empty()) {
return;
}
auto cmd = new CreateCSRForCardKeyCommand(keyRef, serialNumber(), NetKeyCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
#include "moc_netkeywidget.cpp"
diff --git a/src/view/p15cardwidget.cpp b/src/view/p15cardwidget.cpp
index d75cc1d29..2a721acbe 100644
--- a/src/view/p15cardwidget.cpp
+++ b/src/view/p15cardwidget.cpp
@@ -1,120 +1,121 @@
/* view/p15cardwiget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Andre Heinecke <aheinecke@g10code.com>
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "p15cardwidget.h"
#include "cardkeysview.h"
#include "settings.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/p15card.h"
#include "smartcard/readerstatus.h"
#include <QLabel>
#include <QPushButton>
#include <QStringList>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <KSeparator>
#include <Libkleo/Compat>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <QGpgME/CryptoConfig>
#include <QGpgME/ImportFromKeyserverJob>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <gpgme++/importresult.h>
#include <gpgme++/keylistresult.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
P15CardWidget::P15CardWidget(QWidget *parent)
: SmartCardWidget{parent}
{
mStatusLabel = new QLabel{this};
mStatusLabel->setVisible(false);
mContentLayout->addWidget(mStatusLabel);
mCardKeysView = new CardKeysView{this, CardKeysView::NoCreated};
mCardKeysView->setVisible(false);
mContentLayout->addWidget(mCardKeysView);
+ connect(mCardKeysView, &CardKeysView::currentCardSlotChanged, this, &SmartCardWidget::updateActions);
}
P15CardWidget::~P15CardWidget() = default;
void P15CardWidget::searchPGPFpr(const std::string &fpr)
{
/* Only do auto import from LDAP */
auto conf = QGpgME::cryptoConfig();
Q_ASSERT(conf);
if (!Settings().alwaysSearchCardOnKeyserver() && !Kleo::keyserver().startsWith(QLatin1StringView{"ldap"})) {
return;
}
mStatusLabel->setText(i18n("Searching in directory service..."));
mStatusLabel->setVisible(true);
qCDebug(KLEOPATRA_LOG) << "Looking for:" << fpr.c_str() << "on ldap server";
QGpgME::KeyListJob *job = QGpgME::openpgp()->keyListJob(true);
connect(job, &QGpgME::KeyListJob::result, job, [this](GpgME::KeyListResult, std::vector<GpgME::Key> keys, QString, GpgME::Error) {
if (keys.size() == 1) {
auto importJob = QGpgME::openpgp()->importFromKeyserverJob();
qCDebug(KLEOPATRA_LOG) << "Importing: " << keys[0].primaryFingerprint();
connect(importJob, &QGpgME::ImportFromKeyserverJob::result, importJob, [this](GpgME::ImportResult, QString, GpgME::Error) {
qCDebug(KLEOPATRA_LOG) << "import job done";
mStatusLabel->setText(i18n("Automatic import finished."));
});
importJob->start(keys);
} else if (keys.size() > 1) {
qCDebug(KLEOPATRA_LOG) << "Multiple keys found on server";
mStatusLabel->setText(i18n("Error multiple keys found on server."));
} else {
qCDebug(KLEOPATRA_LOG) << "No key found";
mStatusLabel->setText(i18n("Key not found in directory service."));
}
});
job->start(QStringList() << QString::fromStdString(fpr));
}
void P15CardWidget::setCard(const P15Card *card)
{
SmartCardWidget::setCard(card);
const auto sigInfo = card->keyInfo(card->signingKeyRef());
if (!sigInfo.grip.empty()) {
const auto key = KeyCache::instance()->findSubkeyByKeyGrip(sigInfo.grip, GpgME::OpenPGP).parent();
if (key.isNull()) {
qCDebug(KLEOPATRA_LOG) << "Failed to find key for grip:" << sigInfo.grip.c_str();
const auto pgpSigFpr = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef());
if (!pgpSigFpr.empty()) {
qCDebug(KLEOPATRA_LOG) << "Should be pgp key:" << pgpSigFpr.c_str();
searchPGPFpr(pgpSigFpr);
}
} else {
mStatusLabel->setVisible(false);
}
}
/* Check if additional keys could be available */
if (!Settings().autoLoadP15Certs()) {
return;
}
mCardKeysView->setVisible(true);
mCardKeysView->setCard(card);
}
#include "moc_p15cardwidget.cpp"
diff --git a/src/view/pivcardwidget.cpp b/src/view/pivcardwidget.cpp
index f054565c9..90c617b94 100644
--- a/src/view/pivcardwidget.cpp
+++ b/src/view/pivcardwidget.cpp
@@ -1,358 +1,132 @@
/* view/pivcardwiget.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 "pivcardwidget.h"
-#include "tooltippreferences.h"
+#include "cardkeysview.h"
-#include "commands/certificatetopivcardcommand.h"
#include "commands/changepincommand.h"
-#include "commands/createcsrforcardkeycommand.h"
#include "commands/createopenpgpkeyfromcardkeyscommand.h"
-#include "commands/importcertificatefrompivcardcommand.h"
-#include "commands/keytocardcommand.h"
-#include "commands/pivgeneratecardkeycommand.h"
#include "commands/setpivcardapplicationadministrationkeycommand.h"
#include "smartcard/pivcard.h"
-#include "smartcard/readerstatus.h"
#include <Libkleo/Compliance>
-#include <Libkleo/Dn>
-#include <Libkleo/Formatting>
-#include <Libkleo/KeyCache>
#include <KLocalizedString>
-#include <KSeparator>
-#include <QGridLayout>
-#include <QLabel>
+#include <QHBoxLayout>
#include <QPushButton>
-#include <QVBoxLayout>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
-namespace
-{
-static void layoutKeyWidgets(QGridLayout *grid, const QString &keyName, const PIVCardWidget::KeyWidgets &keyWidgets)
-{
- int row = grid->rowCount();
- grid->addWidget(new QLabel(keyName), row, 0);
- grid->addWidget(keyWidgets.keyGrip, row, 1);
- grid->addWidget(keyWidgets.keyAlgorithm, row, 2);
- grid->addWidget(keyWidgets.generateButton, row, 3);
- if (keyWidgets.writeKeyButton) {
- grid->addWidget(keyWidgets.writeKeyButton, row, 4);
- }
-
- row++;
- grid->addWidget(keyWidgets.certificateInfo, row, 1, 1, 2);
- grid->addWidget(keyWidgets.writeCertificateButton, row, 3);
- grid->addWidget(keyWidgets.importCertificateButton, row, 4);
- if (keyWidgets.createCSRButton) {
- grid->addWidget(keyWidgets.createCSRButton, row, 5);
- }
-}
-
-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 TooltipPreferences prefs;
-
- int flags = KeyID;
- flags |= prefs.showValidity() ? validityFlags : 0;
- flags |= prefs.showOwnerInformation() ? ownerFlags : 0;
- flags |= prefs.showCertificateDetails() ? detailsFlags : 0;
- return flags;
-}
-}
-
PIVCardWidget::PIVCardWidget(QWidget *parent)
: SmartCardWidget(parent)
{
- mContentLayout->addWidget(new KSeparator(Qt::Horizontal));
-
- mContentLayout->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:")), this));
-
- auto keysGrid = new QGridLayout;
- for (const auto &keyInfo : PIVCard::supportedKeys()) {
- KeyWidgets keyWidgets = createKeyWidgets(keyInfo);
- layoutKeyWidgets(keysGrid, PIVCard::keyDisplayName(keyInfo.keyRef), keyWidgets);
- }
- mContentLayout->addLayout(keysGrid);
-
- mContentLayout->addWidget(new KSeparator(Qt::Horizontal));
+ mCardKeysView = new CardKeysView{this, CardKeysView::NoCreated};
+ mContentLayout->addWidget(mCardKeysView);
+ connect(mCardKeysView, &CardKeysView::currentCardSlotChanged, this, &SmartCardWidget::updateActions);
auto actionLayout = new QHBoxLayout;
if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
mKeyForCardKeysButton = new QPushButton(this);
mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key"));
mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card."));
actionLayout->addWidget(mKeyForCardKeysButton);
connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PIVCardWidget::createKeyFromCardKeys);
}
{
auto button = new QPushButton(i18nc("@action:button", "Change PIN"), this);
button->setToolTip(i18nc("@info:tooltip",
"Change the PIV Card Application PIN that activates the PIV Card "
"and enables private key operations using the stored keys."));
actionLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, [this]() {
changePin(PIVCard::pinKeyRef());
});
}
{
auto button = new QPushButton(i18nc("@action:button", "Change PUK"), this);
button->setToolTip(i18nc("@info:tooltip", "Change the PIN Unblocking Key that enables a reset of the PIN."));
actionLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, [this]() {
changePin(PIVCard::pukKeyRef());
});
}
{
auto button = new QPushButton(i18nc("@action:button", "Change Admin Key"), this);
button->setToolTip(i18nc("@info:tooltip",
"Change the PIV Card Application Administration Key that is used by the "
"PIV Card Application to authenticate the PIV Card Application Administrator and by the "
"administrator (resp. Kleopatra) to authenticate the PIV Card Application."));
actionLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, [this]() {
setAdminKey();
});
}
actionLayout->addStretch(-1);
mContentLayout->addLayout(actionLayout);
-
- mContentLayout->addStretch(1);
-}
-
-PIVCardWidget::KeyWidgets PIVCardWidget::createKeyWidgets(const KeyPairInfo &keyInfo)
-{
- const std::string keyRef = keyInfo.keyRef;
- KeyWidgets keyWidgets;
- keyWidgets.keyGrip = new QLabel(this);
- keyWidgets.keyGrip->setTextInteractionFlags(Qt::TextBrowserInteraction);
- keyWidgets.keyAlgorithm = new QLabel(this);
- keyWidgets.keyAlgorithm->setTextInteractionFlags(Qt::TextSelectableByMouse);
- keyWidgets.generateButton = new QPushButton(i18nc("@action:button", "Generate"), this);
- keyWidgets.generateButton->setEnabled(false);
- connect(keyWidgets.generateButton, &QPushButton::clicked, this, [this, keyRef]() {
- generateKey(keyRef);
- });
- if (keyRef == PIVCard::cardAuthenticationKeyRef() || keyRef == PIVCard::keyManagementKeyRef()) {
- keyWidgets.writeKeyButton = new QPushButton(i18nc("@action:button", "Write Key"), this);
- keyWidgets.writeKeyButton->setToolTip(i18nc("@info:tooltip", "Write the key pair of a certificate to the card"));
- keyWidgets.writeKeyButton->setEnabled(true);
- connect(keyWidgets.writeKeyButton, &QPushButton::clicked, this, [this, keyRef]() {
- writeKeyToCard(keyRef);
- });
- }
- keyWidgets.certificateInfo = new QLabel(this);
- keyWidgets.certificateInfo->setTextInteractionFlags(Qt::TextBrowserInteraction);
- keyWidgets.writeCertificateButton = new QPushButton(i18nc("@action:button", "Write Certificate"), this);
- keyWidgets.writeCertificateButton->setToolTip(i18nc("@info:tooltip", "Write the certificate corresponding to this key to the card"));
- keyWidgets.writeCertificateButton->setEnabled(false);
- connect(keyWidgets.writeCertificateButton, &QPushButton::clicked, this, [this, keyRef]() {
- writeCertificateToCard(keyRef);
- });
- keyWidgets.importCertificateButton = new QPushButton(i18nc("@action:button", "Import Certificate"), this);
- keyWidgets.importCertificateButton->setToolTip(i18nc("@info:tooltip", "Import the certificate stored on the card"));
- keyWidgets.importCertificateButton->setEnabled(false);
- connect(keyWidgets.importCertificateButton, &QPushButton::clicked, this, [this, keyRef]() {
- importCertificateFromCard(keyRef);
- });
- if (keyInfo.canSign() || keyInfo.canEncrypt()) {
- keyWidgets.createCSRButton = new QPushButton(i18nc("@action:button", "Create CSR"), this);
- keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key"));
- keyWidgets.createCSRButton->setEnabled(false);
- connect(keyWidgets.createCSRButton, &QPushButton::clicked, this, [this, keyRef]() {
- createCSR(keyRef);
- });
- }
- mKeyWidgets.insert({keyRef, keyWidgets});
- return keyWidgets;
}
PIVCardWidget::~PIVCardWidget()
{
}
void PIVCardWidget::setCard(const PIVCard *card)
{
SmartCardWidget::setCard(card);
- if (card) {
- updateCachedValues(PIVCard::pivAuthenticationKeyRef(), card);
- updateCachedValues(PIVCard::cardAuthenticationKeyRef(), card);
- updateCachedValues(PIVCard::digitalSignatureKeyRef(), card);
- updateCachedValues(PIVCard::keyManagementKeyRef(), card);
- }
- updateKeyWidgets(PIVCard::pivAuthenticationKeyRef());
- updateKeyWidgets(PIVCard::cardAuthenticationKeyRef());
- updateKeyWidgets(PIVCard::digitalSignatureKeyRef());
- updateKeyWidgets(PIVCard::keyManagementKeyRef());
-
if (mKeyForCardKeysButton) {
mKeyForCardKeysButton->setEnabled(card->hasSigningKey() //
&& card->hasEncryptionKey() //
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
}
-}
-void PIVCardWidget::updateCachedValues(const std::string &keyRef, const SmartCard::PIVCard *card)
-{
- KeyWidgets &widgets = mKeyWidgets.at(keyRef);
- widgets.keyInfo = card->keyInfo(keyRef);
- widgets.certificateData = card->certificateData(keyRef);
-}
-
-void PIVCardWidget::updateKeyWidgets(const std::string &keyRef)
-{
- const KeyWidgets &widgets = mKeyWidgets.at(keyRef);
- const std::string grip = widgets.keyInfo.grip;
- if (grip.empty()) {
- widgets.certificateInfo->setText(i18nc("@info", "<em>slot empty</em>"));
- widgets.certificateInfo->setToolTip(QString());
- widgets.keyGrip->setText(QString());
- widgets.keyAlgorithm->setText(QString());
- widgets.generateButton->setText(i18nc("@action:button", "Generate"));
- widgets.generateButton->setToolTip(i18nc("@info:tooltip %1 display name of a key", "Generate %1", PIVCard::keyDisplayName(keyRef)));
- if (widgets.createCSRButton) {
- widgets.createCSRButton->setEnabled(false);
- }
- widgets.writeCertificateButton->setEnabled(false);
- widgets.importCertificateButton->setEnabled(false);
- } else {
- const Key certificate = KeyCache::instance()->findSubkeyByKeyGrip(grip, GpgME::CMS).parent();
- if (!certificate.isNull()) {
- widgets.certificateInfo->setText(i18nc("X.509 certificate DN (validity, created: date)",
- "%1 (%2, created: %3)",
- DN(certificate.userID(0).id()).prettyDN(),
- Formatting::complianceStringShort(certificate),
- Formatting::creationDateString(certificate)));
- widgets.certificateInfo->setToolTip(Formatting::toolTip(certificate, toolTipOptions()));
- widgets.writeCertificateButton->setEnabled(true);
- } else {
- widgets.certificateInfo->setText(i18nc("@info", "<em>no matching certificate</em>"));
- widgets.certificateInfo->setToolTip(QString());
- widgets.writeCertificateButton->setEnabled(false);
- }
- widgets.keyGrip->setText(QString::fromStdString(grip));
- const std::string algo = widgets.keyInfo.algorithm;
- widgets.keyAlgorithm->setText(algo.empty() ? i18nc("@info unknown key algorithm", "unknown") : QString::fromStdString(algo));
- widgets.importCertificateButton->setEnabled(!widgets.certificateData.empty());
-
- widgets.generateButton->setText(i18nc("@action:button", "Replace"));
- widgets.generateButton->setToolTip(i18nc("@info:tooltip %1 display name of a key", "Replace %1 with new key", PIVCard::keyDisplayName(keyRef)));
- if (widgets.createCSRButton) {
- widgets.createCSRButton->setEnabled(DeVSCompliance::algorithmIsCompliant(algo));
- }
- }
-
- widgets.generateButton->setEnabled(true);
-}
-
-void PIVCardWidget::generateKey(const std::string &keyref)
-{
- auto cmd = new PIVGenerateCardKeyCommand(serialNumber(), this);
- this->setEnabled(false);
- connect(cmd, &PIVGenerateCardKeyCommand::finished, this, [this]() {
- this->setEnabled(true);
- });
- cmd->setKeyRef(keyref);
- cmd->start();
-}
-
-void PIVCardWidget::createCSR(const std::string &keyref)
-{
- auto cmd = new CreateCSRForCardKeyCommand(keyref, serialNumber(), PIVCard::AppName, this);
- this->setEnabled(false);
- connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() {
- this->setEnabled(true);
- });
- cmd->start();
-}
-
-void PIVCardWidget::writeCertificateToCard(const std::string &keyref)
-{
- auto cmd = new CertificateToPIVCardCommand(keyref, serialNumber());
- this->setEnabled(false);
- connect(cmd, &CertificateToPIVCardCommand::finished, this, [this]() {
- this->setEnabled(true);
- });
- cmd->setParentWidget(this);
- cmd->start();
-}
-
-void PIVCardWidget::importCertificateFromCard(const std::string &keyref)
-{
- auto cmd = new ImportCertificateFromPIVCardCommand(keyref, serialNumber());
- this->setEnabled(false);
- connect(cmd, &ImportCertificateFromPIVCardCommand::finished, this, [this, keyref]() {
- this->updateKeyWidgets(keyref);
- this->setEnabled(true);
- });
- cmd->setParentWidget(this);
- cmd->start();
-}
-
-void PIVCardWidget::writeKeyToCard(const std::string &keyref)
-{
- auto cmd = new KeyToCardCommand(keyref, serialNumber(), PIVCard::AppName);
- this->setEnabled(false);
- connect(cmd, &KeyToCardCommand::finished, this, [this]() {
- this->setEnabled(true);
- });
- cmd->setParentWidget(this);
- cmd->start();
+ mCardKeysView->setCard(card);
}
void PIVCardWidget::createKeyFromCardKeys()
{
auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(serialNumber(), PIVCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PIVCardWidget::changePin(const std::string &keyRef)
{
auto cmd = new ChangePinCommand(serialNumber(), PIVCard::AppName, this);
this->setEnabled(false);
connect(cmd, &ChangePinCommand::finished, this, [this]() {
this->setEnabled(true);
});
cmd->setKeyRef(keyRef);
cmd->start();
}
void PIVCardWidget::setAdminKey()
{
auto cmd = new SetPIVCardApplicationAdministrationKeyCommand(serialNumber(), this);
this->setEnabled(false);
connect(cmd, &SetPIVCardApplicationAdministrationKeyCommand::finished, this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
#include "moc_pivcardwidget.cpp"
diff --git a/src/view/pivcardwidget.h b/src/view/pivcardwidget.h
index 3e5c3ea87..78ec9d276 100644
--- a/src/view/pivcardwidget.h
+++ b/src/view/pivcardwidget.h
@@ -1,69 +1,40 @@
/* view/pivcardwiget.h
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
*/
#pragma once
#include "smartcardwidget.h"
-#include <smartcard/keypairinfo.h>
-
-#include <QMap>
-
-#include <gpgme++/error.h>
-
-class QLabel;
class QPushButton;
namespace Kleo
{
namespace SmartCard
{
class PIVCard;
} // namespace SmartCard
class PIVCardWidget : public SmartCardWidget
{
Q_OBJECT
public:
explicit PIVCardWidget(QWidget *parent = nullptr);
~PIVCardWidget() override;
void setCard(const SmartCard::PIVCard *card);
- struct KeyWidgets {
- SmartCard::KeyPairInfo keyInfo;
- std::string certificateData;
- QLabel *keyGrip = nullptr;
- QLabel *keyAlgorithm = nullptr;
- QLabel *certificateInfo = nullptr;
- QPushButton *generateButton = nullptr;
- QPushButton *createCSRButton = nullptr;
- QPushButton *writeCertificateButton = nullptr;
- QPushButton *importCertificateButton = nullptr;
- QPushButton *writeKeyButton = nullptr;
- };
-
private:
- KeyWidgets createKeyWidgets(const SmartCard::KeyPairInfo &keyInfo);
- void updateCachedValues(const std::string &keyRef, const SmartCard::PIVCard *card);
- void updateKeyWidgets(const std::string &keyRef);
- void generateKey(const std::string &keyref);
- void createCSR(const std::string &keyref);
- void writeCertificateToCard(const std::string &keyref);
- void importCertificateFromCard(const std::string &keyref);
- void writeKeyToCard(const std::string &keyref);
void createKeyFromCardKeys();
void changePin(const std::string &keyRef);
void setAdminKey();
private:
QPushButton *mKeyForCardKeysButton = nullptr;
- std::map<std::string, KeyWidgets> mKeyWidgets;
};
} // namespace Kleo
diff --git a/src/view/smartcardactions.cpp b/src/view/smartcardactions.cpp
index e3484546d..348d054d6 100644
--- a/src/view/smartcardactions.cpp
+++ b/src/view/smartcardactions.cpp
@@ -1,75 +1,105 @@
/* view/smartcardactions.cpp
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 "smartcardactions.h"
#include <KActionCollection>
#include <KLocalizedString>
#include <algorithm>
using namespace Qt::Literals::StringLiterals;
SmartCardActions::SmartCardActions()
: mActionCollection{std::make_unique<KActionCollection>(nullptr, u"smartcards"_s)}
{
mActionCollection->setComponentDisplayName(i18n("Smart Card Management"));
// window actions
mActionCollection->addAction(KStandardAction::StandardAction::Close, u"window_close"_s);
// general actions
{
QAction *action = mActionCollection->addAction(KStandardAction::StandardAction::Redisplay, u"reload"_s);
action->setText(i18nc("@action", "Reload"));
action->setToolTip(i18nc("@info:tooltip", "Reload smart cards"));
}
// card slot actions
{
QAction *action = mActionCollection->addAction(u"card_slot_show_certificate_details"_s);
action->setText(i18nc("@action", "Show Certificate Details"));
action->setIcon(QIcon::fromTheme(u"dialog-information"_s));
}
+ {
+ QAction *action = mActionCollection->addAction(u"card_slot_generate_key"_s);
+ action->setText(i18nc("@action", "Generate New Key"));
+ action->setToolTip(i18nc("@info:tooltip", "If the card slot already contains a key then the new key will irrevocably replace the old key."));
+ action->setIcon(QIcon::fromTheme(u"view-certificate-add"_s));
+ }
+ {
+ QAction *action = mActionCollection->addAction(u"card_slot_write_key"_s);
+ action->setText(i18nc("@action", "Write Key to Card"));
+ action->setToolTip(i18nc("@info:tooltip", "Write the key pair of a certificate to the card"));
+ action->setIcon(QIcon::fromTheme(u"view-certificate-export"_s));
+ }
+ {
+ QAction *action = mActionCollection->addAction(u"card_slot_write_certificate"_s);
+ action->setText(i18nc("@action", "Write Certificate to Card"));
+ action->setToolTip(i18nc("@info:tooltip", "Write the certificate corresponding to this key to the card"));
+ action->setIcon(QIcon::fromTheme(u"view-certificate-export"_s));
+ }
+ {
+ QAction *action = mActionCollection->addAction(u"card_slot_read_certificate"_s);
+ action->setText(i18nc("@action", "Import Certificate from Card"));
+ action->setToolTip(i18nc("@info:tooltip", "Import the certificate stored on the card"));
+ action->setIcon(QIcon::fromTheme(u"view-certificate-import"_s));
+ }
+ {
+ QAction *action = mActionCollection->addAction(u"card_slot_create_csr"_s);
+ action->setText(i18nc("@action", "Create S/MIME Certification Request"));
+ action->setToolTip(i18nc("@info:tooltip", "Create an S/MIME certificate signing request for this key"));
+ action->setIcon(QIcon::fromTheme(u"view-certificate-add"_s));
+ }
}
SmartCardActions::~SmartCardActions() = default;
std::shared_ptr<const SmartCardActions> SmartCardActions::instance()
{
return mutableInstance();
}
std::shared_ptr<SmartCardActions> SmartCardActions::mutableInstance()
{
static std::weak_ptr<SmartCardActions> self;
if (std::shared_ptr<SmartCardActions> shared = self.lock()) {
return shared;
} else {
const std::shared_ptr<SmartCardActions> s{new SmartCardActions};
self = s;
return s;
}
}
QAction *SmartCardActions::action(const QString &name) const
{
return mActionCollection->action(name);
}
std::vector<QAction *> SmartCardActions::actions(const std::vector<QString> &names) const
{
std::vector<QAction *> result;
result.reserve(names.size());
std::ranges::transform(names, std::back_inserter(result), [this](const QString &name) {
return action(name);
});
std::erase(result, nullptr);
return result;
}
diff --git a/src/view/smartcardswidget.cpp b/src/view/smartcardswidget.cpp
index c60d0e818..bdb4af189 100644
--- a/src/view/smartcardswidget.cpp
+++ b/src/view/smartcardswidget.cpp
@@ -1,304 +1,441 @@
/* view/smartcardswidget.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 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "smartcardswidget.h"
#include "netkeywidget.h"
#include "p15cardwidget.h"
#include "pgpcardwidget.h"
#include "pivcardwidget.h"
#include "smartcardactions.h"
#include "smartcardwidget.h"
+#include <commands/certificatetopivcardcommand.h>
+#include <commands/createcsrforcardkeycommand.h>
#include <commands/detailscommand.h>
+#include <commands/importcertificatefrompivcardcommand.h>
+#include <commands/keytocardcommand.h>
+#include <commands/pivgeneratecardkeycommand.h>
#include "smartcard/netkeycard.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/p15card.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "smartcard/utils.h"
#include "kleopatra_debug.h"
#include <KActionCollection>
#include <KLocalizedString>
#include <QHBoxLayout>
#include <QLabel>
#include <QPointer>
#include <QPushButton>
#include <QStackedWidget>
#include <QTabWidget>
#include <QToolButton>
#include <QVBoxLayout>
#include <gpgme++/key.h>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace Qt::Literals::StringLiterals;
namespace
{
class PlaceHolderWidget : public QWidget
{
Q_OBJECT
public:
explicit PlaceHolderWidget(QWidget *parent = nullptr)
: QWidget{parent}
{
auto lay = new QVBoxLayout;
lay->addStretch(-1);
const QStringList supported{
i18nc("OpenPGP refers to a smartcard protocol", "OpenPGP v2.0 or later"),
i18nc("Gnuk is a cryptographic token for GnuPG", "Gnuk"),
i18nc("NetKey refers to a smartcard protocol", "NetKey v3 or later"),
i18nc("PIV refers to a smartcard protocol", "PIV (requires GnuPG 2.3 or later)"),
i18nc("CardOS is a smartcard operating system", "CardOS 5 (various apps)"),
};
lay->addWidget(new QLabel(QStringLiteral("\t\t<h3>") + i18n("Please insert a compatible smartcard.") + QStringLiteral("</h3>"), this));
lay->addSpacing(10);
lay->addWidget(new QLabel(QStringLiteral("\t\t") + i18n("Kleopatra currently supports the following card types:") + QStringLiteral("<ul><li>")
+ supported.join(QLatin1StringView("</li><li>")) + QStringLiteral("</li></ul>"),
this));
lay->addSpacing(10);
{
auto hbox = new QHBoxLayout;
hbox->addStretch(1);
mReloadButton = new QPushButton{i18n("Reload"), this};
hbox->addWidget(mReloadButton);
hbox->addStretch(1);
lay->addLayout(hbox);
}
lay->addStretch(-1);
auto hLay = new QHBoxLayout(this);
hLay->addStretch(-1);
hLay->addLayout(lay);
hLay->addStretch(-1);
lay->addStretch(-1);
connect(mReloadButton, &QPushButton::clicked, this, &PlaceHolderWidget::reload);
connect(ReaderStatus::instance(), &ReaderStatus::currentActionChanged, this, &PlaceHolderWidget::updateReloadButton);
updateReloadButton();
}
void updateReloadButton()
{
mReloadButton->setEnabled(ReaderStatus::instance()->currentAction() != ReaderStatus::UpdateCards);
}
Q_SIGNALS:
void reload();
private:
QPushButton *mReloadButton = nullptr;
};
} // namespace
class SmartCardsWidget::Private
{
friend class ::Kleo::SmartCardsWidget;
public:
Private(SmartCardsWidget *qq);
+ const SmartCardWidget *currentCardWidget() const;
+ AppType currentCardType() const;
+ std::string currentSerialNumber() const;
std::string currentCardSlot() const;
GpgME::Key currentCertificate() const;
void cardAddedOrChanged(const std::string &serialNumber, const std::string &appName);
void cardRemoved(const std::string &serialNumber, const std::string &appName);
+ void enableCurrentWidget();
+ void disableCurrentWidget();
+
void showCertificateDetails();
+ void generateKey();
+ void createCSR();
+ void writeCertificateToCard();
+ void readCertificateFromCard();
+ void writeKeyToCard();
private:
template<typename C, typename W>
void cardAddedOrChanged(const std::string &serialNumber);
private:
SmartCardsWidget *const q;
QMap<std::pair<std::string, std::string>, QPointer<SmartCardWidget>> mCardWidgets;
PlaceHolderWidget *mPlaceHolderWidget;
QStackedWidget *mStack;
QTabWidget *mTabWidget;
QToolButton *mReloadButton;
};
SmartCardsWidget::Private::Private(SmartCardsWidget *qq)
: q{qq}
{
auto vLay = new QVBoxLayout(q);
mStack = new QStackedWidget{q};
vLay->addWidget(mStack);
mPlaceHolderWidget = new PlaceHolderWidget{q};
mStack->addWidget(mPlaceHolderWidget);
mTabWidget = new QTabWidget{q};
// create "Reload" button after tab widget to ensure correct tab order
mReloadButton = new QToolButton{q};
mTabWidget->setCornerWidget(mReloadButton, Qt::TopRightCorner);
mStack->addWidget(mTabWidget);
mStack->setCurrentWidget(mPlaceHolderWidget);
connect(mPlaceHolderWidget, &PlaceHolderWidget::reload, q, &SmartCardsWidget::reload);
connect(ReaderStatus::instance(), &ReaderStatus::cardAdded, q, [this](const std::string &serialNumber, const std::string &appName) {
cardAddedOrChanged(serialNumber, appName);
});
connect(ReaderStatus::instance(), &ReaderStatus::cardChanged, q, [this](const std::string &serialNumber, const std::string &appName) {
cardAddedOrChanged(serialNumber, appName);
});
connect(ReaderStatus::instance(), &ReaderStatus::cardRemoved, q, [this](const std::string &serialNumber, const std::string &appName) {
cardRemoved(serialNumber, appName);
});
const auto actions = SmartCardActions::instance();
actions->connectAction(u"reload"_s, q, &SmartCardsWidget::reload);
mReloadButton->setDefaultAction(actions->action(u"reload"_s));
actions->connectAction(u"card_slot_show_certificate_details"_s, q, [this]() {
showCertificateDetails();
});
+ actions->connectAction(u"card_slot_generate_key"_s, q, [this]() {
+ generateKey();
+ });
+ actions->connectAction(u"card_slot_write_key"_s, q, [this]() {
+ writeKeyToCard();
+ });
+ actions->connectAction(u"card_slot_write_certificate"_s, q, [this]() {
+ writeCertificateToCard();
+ });
+ actions->connectAction(u"card_slot_read_certificate"_s, q, [this]() {
+ readCertificateFromCard();
+ });
+ actions->connectAction(u"card_slot_create_csr"_s, q, [this]() {
+ createCSR();
+ });
+}
+
+const SmartCardWidget *SmartCardsWidget::Private::currentCardWidget() const
+{
+ return qobject_cast<const SmartCardWidget *>(mTabWidget->currentWidget());
+}
+
+AppType SmartCardsWidget::Private::currentCardType() const
+{
+ if (const SmartCardWidget *widget = currentCardWidget()) {
+ return widget->cardType();
+ }
+ return AppType::NoApp;
+}
+
+std::string SmartCardsWidget::Private::currentSerialNumber() const
+{
+ if (const SmartCardWidget *widget = currentCardWidget()) {
+ return widget->serialNumber();
+ }
+ return {};
}
std::string SmartCardsWidget::Private::currentCardSlot() const
{
- if (const SmartCardWidget *widget = qobject_cast<const SmartCardWidget *>(mTabWidget->currentWidget())) {
+ if (const SmartCardWidget *widget = currentCardWidget()) {
return widget->currentCardSlot();
}
return {};
}
GpgME::Key SmartCardsWidget::Private::currentCertificate() const
{
- if (const SmartCardWidget *widget = qobject_cast<const SmartCardWidget *>(mTabWidget->currentWidget())) {
+ if (const SmartCardWidget *widget = currentCardWidget()) {
return widget->currentCertificate();
}
return {};
}
void SmartCardsWidget::Private::cardAddedOrChanged(const std::string &serialNumber, const std::string &appName)
{
if (appName == SmartCard::NetKeyCard::AppName) {
cardAddedOrChanged<NetKeyCard, NetKeyWidget>(serialNumber);
} else if (appName == SmartCard::OpenPGPCard::AppName) {
cardAddedOrChanged<OpenPGPCard, PGPCardWidget>(serialNumber);
} else if (appName == SmartCard::PIVCard::AppName) {
cardAddedOrChanged<PIVCard, PIVCardWidget>(serialNumber);
} else if (appName == SmartCard::P15Card::AppName) {
cardAddedOrChanged<P15Card, P15CardWidget>(serialNumber);
} else {
qCWarning(KLEOPATRA_LOG) << "SmartCardsWidget::Private::cardAddedOrChanged:"
<< "App" << appName.c_str() << "is not supported";
}
}
namespace
{
static QString getCardLabel(const std::shared_ptr<Card> &card)
{
if (!card->cardHolder().isEmpty()) {
return i18nc("@title:tab smartcard application - name of card holder - serial number of smartcard",
"%1 - %2 - %3",
displayAppName(card->appName()),
card->cardHolder(),
card->displaySerialNumber());
} else {
return i18nc("@title:tab smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber());
}
}
}
template<typename C, typename W>
void SmartCardsWidget::Private::cardAddedOrChanged(const std::string &serialNumber)
{
const auto card = ReaderStatus::instance()->getCard<C>(serialNumber);
if (!card) {
qCWarning(KLEOPATRA_LOG) << "SmartCardsWidget::Private::cardAddedOrChanged:"
<< "New or changed card" << serialNumber.c_str() << "with app" << C::AppName.c_str() << "not found";
return;
}
W *cardWidget = dynamic_cast<W *>(mCardWidgets.value({serialNumber, C::AppName}).data());
if (!cardWidget) {
cardWidget = new W;
mCardWidgets.insert({serialNumber, C::AppName}, cardWidget);
mTabWidget->addTab(cardWidget, getCardLabel(card));
if (mCardWidgets.size() == 1) {
mStack->setCurrentWidget(mTabWidget);
}
}
cardWidget->setCard(card.get());
}
void SmartCardsWidget::Private::cardRemoved(const std::string &serialNumber, const std::string &appName)
{
QWidget *cardWidget = mCardWidgets.take({serialNumber, appName});
if (cardWidget) {
const int index = mTabWidget->indexOf(cardWidget);
if (index != -1) {
mTabWidget->removeTab(index);
}
delete cardWidget;
}
if (mCardWidgets.empty()) {
mStack->setCurrentWidget(mPlaceHolderWidget);
}
}
+void SmartCardsWidget::Private::enableCurrentWidget()
+{
+ mTabWidget->currentWidget()->setEnabled(true);
+}
+
+void SmartCardsWidget::Private::disableCurrentWidget()
+{
+ mTabWidget->currentWidget()->setEnabled(false);
+}
+
void SmartCardsWidget::Private::showCertificateDetails()
{
const Key certificate = currentCertificate();
if (!certificate.isNull()) {
auto cmd = new DetailsCommand(certificate);
cmd->setParentWidget(q->window());
cmd->start();
}
}
+void SmartCardsWidget::Private::generateKey()
+{
+ Q_ASSERT(currentCardType() == AppType::PIVApp);
+ const std::string serialNumber = currentSerialNumber();
+ Q_ASSERT(!serialNumber.empty());
+ const std::string keyRef = currentCardSlot();
+ auto cmd = new PIVGenerateCardKeyCommand(serialNumber, q->window());
+ disableCurrentWidget();
+ connect(cmd, &PIVGenerateCardKeyCommand::finished, q, [this]() {
+ enableCurrentWidget();
+ });
+ cmd->setKeyRef(keyRef);
+ cmd->start();
+}
+
+void SmartCardsWidget::Private::createCSR()
+{
+ Q_ASSERT(currentCardType() == AppType::PIVApp);
+ const std::string serialNumber = currentSerialNumber();
+ Q_ASSERT(!serialNumber.empty());
+ const std::string keyRef = currentCardSlot();
+ auto cmd = new CreateCSRForCardKeyCommand(keyRef, serialNumber, PIVCard::AppName, q->window());
+ disableCurrentWidget();
+ connect(cmd, &CreateCSRForCardKeyCommand::finished, q, [this]() {
+ enableCurrentWidget();
+ });
+ cmd->start();
+}
+
+void SmartCardsWidget::Private::writeCertificateToCard()
+{
+ Q_ASSERT(currentCardType() == AppType::PIVApp);
+ const std::string serialNumber = currentSerialNumber();
+ Q_ASSERT(!serialNumber.empty());
+ const std::string keyRef = currentCardSlot();
+ auto cmd = new CertificateToPIVCardCommand(keyRef, serialNumber);
+ disableCurrentWidget();
+ connect(cmd, &CertificateToPIVCardCommand::finished, q, [this]() {
+ enableCurrentWidget();
+ });
+ cmd->setParentWidget(q->window());
+ cmd->start();
+}
+
+void SmartCardsWidget::Private::readCertificateFromCard()
+{
+ Q_ASSERT(currentCardType() == AppType::PIVApp);
+ const std::string serialNumber = currentSerialNumber();
+ Q_ASSERT(!serialNumber.empty());
+ const std::string keyRef = currentCardSlot();
+ auto cmd = new ImportCertificateFromPIVCardCommand(keyRef, serialNumber);
+ disableCurrentWidget();
+ connect(cmd, &ImportCertificateFromPIVCardCommand::finished, q, [this, keyRef]() {
+ // this->updateKeyWidgets(keyRef); // this should happen automatically
+ enableCurrentWidget();
+ });
+ cmd->setParentWidget(q->window());
+ cmd->start();
+}
+
+void SmartCardsWidget::Private::writeKeyToCard()
+{
+ Q_ASSERT(currentCardType() == AppType::PIVApp);
+ const std::string serialNumber = currentSerialNumber();
+ Q_ASSERT(!serialNumber.empty());
+ const std::string keyRef = currentCardSlot();
+ auto cmd = new KeyToCardCommand(keyRef, serialNumber, PIVCard::AppName);
+ disableCurrentWidget();
+ connect(cmd, &KeyToCardCommand::finished, q, [this]() {
+ enableCurrentWidget();
+ });
+ cmd->setParentWidget(q->window());
+ cmd->start();
+}
+
SmartCardsWidget::SmartCardsWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
connect(ReaderStatus::instance(), &ReaderStatus::currentActionChanged, this, &SmartCardsWidget::updateReloadButton);
updateReloadButton();
}
SmartCardsWidget::~SmartCardsWidget() = default;
void SmartCardsWidget::showCards(const std::vector<std::shared_ptr<Kleo::SmartCard::Card>> &cards)
{
for (const auto &card : cards) {
d->cardAddedOrChanged(card->serialNumber(), card->appName());
}
}
void SmartCardsWidget::reload()
{
ReaderStatus::mutableInstance()->updateStatus();
}
void SmartCardsWidget::updateReloadButton()
{
d->mReloadButton->setEnabled(ReaderStatus::instance()->currentAction() != ReaderStatus::UpdateCards);
}
#include "smartcardswidget.moc"
#include "moc_smartcardswidget.cpp"
diff --git a/src/view/smartcardwidget.cpp b/src/view/smartcardwidget.cpp
index 6f842d7dc..0ed7b44af 100644
--- a/src/view/smartcardwidget.cpp
+++ b/src/view/smartcardwidget.cpp
@@ -1,126 +1,171 @@
/* view/smartcardwidget.cpp
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 "smartcardwidget.h"
#include "infofield.h"
+#include "smartcardactions.h"
#include <smartcard/card.h>
+#include <smartcard/pivcard.h>
#include <view/cardkeysview.h>
+#include <Libkleo/Compliance>
+
#include <KLocalizedString>
#include <QGridLayout>
#include <QLabel>
#include <QScrollArea>
#include <QVBoxLayout>
using namespace Kleo;
using namespace Kleo::SmartCard;
+using namespace Qt::Literals::StringLiterals;
static QString cardTypeForDisplay(const Card *card)
{
switch (card->appType()) {
case AppType::NetKeyApp:
return i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion());
case AppType::OpenPGPApp: {
const std::string manufacturer = card->manufacturer();
const bool manufacturerIsUnknown = manufacturer.empty() || manufacturer == "unknown";
return (manufacturerIsUnknown //
? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", card->displayAppVersion())
: i18nc("First placeholder is manufacturer, second placeholder is a version number",
"%1 OpenPGP v%2 card",
QString::fromStdString(manufacturer),
card->displayAppVersion()));
}
case AppType::P15App:
return i18nc("%1 is a smartcard manufacturer", "%1 PKCS#15 card", QString::fromStdString(card->manufacturer()));
case AppType::PIVApp:
return i18nc("%1 version number", "PIV v%1 card", card->displayAppVersion());
default:
return {};
};
}
SmartCardWidget::SmartCardWidget(QWidget *parent)
: QWidget{parent}
{
auto mainLayout = new QVBoxLayout{this};
mainLayout->setContentsMargins({});
auto area = new QScrollArea{this};
area->setFocusPolicy(Qt::NoFocus);
area->setFrameShape(QFrame::NoFrame);
area->setWidgetResizable(true);
mainLayout->addWidget(area);
auto areaWidget = new QWidget{this};
area->setWidget(areaWidget);
mContentLayout = new QVBoxLayout{areaWidget};
auto contentLayout = mContentLayout;
{
// auto gridLayout = new QGridLayout;
mInfoGridLayout = new QGridLayout;
auto gridLayout = mInfoGridLayout;
// gridLayout->setColumnStretch(1, 1);
int row = -1;
row++;
mCardTypeField = std::make_unique<InfoField>(i18nc("@label", "Card type:"), parent);
gridLayout->addWidget(mCardTypeField->label(), row, 0);
gridLayout->addLayout(mCardTypeField->layout(), row, 1);
row++;
mSerialNumberField = std::make_unique<InfoField>(i18nc("@label", "Serial number:"), parent);
gridLayout->addWidget(mSerialNumberField->label(), row, 0);
gridLayout->addLayout(mSerialNumberField->layout(), row, 1);
gridLayout->setColumnStretch(gridLayout->columnCount(), 1);
contentLayout->addLayout(gridLayout);
}
}
SmartCardWidget::~SmartCardWidget() = default;
void SmartCardWidget::setCard(const Card *card)
{
mCard.reset(card->clone());
mCardTypeField->setValue(cardTypeForDisplay(card));
mSerialNumberField->setValue(card->displaySerialNumber());
}
Kleo::SmartCard::AppType SmartCardWidget::cardType() const
{
return mCard ? mCard->appType() : AppType::NoApp;
}
std::string SmartCardWidget::serialNumber() const
{
return mCard ? mCard->serialNumber() : std::string{};
}
std::string SmartCardWidget::currentCardSlot() const
{
if (mCardKeysView) {
return mCardKeysView->currentCardSlot();
}
return {};
}
GpgME::Key SmartCardWidget::currentCertificate() const
{
if (mCardKeysView) {
return mCardKeysView->currentCertificate();
}
return {};
}
+
+void SmartCardWidget::updateActions()
+{
+ const auto actions = SmartCardActions::instance();
+ if (QAction *action = actions->action(u"card_slot_show_certificate_details"_s)) {
+ action->setEnabled(!currentCertificate().isNull());
+ }
+ switch (cardType()) {
+ case AppType::PIVApp: {
+ const std::string keyRef = currentCardSlot();
+ if (QAction *action = actions->action(u"card_slot_write_key"_s)) {
+ action->setEnabled(keyRef == PIVCard::cardAuthenticationKeyRef() || keyRef == PIVCard::keyManagementKeyRef());
+ }
+ if (QAction *action = actions->action(u"card_slot_write_certificate"_s)) {
+ action->setEnabled(currentCertificate().protocol() == GpgME::CMS);
+ }
+ if (QAction *action = actions->action(u"card_slot_read_certificate"_s)) {
+ action->setEnabled(!mCard->certificateData(keyRef).empty());
+ }
+ if (QAction *action = actions->action(u"card_slot_create_csr"_s)) {
+ const auto keyInfo = mCard->keyInfo(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:
+ // TODO
+ break;
+ case AppType::NetKeyApp:
+ case AppType::P15App:
+ // nothing to do
+ break;
+ case AppType::NoApp:
+ // cannot happen
+ break;
+ };
+}
diff --git a/src/view/smartcardwidget.h b/src/view/smartcardwidget.h
index 03f8fdcea..ba1c6cfd4 100644
--- a/src/view/smartcardwidget.h
+++ b/src/view/smartcardwidget.h
@@ -1,61 +1,63 @@
/* view/smartcardwidget.h
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
*/
#pragma once
#include <QWidget>
#include <memory>
#include <string>
class QGridLayout;
class QVBoxLayout;
namespace GpgME
{
class Key;
}
namespace Kleo
{
class CardKeysView;
class InfoField;
}
namespace Kleo::SmartCard
{
enum class AppType;
class Card;
}
class SmartCardWidget : public QWidget
{
Q_OBJECT
public:
SmartCardWidget(QWidget *parent = nullptr);
~SmartCardWidget() override;
void setCard(const Kleo::SmartCard::Card *card);
Kleo::SmartCard::AppType cardType() const;
std::string serialNumber() const;
std::string currentCardSlot() const;
GpgME::Key currentCertificate() const;
+ void updateActions();
+
protected:
QVBoxLayout *mContentLayout = nullptr;
QGridLayout *mInfoGridLayout = nullptr;
private:
std::unique_ptr<const Kleo::SmartCard::Card> mCard;
std::unique_ptr<Kleo::InfoField> mCardTypeField;
std::unique_ptr<Kleo::InfoField> mSerialNumberField;
protected:
Kleo::CardKeysView *mCardKeysView = nullptr;
};

File Metadata

Mime Type
text/x-diff
Expires
Sat, Feb 1, 9:34 AM (1 d, 13 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b9/48/4eb09ba95621efbd1f5ce5522293

Event Timeline