diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e13dfd15..8517e5720 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,350 +1,351 @@ add_subdirectory(icons) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (NOT DISABLE_KWATCHGNUPG) add_subdirectory(kwatchgnupg) endif() add_subdirectory(libkleopatraclient) add_subdirectory(conf) add_subdirectory(kconf_update) if(WIN32) set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp) set(_kleopatra_extra_SRCS utils/gnupg-registry.c selftest/registrycheck.cpp utils/windowsprocessdevice.cpp ) else() set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp) set(_kleopatra_extra_SRCS) endif() set(_kleopatra_uiserver_SRCS uiserver/sessiondata.cpp uiserver/uiserver.cpp ${_kleopatra_extra_uiserver_SRCS} uiserver/assuanserverconnection.cpp uiserver/echocommand.cpp uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandfilesbase.cpp uiserver/signcommand.cpp uiserver/signencryptfilescommand.cpp uiserver/prepencryptcommand.cpp uiserver/prepsigncommand.cpp uiserver/encryptcommand.cpp uiserver/selectcertificatecommand.cpp uiserver/importfilescommand.cpp uiserver/createchecksumscommand.cpp uiserver/verifychecksumscommand.cpp selftest/uiservercheck.cpp ) if(ASSUAN2_FOUND) include_directories(${ASSUAN2_INCLUDES}) set(_kleopatra_uiserver_extra_libs ${ASSUAN2_LIBRARIES}) else() include_directories(${ASSUAN_INCLUDES}) if(WIN32) set(_kleopatra_uiserver_extra_libs ${ASSUAN_VANILLA_LIBRARIES}) else() set(_kleopatra_uiserver_extra_libs ${ASSUAN_PTHREAD_LIBRARIES}) endif() endif() if(HAVE_GPG_ERR_SOURCE_KLEO) add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) else() add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) endif() ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui) set(_kleopatra_SRCS utils/gui-helper.cpp utils/filedialog.cpp utils/kdpipeiodevice.cpp utils/headerview.cpp utils/scrollarea.cpp utils/dragqueen.cpp utils/multivalidator.cpp utils/systemtrayicon.cpp utils/hex.cpp utils/path-helper.cpp utils/input.cpp utils/output.cpp utils/validation.cpp utils/wsastarter.cpp utils/iodevicelogger.cpp utils/log.cpp utils/action_data.cpp utils/types.cpp utils/archivedefinition.cpp utils/auditlog.cpp utils/clipboardmenu.cpp utils/kuniqueservice.cpp utils/remarks.cpp utils/writecertassuantransaction.cpp selftest/selftest.cpp selftest/enginecheck.cpp selftest/gpgconfcheck.cpp selftest/gpgagentcheck.cpp selftest/libkleopatrarccheck.cpp ${_kleopatra_extra_SRCS} view/keylistcontroller.cpp view/keytreeview.cpp view/searchbar.cpp view/smartcardwidget.cpp view/padwidget.cpp view/pgpcardwidget.cpp view/pivcardwidget.cpp view/netkeywidget.cpp view/nullpinwidget.cpp view/tabwidget.cpp view/keycacheoverlay.cpp view/waitwidget.cpp view/welcomewidget.cpp dialogs/certificateselectiondialog.cpp dialogs/certifywidget.cpp dialogs/expirydialog.cpp dialogs/lookupcertificatesdialog.cpp dialogs/ownertrustdialog.cpp dialogs/selftestdialog.cpp dialogs/certifycertificatedialog.cpp dialogs/adduseriddialog.cpp dialogs/addemaildialog.cpp dialogs/exportcertificatesdialog.cpp dialogs/deletecertificatesdialog.cpp dialogs/setinitialpindialog.cpp dialogs/certificatedetailswidget.cpp dialogs/trustchainwidget.cpp dialogs/weboftrustwidget.cpp dialogs/weboftrustdialog.cpp dialogs/exportdialog.cpp dialogs/subkeyswidget.cpp dialogs/gencardkeydialog.cpp dialogs/updatenotification.cpp dialogs/pivcardapplicationadministrationkeyinputdialog.cpp crypto/controller.cpp crypto/certificateresolver.cpp crypto/sender.cpp crypto/recipient.cpp crypto/task.cpp crypto/taskcollection.cpp crypto/decryptverifytask.cpp crypto/decryptverifyemailcontroller.cpp crypto/decryptverifyfilescontroller.cpp crypto/autodecryptverifyfilescontroller.cpp crypto/encryptemailtask.cpp crypto/encryptemailcontroller.cpp crypto/newsignencryptemailcontroller.cpp crypto/signencrypttask.cpp crypto/signencryptfilescontroller.cpp crypto/signemailtask.cpp crypto/signemailcontroller.cpp crypto/createchecksumscontroller.cpp crypto/verifychecksumscontroller.cpp crypto/gui/wizard.cpp crypto/gui/wizardpage.cpp crypto/gui/certificateselectionline.cpp crypto/gui/certificatelineedit.cpp crypto/gui/signingcertificateselectionwidget.cpp crypto/gui/signingcertificateselectiondialog.cpp crypto/gui/resultitemwidget.cpp crypto/gui/resultlistwidget.cpp crypto/gui/resultpage.cpp crypto/gui/newresultpage.cpp crypto/gui/signencryptfileswizard.cpp crypto/gui/signencryptemailconflictdialog.cpp crypto/gui/decryptverifyoperationwidget.cpp crypto/gui/decryptverifyfileswizard.cpp crypto/gui/decryptverifyfilesdialog.cpp crypto/gui/objectspage.cpp crypto/gui/resolverecipientspage.cpp crypto/gui/signerresolvepage.cpp crypto/gui/encryptemailwizard.cpp crypto/gui/signemailwizard.cpp crypto/gui/signencryptwidget.cpp crypto/gui/signencryptwizard.cpp crypto/gui/unknownrecipientwidget.cpp crypto/gui/verifychecksumsdialog.cpp commands/command.cpp commands/gnupgprocesscommand.cpp commands/detailscommand.cpp commands/exportcertificatecommand.cpp commands/importcertificatescommand.cpp commands/importcertificatefromfilecommand.cpp commands/importcertificatefromclipboardcommand.cpp commands/importcertificatefromdatacommand.cpp commands/lookupcertificatescommand.cpp commands/reloadkeyscommand.cpp commands/refreshx509certscommand.cpp commands/refreshopenpgpcertscommand.cpp commands/deletecertificatescommand.cpp commands/decryptverifyfilescommand.cpp commands/signencryptfilescommand.cpp commands/signencryptfoldercommand.cpp commands/encryptclipboardcommand.cpp commands/signclipboardcommand.cpp commands/decryptverifyclipboardcommand.cpp commands/clearcrlcachecommand.cpp commands/dumpcrlcachecommand.cpp commands/dumpcertificatecommand.cpp commands/importcrlcommand.cpp commands/changeexpirycommand.cpp commands/changeownertrustcommand.cpp commands/changeroottrustcommand.cpp commands/changepassphrasecommand.cpp commands/certifycertificatecommand.cpp commands/selftestcommand.cpp commands/exportsecretkeycommand.cpp commands/exportopenpgpcertstoservercommand.cpp commands/adduseridcommand.cpp commands/newcertificatecommand.cpp commands/setinitialpincommand.cpp commands/learncardkeyscommand.cpp commands/checksumcreatefilescommand.cpp commands/checksumverifyfilescommand.cpp commands/exportpaperkeycommand.cpp commands/importpaperkeycommand.cpp commands/genrevokecommand.cpp commands/keytocardcommand.cpp commands/cardcommand.cpp commands/pivgeneratecardkeycommand.cpp commands/changepincommand.cpp commands/authenticatepivcardapplicationcommand.cpp commands/setpivcardapplicationadministrationkeycommand.cpp commands/certificatetopivcardcommand.cpp commands/importcertificatefrompivcardcommand.cpp ${_kleopatra_uiserver_files} conf/configuredialog.cpp newcertificatewizard/listwidget.cpp newcertificatewizard/newcertificatewizard.cpp smartcard/readerstatus.cpp smartcard/card.cpp smartcard/openpgpcard.cpp smartcard/netkeycard.cpp smartcard/pivcard.cpp smartcard/keypairinfo.cpp + smartcard/utils.cpp aboutdata.cpp systrayicon.cpp kleopatraapplication.cpp mainwindow.cpp main.cpp ) if(WIN32) configure_file (versioninfo.rc.in versioninfo.rc) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS}) endif() if(HAVE_KCMUTILS) set (_kleopatra_extra_libs KF5::KCMUtils) else() set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp ${_kleopatra_SRCS}) endif() ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra DESCRIPTION "kleopatra (kleopatra)" OLD_CATEGORY_NAMES log_kleopatra EXPORT KLEOPATRA ) if(KLEO_MODEL_TEST) add_definitions(-DKLEO_MODEL_TEST) set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp) endif() ki18n_wrap_ui(_kleopatra_SRCS dialogs/expirydialog.ui dialogs/lookupcertificatesdialog.ui dialogs/ownertrustdialog.ui dialogs/selectchecklevelwidget.ui dialogs/selftestdialog.ui dialogs/adduseriddialog.ui dialogs/setinitialpindialog.ui dialogs/certificatedetailswidget.ui dialogs/trustchainwidget.ui dialogs/subkeyswidget.ui newcertificatewizard/listwidget.ui newcertificatewizard/chooseprotocolpage.ui newcertificatewizard/enterdetailspage.ui newcertificatewizard/overviewpage.ui newcertificatewizard/keycreationpage.ui newcertificatewizard/resultpage.ui newcertificatewizard/advancedsettingsdialog.ui ) kconfig_add_kcfg_files(_kleopatra_SRCS kcfg/tooltippreferences.kcfgc kcfg/emailoperationspreferences.kcfgc kcfg/fileoperationspreferences.kcfgc kcfg/smimevalidationpreferences.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png") ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS}) qt5_add_resources(_kleopatra_SRCS kleopatra.qrc) add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS}) set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra) target_link_libraries(kleopatra_bin Gpgmepp QGpgme ${_kleopatra_extra_libs} KF5::Libkleo KF5::Mime KF5::I18n KF5::XmlGui KF5::IconThemes KF5::WindowSystem KF5::CoreAddons KF5::ItemModels KF5::Crash Qt5::Network Qt5::PrintSupport # Printing secret keys ${_kleopatra_uiserver_extra_libs} ${_kleopatra_dbusaddons_libs} kleopatraclientcore ) install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install( FILES data/kleopatra_signencryptfiles.desktop data/kleopatra_signencryptfolders.desktop data/kleopatra_decryptverifyfiles.desktop data/kleopatra_decryptverifyfolders.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) diff --git a/src/commands/cardcommand_p.h b/src/commands/cardcommand_p.h index d52b24ecf..c6de00afc 100644 --- a/src/commands/cardcommand_p.h +++ b/src/commands/cardcommand_p.h @@ -1,37 +1,43 @@ /* commands/cardcommand_p.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_COMMANDS_CARDCOMMAND_P_H__ #define __KLEOPATRA_COMMANDS_CARDCOMMAND_P_H__ #include "cardcommand.h" #include "command_p.h" class Kleo::CardCommand::Private : public Command::Private { friend class ::Kleo::CardCommand; Kleo::CardCommand *q_func() const { return static_cast(q); } public: explicit Private(CardCommand *qq, const std::string &serialNumber, QWidget *parent); ~Private(); std::string serialNumber() const { return serialNumber_; } +protected: + void setSerialNumber(const std::string &serialNumber) + { + serialNumber_ = serialNumber; + } + private: std::string serialNumber_; }; #endif /* __KLEOPATRA_COMMANDS_CARDCOMMAND_P_H__ */ diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp index a28c1270b..9cc23026d 100644 --- a/src/commands/keytocardcommand.cpp +++ b/src/commands/keytocardcommand.cpp @@ -1,457 +1,500 @@ /* commands/keytocardcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keytocardcommand.h" #include "cardcommand_p.h" #include "commands/authenticatepivcardapplicationcommand.h" #include "dialogs/certificateselectiondialog.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" +#include "smartcard/utils.h" #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace Kleo::SmartCard; using namespace GpgME; class KeyToCardCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::KeyToCardCommand; KeyToCardCommand *q_func() const { return static_cast(q); } public: - explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey, const std::string &serialNumber, const std::string &appName); + explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey); explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName); ~Private(); private: void start(); void startKeyToOpenPGPCard(); Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &card); void startKeyToPIVCard(); void authenticate(); void authenticationFinished(); void authenticationCanceled(); private: std::string appName; GpgME::Subkey subkey; std::string cardSlot; bool overwriteExistingAlreadyApproved = false; bool hasBeenCanceled = false; }; KeyToCardCommand::Private *KeyToCardCommand::d_func() { return static_cast(d.get()); } const KeyToCardCommand::Private *KeyToCardCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() -KeyToCardCommand::Private::Private(KeyToCardCommand *qq, - const GpgME::Subkey &subkey_, - const std::string &serialNumber, - const std::string &appName_ - ) - : CardCommand::Private(qq, serialNumber, nullptr) - , appName(appName_) +KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey_) + : CardCommand::Private(qq, "", nullptr) , subkey(subkey_) { } KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName_) : CardCommand::Private(qq, serialNumber, nullptr) , appName(appName_) , cardSlot(slot) { } KeyToCardCommand::Private::~Private() { } +namespace { +static std::shared_ptr getCardToTransferSubkeyTo(const Subkey &subkey, QWidget *parent) +{ + const std::vector > suitableCards = KeyToCardCommand::getSuitableCards(subkey); + if (suitableCards.empty()) { + return std::shared_ptr(); + } else if (suitableCards.size() == 1) { + return suitableCards[0]; + } + + QStringList options; + for (const auto &card: suitableCards) { + options.push_back(i18nc("smartcard application - serial number of smartcard", "%1 - %2", + displayAppName(card->appName()), card->displaySerialNumber())); + } + + bool ok; + const QString choice = QInputDialog::getItem(parent, i18n("Select Card"), + i18n("Please select the card the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); + if (!ok) { + return std::shared_ptr(); + } + const int index = options.indexOf(choice); + return suitableCards[index]; +} +} + void KeyToCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()"; + if (!subkey.isNull() && serialNumber().empty()) { + const auto card = getCardToTransferSubkeyTo(subkey, parentWidgetOrView()); + if (!card) { + finished(); + return; + } + setSerialNumber(card->serialNumber()); + appName = card->appName(); + } + const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (card->appName() == SmartCard::OpenPGPCard::AppName) { startKeyToOpenPGPCard(); } else if (card->appName() == SmartCard::PIVCard::AppName) { startKeyToPIVCard(); } else { error(i18n("Sorry! Transferring keys to this card is not supported.")); finished(); return; } } namespace { static int getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent) { // Check if we need to ask the user for the slot if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) { // Signing only return 1; } if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) { // Encrypt only return 2; } if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) { // Auth only return 3; } // Multiple uses, ask user. QStringList options; if (subKey.canSign() || subKey.canCertify()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Signature (%1)", 1); } if (subKey.canEncrypt()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Encryption (%1)", 2); } if (subKey.canAuthenticate()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Authentication (%1)", 3); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"), i18n("Please select the card slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); const int slot = options.indexOf(choice) + 1; return ok ? slot : -1; } } void KeyToCardCommand::Private::startKeyToOpenPGPCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()"; const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); if (!pgpCard) { error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (subkey.isNull()) { finished(); return; } if (subkey.parent().protocol() != GpgME::OpenPGP) { error(i18n("Sorry! This key cannot be transferred to an OpenPGP card.")); finished(); return; } const auto slot = getOpenPGPCardSlotForKey(subkey, parentWidgetOrView()); if (slot < 1) { finished(); return; } // Check if we need to do the overwrite warning. std::string existingKey; QString encKeyWarning; if (slot == 1) { existingKey = pgpCard->sigFpr(); } else if (slot == 2) { existingKey = pgpCard->encFpr(); encKeyWarning = i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."); } else if (slot == 3) { existingKey = pgpCard->authFpr(); } if (!existingKey.empty()) { const QString message = i18nc("@info", "

This card already contains a key in this slot. Continuing will overwrite that key.

" "

If there is no backup the existing key will be irrecoverably lost.

") + i18n("The existing key has the fingerprint:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + encKeyWarning; const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message, i18nc("@title:window", "Overwrite existing key"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { finished(); return; } } // Now do the deed const auto time = QDateTime::fromSecsSinceEpoch(subkey.creationTime()); const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss")); const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 OPENPGP.%3 %4") .arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber())) .arg(slot) .arg(timestamp); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, cmd.toUtf8(), q_func(), "keyToOpenPGPCardDone"); } namespace { static std::vector getEncryptionCertificates() { std::vector encryptionCertificates = KeyCache::instance()->secretKeys(); const auto it = std::remove_if(encryptionCertificates.begin(), encryptionCertificates.end(), [](const Key &key) { return ! (key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canEncrypt() && key.subkey(0).isSecret()); }); encryptionCertificates.erase(it, encryptionCertificates.end()); return encryptionCertificates; } } Subkey KeyToCardCommand::Private::getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &/*card*/) { if (cardSlot != PIVCard::keyManagementKeyRef()) { return Subkey(); } const std::vector encryptionCertificates = getEncryptionCertificates(); if (encryptionCertificates.empty()) { error(i18n("Sorry! No suitable certificate to write to this card slot was found.")); return Subkey(); } auto dialog = new KeySelectionDialog(parentWidgetOrView()); dialog->setWindowTitle(i18nc("@title:window", "Select Certificate")); dialog->setText(i18n("Please select the certificate whose key pair you want to write to the card:")); dialog->setKeys(encryptionCertificates); if (dialog->exec() == QDialog::Rejected) { return Subkey(); } return dialog->selectedKey().subkey(0); } void KeyToCardCommand::Private::startKeyToPIVCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()"; const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); if (!pivCard) { error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (cardSlot != PIVCard::keyManagementKeyRef()) { // key to card is only supported for encryption keys finished(); return; } if (subkey.isNull()) { subkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard); } if (subkey.isNull()) { finished(); return; } if (subkey.parent().protocol() != GpgME::CMS) { error(i18n("Sorry! This key cannot be transferred to a PIV card.")); finished(); return; } if (!subkey.canEncrypt() && !subkey.canSign()) { error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card.")); finished(); return; } // Check if we need to do the overwrite warning. if (!overwriteExistingAlreadyApproved) { const std::string existingKey = pivCard->keyGrip(cardSlot); if (!existingKey.empty() && (existingKey != subkey.keyGrip())) { const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef()) ? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") : QString(); const QString message = i18nc("@info", "

This card already contains a key in this slot. Continuing will overwrite that key.

" "

If there is no backup the existing key will be irrecoverably lost.

") + i18n("The existing key has the key grip:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + decryptionWarning; const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message, i18nc("@title:window", "Overwrite existing key"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { finished(); return; } overwriteExistingAlreadyApproved = true; } } const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3") .arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber())) .arg(QString::fromStdString(cardSlot)); ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, cmd.toUtf8(), q_func(), "keyToPIVCardDone"); } void KeyToCardCommand::Private::authenticate() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()"; auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView()); connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() { authenticationFinished(); }); connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() { authenticationCanceled(); }); cmd->start(); } void KeyToCardCommand::Private::authenticationFinished() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()"; if (!hasBeenCanceled) { startKeyToPIVCard(); } } void KeyToCardCommand::Private::authenticationCanceled() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()"; hasBeenCanceled = true; canceled(); } -KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialNumber, const std::string &appName) - : CardCommand(new Private(this, key, serialNumber, appName)) +KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &subkey) + : CardCommand(new Private(this, subkey)) { } KeyToCardCommand::KeyToCardCommand(const std::string& cardSlot, const std::string &serialNumber, const std::string &appName) : CardCommand(new Private(this, cardSlot, serialNumber, appName)) { } KeyToCardCommand::~KeyToCardCommand() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()"; } -bool KeyToCardCommand::supported() +// static +std::vector > KeyToCardCommand::getSuitableCards(const GpgME::Subkey &subkey) { - return true; + std::vector > suitableCards; + if (subkey.isNull() || subkey.parent().protocol() != GpgME::OpenPGP) { + return suitableCards; + } + for (const auto &card: ReaderStatus::instance()->getCards()) { + if (card->appName() == OpenPGPCard::AppName) { + suitableCards.push_back(card); + } + } + return suitableCards; } void KeyToCardCommand::keyToOpenPGPCardDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Moving the key to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { /* TODO DELETE_KEY is too strong, because it also deletes the stub * of the secret key. I could not find out how GnuPG does this. Question * to GnuPG Developers is pending an answer if (KMessageBox::questionYesNo(d->parentWidgetOrView(), i18n("Do you want to delete the key on this computer?"), i18nc("@title:window", "Key transferred to card")) == KMessageBox::Yes) { const QString cmd = QStringLiteral("DELETE_KEY --force %1").arg(d->subkey.keyGrip()); // Using readerstatus is a bit overkill but it's an easy way to talk to the agent. ReaderStatus::mutableInstance()->startSimpleTransaction(card, cmd.toUtf8(), this, "deleteDone"); } */ d->information(i18nc("@info", "Successfully copied the key to the card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } d->finished(); } void KeyToCardCommand::keyToPIVCardDone(const GpgME::Error &err) { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::keyToPIVCardDone():" << err.asString() << "(" << err.code() << ")"; if (err) { // gpgme 1.13 reports "BAD PIN" instead of "NO AUTH" if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) { d->authenticate(); return; } d->error(i18nc("@info", "Copying the key pair to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { d->information(i18nc("@info", "Successfully copied the key pair to the card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } d->finished(); } void KeyToCardCommand::deleteDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Failed to delete the key: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } d->finished(); } void KeyToCardCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()"; d->start(); } void KeyToCardCommand::doCancel() { } #undef q_func #undef d_func diff --git a/src/commands/keytocardcommand.h b/src/commands/keytocardcommand.h index 7ba6f90a6..010151c3c 100644 --- a/src/commands/keytocardcommand.h +++ b/src/commands/keytocardcommand.h @@ -1,52 +1,60 @@ /* commands/keytocardcommand.h 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 SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ #define __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ #include #include +namespace Kleo +{ +namespace SmartCard +{ +class Card; +} +} + namespace Kleo { namespace Commands { class KeyToCardCommand : public CardCommand { Q_OBJECT public: - KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialNumber, const std::string &appName); + KeyToCardCommand(const GpgME::Subkey &subkey); KeyToCardCommand(const std::string &cardSlot, const std::string &serialNumber, const std::string &appName); ~KeyToCardCommand() override; - static bool supported(); + static std::vector > getSuitableCards(const GpgME::Subkey &subkey); public Q_SLOTS: void keyToOpenPGPCardDone(const GpgME::Error &err); void keyToPIVCardDone(const GpgME::Error &err); void deleteDone(const GpgME::Error &err); private: void doStart() override; void doCancel() override; private: class Private; inline Private *d_func(); inline const Private *d_func() const; }; } } #endif /* __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ */ diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index 02ce3a11c..b3ecb5baa 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,263 +1,255 @@ /* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "subkeyswidget.h" #include "ui_subkeyswidget.h" -#include "smartcard/openpgpcard.h" -#include "smartcard/readerstatus.h" - #include "commands/changeexpirycommand.h" #include "commands/keytocardcommand.h" #include "commands/importpaperkeycommand.h" #include "exportdialog.h" #include #include #include #include #include #include #include #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_EXPORT_FLAGS #endif #if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 # define CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS #endif #include Q_DECLARE_METATYPE(GpgME::Subkey) using namespace Kleo; +using namespace Kleo::Commands; class SubKeysWidget::Private { public: Private(SubKeysWidget *q) : q(q) { ui.setupUi(q); ui.subkeysTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.subkeysTree, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { tableContextMenuRequested(p); }); } GpgME::Key key; Ui::SubKeysWidget ui; void tableContextMenuRequested(const QPoint &p); private: SubKeysWidget *const q; }; void SubKeysWidget::Private::tableContextMenuRequested(const QPoint &p) { auto item = ui.subkeysTree->itemAt(p); if (!item) { return; } const auto subkey = item->data(0, Qt::UserRole).value(); QMenu *menu = new QMenu(q); connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); bool hasActions = false; #ifdef CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS if (subkey.parent().protocol() == GpgME::OpenPGP && subkey.parent().hasSecret()) { hasActions = true; menu->addAction(i18n("Change Expiry Date..."), q, [this, subkey]() { - auto cmd = new Kleo::Commands::ChangeExpiryCommand(subkey.parent()); + auto cmd = new ChangeExpiryCommand(subkey.parent()); if (subkey.keyID() != key.keyID()) { // do not set the primary key as subkey cmd->setSubkey(subkey); } ui.subkeysTree->setEnabled(false); - connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, + connect(cmd, &ChangeExpiryCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); key.update(); q->setKey(key); }); cmd->setParentWidget(q); cmd->start(); } ); } #endif // CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS #ifdef GPGME_HAS_EXPORT_FLAGS if (subkey.parent().protocol() == GpgME::OpenPGP && subkey.canAuthenticate()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export OpenSSH key"), q, [this, subkey]() { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(subkey, static_cast (GpgME::Context::ExportSSH)); dlg->exec(); }); } #endif // GPGME_HAS_EXPORT_FLAGS if (!subkey.isSecret()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-import")), i18n("Restore printed backup"), q, [this, subkey] () { - auto cmd = new Kleo::Commands::ImportPaperKeyCommand(subkey.parent()); + auto cmd = new ImportPaperKeyCommand(subkey.parent()); ui.subkeysTree->setEnabled(false); - connect(cmd, &Kleo::Commands::ImportPaperKeyCommand::finished, + connect(cmd, &ImportPaperKeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } - if (subkey.isSecret() && Kleo::Commands::KeyToCardCommand::supported()) { - const auto cards = SmartCard::ReaderStatus::instance()->getCards(); - if (cards.size() && cards[0]->appName() == SmartCard::OpenPGPCard::AppName) { - const auto card = cards[0]; - - if (!subkey.cardSerialNumber() || card->serialNumber() != subkey.cardSerialNumber()) { - hasActions = true; - menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), - i18n("Transfer to smartcard"), - q, [this, subkey, card]() { - auto cmd = new Kleo::Commands::KeyToCardCommand(subkey, card->serialNumber(), card->appName()); - ui.subkeysTree->setEnabled(false); - connect(cmd, &Kleo::Commands::KeyToCardCommand::finished, - q, [this]() { ui.subkeysTree->setEnabled(true); }); - cmd->setParentWidget(q); - cmd->start(); - }); - } - } + if (subkey.isSecret()) { + hasActions = true; + auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), + i18n("Transfer to smartcard"), + q, [this, subkey]() { + auto cmd = new KeyToCardCommand(subkey); + ui.subkeysTree->setEnabled(false); + connect(cmd, &KeyToCardCommand::finished, + q, [this]() { ui.subkeysTree->setEnabled(true); }); + cmd->setParentWidget(q); + cmd->start(); + }); + action->setEnabled(!KeyToCardCommand::getSuitableCards(subkey).empty()); } if (hasActions) { menu->popup(ui.subkeysTree->viewport()->mapToGlobal(p)); } else { delete menu; } } SubKeysWidget::SubKeysWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } SubKeysWidget::~SubKeysWidget() { } void SubKeysWidget::setKey(const GpgME::Key &key) { d->key = key; const auto currentItem = d->ui.subkeysTree->currentItem(); const QByteArray selectedKeyFingerprint = currentItem ? QByteArray(currentItem->data(0, Qt::UserRole).value().fingerprint()) : QByteArray(); d->ui.subkeysTree->clear(); for (const auto &subkey : key.subkeys()) { auto item = new QTreeWidgetItem(); item->setData(0, Qt::DisplayRole, Formatting::prettyID(subkey.keyID())); item->setData(0, Qt::UserRole, QVariant::fromValue(subkey)); item->setData(1, Qt::DisplayRole, Kleo::Formatting::type(subkey)); item->setData(2, Qt::DisplayRole, Kleo::Formatting::creationDateString(subkey)); item->setData(3, Qt::DisplayRole, Kleo::Formatting::expirationDateString(subkey)); item->setData(4, Qt::DisplayRole, Kleo::Formatting::validityShort(subkey)); switch (subkey.publicKeyAlgorithm()) { case GpgME::Subkey::AlgoECDSA: case GpgME::Subkey::AlgoEDDSA: case GpgME::Subkey::AlgoECDH: item->setData(5, Qt::DisplayRole, QString::fromStdString(subkey.algoName())); break; default: item->setData(5, Qt::DisplayRole, QString::number(subkey.length())); } item->setData(6, Qt::DisplayRole, Kleo::Formatting::usageString(subkey)); item->setData(7, Qt::DisplayRole, subkey.keyID() == key.keyID() ? QStringLiteral("✓") : QString()); d->ui.subkeysTree->addTopLevelItem(item); if (subkey.fingerprint() == selectedKeyFingerprint) { d->ui.subkeysTree->setCurrentItem(item); } } const auto subkey = key.subkey(0); if (const char *card = subkey.cardSerialNumber()) { d->ui.stored->setText(i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { d->ui.stored->setText(i18nc("stored...", "on this computer")); } d->ui.subkeysTree->resizeColumnToContents(0); } GpgME::Key SubKeysWidget::key() const { return d->key; } SubKeysDialog::SubKeysDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Subkeys Details")); auto l = new QVBoxLayout(this); l->addWidget(new SubKeysWidget(this)); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::clicked, this, &QDialog::accept); l->addWidget(bbox); readConfig(); } SubKeysDialog::~SubKeysDialog() { writeConfig(); } void SubKeysDialog::readConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "SubKeysDialog"); const QSize size = dialog.readEntry("Size", QSize(820, 280)); if (size.isValid()) { resize(size); } } void SubKeysDialog::writeConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "SubKeysDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } void SubKeysDialog::setKey(const GpgME::Key &key) { auto w = findChild(); Q_ASSERT(w); w->setKey(key); } GpgME::Key SubKeysDialog::key() const { auto w = findChild(); Q_ASSERT(w); return w->key(); } diff --git a/src/smartcard/utils.cpp b/src/smartcard/utils.cpp new file mode 100644 index 000000000..f32dc7e5f --- /dev/null +++ b/src/smartcard/utils.cpp @@ -0,0 +1,33 @@ +/* smartcard/utils.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "utils.h" + +#include "netkeycard.h" +#include "openpgpcard.h" +#include "pivcard.h" + +#include + +#include + +using namespace Kleo::SmartCard; + +QString Kleo::SmartCard::displayAppName(const std::string &appName) +{ + if (appName == NetKeyCard::AppName) { + return i18nc("proper name of a type of smartcard", "NetKey"); + } else if (appName == OpenPGPCard::AppName) { + return i18nc("proper name of a type of smartcard", "OpenPGP"); + } else if (appName == PIVCard::AppName) { + return i18nc("proper name of a type of smartcard", "PIV"); + } else { + return QString::fromStdString(appName); + } +} diff --git a/src/smartcard/utils.h b/src/smartcard/utils.h new file mode 100644 index 000000000..b7552f108 --- /dev/null +++ b/src/smartcard/utils.h @@ -0,0 +1,26 @@ +/* smartcard/utils.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef SMARTCARD_UTILS_H +#define SMARTCARD_UTILS_H + +#include + +class QString; + +namespace Kleo +{ +namespace SmartCard +{ + +QString displayAppName(const std::string &appName); + +} // namespace Smartcard +} // namespace Kleopatra + +#endif // SMARTCARD_UTILS_H diff --git a/src/view/smartcardwidget.cpp b/src/view/smartcardwidget.cpp index 14ce38b66..5bc39bc9e 100644 --- a/src/view/smartcardwidget.cpp +++ b/src/view/smartcardwidget.cpp @@ -1,208 +1,193 @@ /* view/smartcardwidget.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 SPDX-License-Identifier: GPL-2.0-or-later */ #include "smartcardwidget.h" #include "smartcard/readerstatus.h" #include "smartcard/openpgpcard.h" #include "smartcard/netkeycard.h" #include "smartcard/pivcard.h" +#include "smartcard/utils.h" #include "view/pgpcardwidget.h" #include "view/netkeywidget.h" #include "view/pivcardwidget.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::SmartCard; 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 = QStringList() << QStringLiteral("OpenPGP v2.0 - v3.3") << QStringLiteral("Gnuk") << QStringLiteral("NetKey v3") << QStringLiteral("PIV"); lay->addWidget(new QLabel(QStringLiteral("\t\t

") + i18n("Please insert a compatible smartcard.") + QStringLiteral("

"), this)); lay->addSpacing(10); lay->addWidget(new QLabel(QStringLiteral("\t\t") + i18n("Kleopatra currently supports the following card types:") + QStringLiteral("
  • ") + supported.join(QLatin1String("
  • ")) + QStringLiteral("
"), this)); lay->addSpacing(10); lay->addWidget(new QLabel(i18n("Refresh the view (F5) to update the smartcard status."), this)); lay->addStretch(-1); auto hLay = new QHBoxLayout(this); hLay->addStretch(-1); hLay->addLayout(lay); hLay->addStretch(-1); lay->addStretch(-1); } }; } // namespace class SmartCardWidget::Private { public: Private(SmartCardWidget *qq); void cardAddedOrChanged(const std::string &serialNumber, const std::string &appName); void cardRemoved(const std::string &serialNumber, const std::string &appName); private: template void cardAddedOrChanged(const std::string &serialNumber); private: SmartCardWidget *const q; QMap, QPointer > mCardWidgets; PlaceHolderWidget *mPlaceHolderWidget; QStackedWidget *mStack; QTabWidget *mTabWidget; }; SmartCardWidget::Private::Private(SmartCardWidget *qq) : q(qq) { QPushButton *backBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("Back")); QHBoxLayout *backH = new QHBoxLayout; backH->addWidget(backBtn); backH->addWidget(new QLabel(QStringLiteral("

") + i18n("Smartcard Management") + QStringLiteral("

"))); backH->addStretch(-1); QVBoxLayout *vLay = new QVBoxLayout(q); connect(backBtn, &QPushButton::clicked, q, [this] () {Q_EMIT q->backRequested();}); vLay->addLayout(backH); mStack = new QStackedWidget; vLay->addWidget(mStack); mPlaceHolderWidget = new PlaceHolderWidget; mStack->addWidget(mPlaceHolderWidget); mTabWidget = new QTabWidget; mStack->addWidget(mTabWidget); mStack->setCurrentWidget(mPlaceHolderWidget); 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); }); } void SmartCardWidget::Private::cardAddedOrChanged(const std::string &serialNumber, const std::string &appName) { if (appName == SmartCard::NetKeyCard::AppName) { cardAddedOrChanged(serialNumber); } else if (appName == SmartCard::OpenPGPCard::AppName) { cardAddedOrChanged(serialNumber); } else if (appName == SmartCard::PIVCard::AppName) { cardAddedOrChanged(serialNumber); } else { qCWarning(KLEOPATRA_LOG) << "SmartCardWidget::Private::cardAddedOrChanged:" << "App" << appName.c_str() << "is not supported"; } } -namespace -{ -QString displayAppName(const std::string &appName) -{ - if (appName == SmartCard::NetKeyCard::AppName) { - return i18nc("proper name of a type of smartcard", "NetKey"); - } else if (appName == SmartCard::OpenPGPCard::AppName) { - return i18nc("proper name of a type of smartcard", "OpenPGP"); - } else if (appName == SmartCard::PIVCard::AppName) { - return i18nc("proper name of a type of smartcard", "PIV"); - } else { - return QString::fromStdString(appName); - } -} -} - template void SmartCardWidget::Private::cardAddedOrChanged(const std::string &serialNumber) { const auto card = ReaderStatus::instance()->getCard(serialNumber); if (!card) { qCWarning(KLEOPATRA_LOG) << "SmartCardWidget::Private::cardAddedOrChanged:" << "New or changed card" << serialNumber.c_str() << "with app" << C::AppName.c_str() << "not found"; return; } W *cardWidget = dynamic_cast(mCardWidgets.value({serialNumber, C::AppName}).data()); if (!cardWidget) { cardWidget = new W; mCardWidgets.insert({serialNumber, C::AppName}, cardWidget); const QString cardLabel = i18nc("@title:tab smartcard application - serial number of smartcard", "%1 - %2", displayAppName(C::AppName), card->displaySerialNumber()); mTabWidget->addTab(cardWidget, cardLabel); if (mCardWidgets.size() == 1) { mStack->setCurrentWidget(mTabWidget); } } cardWidget->setCard(card.get()); } void SmartCardWidget::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); } } SmartCardWidget::SmartCardWidget(QWidget *parent): QWidget(parent), d(new Private(this)) { } void SmartCardWidget::reload() { ReaderStatus::mutableInstance()->updateStatus(); } #include "smartcardwidget.moc"