diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5ef61c820..0ce637008 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,349 +1,350 @@ 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/gnupg-helper.cpp 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 ${_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 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/smartcard/keypairinfo.cpp b/src/smartcard/keypairinfo.cpp new file mode 100644 index 000000000..64c2ebd76 --- /dev/null +++ b/src/smartcard/keypairinfo.cpp @@ -0,0 +1,39 @@ +/* smartcard/keypairinfo.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 "keypairinfo.h" + +#include +#include + +using namespace Kleo::SmartCard; + +// static +KeyPairInfo KeyPairInfo::fromStatusLine(const std::string &s) { + // The format of a KEYPAIRINFO line is + // KEYPAIRINFO [usage] [keytime] [algostr] + // The string s does not contain the leading "KEYPAIRINFO ". + KeyPairInfo info; + const auto values = QString::fromStdString(s).split(QLatin1Char(' ')); + if (values.size() < 2) { + return info; + } + info.grip = values[0].toStdString(); + info.keyRef = values[1].toStdString(); + if (values.size() >= 3) { + info.usage = values[2].toStdString(); + } + if (values.size() >= 4) { + info.keyTime = values[3].toStdString(); + } + if (values.size() >= 5) { + info.algorithm = values[4].toStdString(); + } + return info; +} diff --git a/src/smartcard/keypairinfo.h b/src/smartcard/keypairinfo.h new file mode 100644 index 000000000..3e3e8bc09 --- /dev/null +++ b/src/smartcard/keypairinfo.h @@ -0,0 +1,30 @@ +/* smartcard/keypairinfo.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_KEYPAIRINFO_H +#define SMARTCARD_KEYPAIRINFO_H + +#include + +namespace Kleo +{ +namespace SmartCard +{ +struct KeyPairInfo { + static KeyPairInfo fromStatusLine(const std::string &s); + + std::string grip; + std::string keyRef; + std::string usage; + std::string keyTime; + std::string algorithm; +}; +} // namespace Smartcard +} // namespace Kleopatra + +#endif // SMARTCARD_PIVCARD_H diff --git a/src/smartcard/pivcard.cpp b/src/smartcard/pivcard.cpp index be0852a51..3e5a47c5d 100644 --- a/src/smartcard/pivcard.cpp +++ b/src/smartcard/pivcard.cpp @@ -1,159 +1,182 @@ /* smartcard/pivcard.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 "pivcard.h" +#include "keypairinfo.h" + #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; PIVCard::PIVCard() { setAppType(Card::PivApplication); } PIVCard::PIVCard(const std::string &serialno): PIVCard() { setSerialNumber(serialno); } // static std::string PIVCard::pivAuthenticationKeyRef() { return std::string("PIV.9A"); } // static std::string PIVCard::cardAuthenticationKeyRef() { return std::string("PIV.9E"); } // static std::string PIVCard::digitalSignatureKeyRef() { return std::string("PIV.9C"); } // static std::string PIVCard::keyManagementKeyRef() { return std::string("PIV.9D"); } // static std::string PIVCard::pinKeyRef() { return std::string("PIV.80"); } // static std::string PIVCard::pukKeyRef() { return std::string("PIV.81"); } +// static +const std::vector & PIVCard::supportedKeys() +{ + static const std::vector keyRefs = { + PIVCard::pivAuthenticationKeyRef(), + PIVCard::cardAuthenticationKeyRef(), + PIVCard::digitalSignatureKeyRef(), + PIVCard::keyManagementKeyRef() + }; + + return keyRefs; +} + // static QString PIVCard::keyDisplayName(const std::string &keyRef) { static const QMap displayNames = { { PIVCard::pivAuthenticationKeyRef(), i18n("PIV Authentication Key") }, { PIVCard::cardAuthenticationKeyRef(), i18n("Card Authentication Key") }, { PIVCard::digitalSignatureKeyRef(), i18n("Digital Signature Key") }, { PIVCard::keyManagementKeyRef(), i18n("Key Management Key") }, }; return displayNames.value(keyRef); } // static std::vector< std::pair > PIVCard::supportedAlgorithms(const std::string &keyRef) { if (keyRef == PIVCard::keyManagementKeyRef()) { return { { "rsa2048", i18n("RSA key transport (2048 bits)") }, { "nistp256", i18n("ECDH (Curve P-256)") }, { "nistp384", i18n("ECDH (Curve P-384)") } }; } else if (keyRef == PIVCard::digitalSignatureKeyRef()) { return { { "rsa2048", i18n("RSA (2048 bits)") }, { "nistp256", i18n("ECDSA (Curve P-256)") }, { "nistp384", i18n("ECDSA (Curve P-384)") } }; } // NIST SP 800-78-4 does not allow Curve P-384 for PIV Authentication key or Card Authentication key return { { "rsa2048", i18n("RSA (2048 bits)") }, { "nistp256", i18n("ECDSA (Curve P-256)") }, }; } std::string PIVCard::keyGrip(const std::string& keyRef) const { return mMetaInfo.value("KEYPAIRINFO-" + keyRef); } namespace { static int parseAppVersion(const std::string &s) { // s is a hex-encoded, unsigned int-packed version tuple bool ok; const auto appVersion = QByteArray::fromStdString(s).toUInt(&ok, 16); return ok ? appVersion : -1; } } void PIVCard::setCardInfo(const std::vector< std::pair > &infos) { qCDebug(KLEOPATRA_LOG) << "Card" << serialNumber().c_str() << "info:"; for (const auto &pair: infos) { qCDebug(KLEOPATRA_LOG) << pair.first.c_str() << ":" << pair.second.c_str(); if (pair.first == "APPVERSION") { setAppVersion(parseAppVersion(pair.second)); } else if (pair.first == "KEYPAIRINFO") { - const auto values = QString::fromStdString(pair.second).split(QLatin1Char(' ')); - if (values.size() != 3) { - qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO entry" << QString::fromStdString(pair.second); + const KeyPairInfo info = KeyPairInfo::fromStatusLine(pair.second); + if (info.grip.empty()) { + qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" + << QString::fromStdString(pair.second); setStatus(Card::CardError); continue; } - const auto grip = values[0].toStdString(); - const auto keyRef = values[1].toStdString(); - //const auto usage = values[2]; - mMetaInfo.insert("KEYPAIRINFO-" + keyRef, grip); + mMetaInfo.insert("KEYPAIRINFO-" + info.keyRef, info.grip); } else { mMetaInfo.insert(pair.first, pair.second); } } } std::string PIVCard::displaySerialNumber() const { return mDisplaySerialNumber; } void PIVCard::setDisplaySerialNumber(const std::string &serialno) { mDisplaySerialNumber = serialno; } +std::string PIVCard::keyAlgorithm(const std::string &keyRef) const +{ + return mMetaInfo.value("KLEO-KEYALGO-" + keyRef); +} + +void PIVCard::setKeyAlgorithm(const std::string &keyRef, const std::string &algorithm) +{ + mMetaInfo.insert("KLEO-KEYALGO-" + keyRef, algorithm); +} + bool PIVCard::operator == (const Card& rhs) const { const PIVCard *other = dynamic_cast(&rhs); if (!other) { return false; } return Card::operator ==(rhs) && mMetaInfo == other->mMetaInfo; } diff --git a/src/smartcard/pivcard.h b/src/smartcard/pivcard.h index 9e27b93d9..21a3d81dc 100644 --- a/src/smartcard/pivcard.h +++ b/src/smartcard/pivcard.h @@ -1,54 +1,58 @@ /* smartcard/pivcard.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_PIVCARD_H #define SMARTCARD_PIVCARD_H #include #include "card.h" namespace Kleo { namespace SmartCard { /** Class to work with PIV smartcards or compatible tokens */ class PIVCard: public Card { public: PIVCard (); PIVCard (const std::string &serialno); static std::string pivAuthenticationKeyRef(); static std::string cardAuthenticationKeyRef(); static std::string digitalSignatureKeyRef(); static std::string keyManagementKeyRef(); static std::string pinKeyRef(); static std::string pukKeyRef(); + static const std::vector & supportedKeys(); static QString keyDisplayName(const std::string &keyRef); static std::vector< std::pair > supportedAlgorithms(const std::string &keyRef); std::string keyGrip(const std::string &keyRef) const; void setCardInfo (const std::vector< std::pair > &infos); std::string displaySerialNumber() const; void setDisplaySerialNumber(const std::string &sn); + std::string keyAlgorithm(const std::string &keyRef) const; + void setKeyAlgorithm(const std::string &keyRef, const std::string &algorithm); + bool operator == (const Card& other) const override; private: std::string mDisplaySerialNumber; QMap mMetaInfo; }; } // namespace Smartcard } // namespace Kleopatra #endif // SMARTCARD_PIVCARD_H diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index b0df9996f..38cc09c59 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,778 +1,808 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "readerstatus.h" +#include "keypairinfo.h" + #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "openpgpcard.h" #include "netkeycard.h" #include "pivcard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/kdtoolsglobal.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace GpgME; static ReaderStatus *self = nullptr; #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) static const char *flags[] = { "NOCARD", "PRESENT", "ACTIVE", "USABLE", }; static_assert(sizeof flags / sizeof * flags == Card::_NumScdStates, ""); static const char *prettyFlags[] = { "NoCard", "CardPresent", "CardActive", "CardUsable", "CardError", }; static_assert(sizeof prettyFlags / sizeof * prettyFlags == Card::NumStates, ""); #if 0 We need this once we have support for multiple readers in scdaemons interface. static unsigned int parseFileName(const QString &fileName, bool *ok) { QRegExp rx(QLatin1String("reader_(\\d+)\\.status")); if (ok) { *ok = false; } if (rx.exactMatch(QFileInfo(fileName).fileName())) { return rx.cap(1).toUInt(ok, 10); } return 0; } #endif Q_DECLARE_METATYPE(GpgME::Error) namespace { static QDebug operator<<(QDebug s, const std::vector< std::pair > &v) { typedef std::pair pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n'; } return s << ')'; } static const char *app_types[] = { "_", // will hopefully never be used as an app-type :) "openpgp", "piv", "nks", "p15", "dinsig", "geldkarte", }; static_assert(sizeof app_types / sizeof * app_types == Card::NumAppTypes, ""); static Card::AppType parse_app_type(const std::string &s) { qCDebug(KLEOPATRA_LOG) << "parse_app_type(" << s.c_str() << ")"; const char **it = std::find_if(std::begin(app_types), std::end(app_types), [&s](const char *type) { return ::strcasecmp(s.c_str(), type) == 0; }); if (it == std::end(app_types)) { qCDebug(KLEOPATRA_LOG) << "App type not found"; return Card::UnknownApplication; } return static_cast(it - std::begin(app_types)); } static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } template static std::unique_ptr gpgagent_transact(std::shared_ptr &gpgAgent, const char *command, std::unique_ptr transaction, Error &err) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")"; err = gpgAgent->assuanTransact(command, std::move(transaction)); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "):" << QString::fromLocal8Bit(err.asString()); if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) { qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context"; gpgAgent.reset(); } return std::unique_ptr(); } std::unique_ptr t = gpgAgent->takeLastAssuanTransaction(); return std::unique_ptr(dynamic_cast(t.release())); } static std::unique_ptr gpgagent_default_transact(std::shared_ptr &gpgAgent, const char *command, Error &err) { return gpgagent_transact(gpgAgent, command, std::unique_ptr(new DefaultAssuanTransaction), err); } -const std::vector< std::pair > gpgagent_statuslines(std::shared_ptr gpgAgent, const char *what, Error &err) +static const std::vector< std::pair > gpgagent_statuslines(std::shared_ptr gpgAgent, const char *what, Error &err) { const std::unique_ptr t = gpgagent_default_transact(gpgAgent, what, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): got" << t->statusLines(); return t->statusLines(); } else { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): t == NULL"; return std::vector >(); } } static const std::string gpgagent_status(const std::shared_ptr &gpgAgent, const char *what, Error &err) { const auto lines = gpgagent_statuslines (gpgAgent, what, err); // The status is only the last attribute // e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO // it would only be FOO const char *p = strrchr(what, ' '); const char *needle = (p + 1) ? (p + 1) : what; for (const auto &pair: lines) { if (pair.first == needle) { return pair.second; } } return std::string(); } static const std::string scd_getattr_status(std::shared_ptr &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return gpgagent_status(gpgAgent, cmd.c_str(), err); } static const char * get_openpgp_card_manufacturer_from_serial_number(const std::string &serialno) { qCDebug(KLEOPATRA_LOG) << "get_openpgp_card_manufacturer_from_serial_number(" << serialno.c_str() << ")"; const bool isProperOpenPGPCardSerialNumber = serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401"; if (isProperOpenPGPCardSerialNumber) { const char *sn = serialno.c_str(); const int manufacturerId = xtoi_2(sn + 16)*256 + xtoi_2(sn + 18); switch (manufacturerId) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid"; case 0x0005: return "ZeitControl"; case 0x0006: return "Yubico"; case 0x0007: return "OpenKMS"; case 0x0008: return "LogoEmail"; case 0x002A: return "Magrathea"; case 0x1337: return "Warsaw Hackerspace"; case 0xF517: return "FSIJ"; /* 0x0000 and 0xFFFF are defined as test cards per spec, 0xFF00 to 0xFFFE are assigned for use with randomly created serial numbers. */ case 0x0000: case 0xffff: return "test card"; default: return (manufacturerId & 0xff00) == 0xff00 ? "unmanaged S/N range" : "unknown"; } } else { return "unknown"; } } static const std::string get_manufacturer(std::shared_ptr &gpgAgent, Error &err) { // The result of SCD GETATTR MANUFACTURER is the manufacturer ID as unsigned number // optionally followed by the name of the manufacturer, e.g. // 6 Yubico // 65534 unmanaged S/N range const auto manufacturerIdAndName = scd_getattr_status(gpgAgent, "MANUFACTURER", err); if (err.code()) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): Querying for attribute MANUFACTURER not yet supported; needs GnuPG 2.2.21+"; } else { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; } return std::string(); } const auto startOfManufacturerName = manufacturerIdAndName.find(' '); if (startOfManufacturerName == std::string::npos) { // only ID without manufacturer name return "unknown"; } const auto manufacturerName = manufacturerIdAndName.substr(startOfManufacturerName + 1); return manufacturerName; } static void handle_openpgp_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto ret = new OpenPGPCard(); ret->setSerialNumber(ci->serialNumber()); ret->setManufacturer(get_manufacturer(gpg_agent, err)); if (err.code()) { // fallback, e.g. if gpg does not yet support querying for the MANUFACTURER attribute ret->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code()) { ci->setStatus(Card::CardError); return; } ret->setKeyPairInfo(info); ci.reset(ret); } static void handle_piv_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto pivCard = new PIVCard(); pivCard->setSerialNumber(ci->serialNumber()); const auto displaySerialnumber = scd_getattr_status(gpg_agent, "$DISPSERIALNO", err); - if (err.code()) { + if (err) { qCWarning(KLEOPATRA_LOG) << "handle_piv_card(): Error on GETATTR $DISPSERIALNO:" << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; } - pivCard->setDisplaySerialNumber(err.code() ? ci->serialNumber() : displaySerialnumber); + pivCard->setDisplaySerialNumber(err ? ci->serialNumber() : displaySerialnumber); const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); - if (err.code()) { + if (err) { ci->setStatus(Card::CardError); return; } pivCard->setCardInfo(info); + + for (const std::string &keyRef : PIVCard::supportedKeys()) { + if (!pivCard->keyGrip(keyRef).empty()) { + const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef; + const auto keyPairInfoLines = gpgagent_statuslines(gpg_agent, command.c_str(), err); + if (err) { + qCWarning(KLEOPATRA_LOG) << "handle_piv_card(): Error on " << QString::fromStdString(command) << ":" + << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; + break; + } + for (const auto &pair: keyPairInfoLines) { + if (pair.first == "KEYPAIRINFO") { + const KeyPairInfo info = KeyPairInfo::fromStatusLine(pair.second); + if (info.grip.empty()) { + qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" + << QString::fromStdString(pair.second); + continue; + } + pivCard->setKeyAlgorithm(keyRef, info.algorithm); + } else { + qCWarning(KLEOPATRA_LOG) << "handle_piv_card(): Unexpected status line on " + << QString::fromStdString(command) << ":" << QString::fromStdString(pair.first) + << QString::fromStdString(pair.second); + } + } + } + } + ci.reset(pivCard); } static void handle_netkey_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto nkCard = new NetKeyCard(); nkCard->setSerialNumber(ci->serialNumber()); ci.reset(nkCard); ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err))); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "NKS-VERSION resulted in error" << err.asString(); ci->setErrorMsg(QStringLiteral ("NKS-VERSION failed: ") + QString::fromUtf8(err.asString())); return; } if (ci->appVersion() != 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 card, giving up. Version:" << ci->appVersion(); ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion())); return; } // the following only works for NKS v3... const auto chvStatus = QString::fromStdString( scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QLatin1Char(' ')); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "no CHV-STATUS" << err.asString(); ci->setErrorMsg(QStringLiteral ("CHV-Status failed: ") + QString::fromUtf8(err.asString())); return; } std::vector states; states.reserve(chvStatus.count()); // CHV Status for NKS v3 is // Pin1 (Normal pin) Pin2 (Normal PUK) // SigG1 SigG PUK. int num = 0; for (const auto &state: chvStatus) { const auto parsed = parse_pin_state (state); states.push_back(parsed); if (parsed == Card::NullPin) { if (num == 0) { ci->setHasNullPin(true); } } ++num; } nkCard->setPinStates(states); // check for keys to learn: const std::unique_ptr result = gpgagent_default_transact(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code() || !result.get()) { if (err) { ci->setErrorMsg(QString::fromLatin1(err.asString())); } else { ci->setErrorMsg(QStringLiteral("Invalid internal state. No result.")); } return; } const std::vector keyPairInfos = result->statusLine("KEYPAIRINFO"); if (keyPairInfos.empty()) { return; } nkCard->setKeyPairInfo(keyPairInfos); } static std::shared_ptr get_card_status(unsigned int slot, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << slot << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr (new Card()); if (slot != 0 || !gpg_agent) { // In the future scdaemon should support multiple slots but // not yet (2.1.18) return ci; } Error err; ci->setSerialNumber(gpgagent_status(gpg_agent, "SCD SERIALNO", err)); if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); return ci; } if (err.code()) { ci->setStatus(Card::CardError); return ci; } ci->setStatus(Card::CardPresent); const auto verbatimType = scd_getattr_status(gpg_agent, "APPTYPE", err); ci->setAppType(parse_app_type(verbatimType)); if (err.code()) { return ci; } // Handle different card types if (ci->appType() == Card::NksApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end"; handle_netkey_card(ci, gpg_agent); return ci; } else if (ci->appType() == Card::OpenPGPApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; handle_openpgp_card(ci, gpg_agent); return ci; } else if (ci->appType() == Card::PivApplication) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end"; handle_piv_card(ci, gpg_agent); return ci; } else { qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << verbatimType.c_str(); return ci; } return ci; } static std::vector > update_cardinfo(std::shared_ptr &gpgAgent) { // Multiple smartcard readers are only supported internally by gnupg // but not by scdaemon (Status gnupg 2.1.18) // We still pretend that there can be multiple cards inserted // at once but we don't handle it yet. const auto ci = get_card_status(0, gpgAgent); return std::vector >(1, ci); } } // namespace struct Transaction { QByteArray command; QPointer receiver; const char *slot; AssuanTransaction* assuanTransaction; }; static const Transaction updateTransaction = { "__update__", nullptr, nullptr, nullptr }; static const Transaction quitTransaction = { "__quit__", nullptr, nullptr, nullptr }; namespace { class ReaderStatusThread : public QThread { Q_OBJECT public: explicit ReaderStatusThread(QObject *parent = nullptr) : QThread(parent), m_gnupgHomePath(Kleo::gnupgHomeDirectory()), m_transactions(1, updateTransaction) // force initial scan { connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished); } std::vector > cardInfos() const { const QMutexLocker locker(&m_mutex); return m_cardInfos; } Card::Status cardStatus(unsigned int slot) const { const QMutexLocker locker(&m_mutex); if (slot < m_cardInfos.size()) { return m_cardInfos[slot]->status(); } else { return Card::NoCard; } } void addTransaction(const Transaction &t) { const QMutexLocker locker(&m_mutex); m_transactions.push_back(t); m_waitForTransactions.wakeOne(); } Q_SIGNALS: void anyCardHasNullPinChanged(bool); void anyCardCanLearnKeysChanged(bool); void cardChanged(unsigned int); void oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: void ping() { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()"; addTransaction(updateTransaction); } void stop() { const QMutexLocker locker(&m_mutex); m_transactions.push_front(quitTransaction); m_waitForTransactions.wakeOne(); } private Q_SLOTS: void slotOneTransactionFinished(const GpgME::Error &err) { std::list ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); Q_FOREACH (const Transaction &t, ft) if (t.receiver && t.slot && *t.slot) { QMetaObject::invokeMethod(t.receiver, t.slot, Qt::DirectConnection, Q_ARG(GpgME::Error, err)); } } private: void run() override { while (true) { std::shared_ptr gpgAgent; QByteArray command; bool nullSlot = false; AssuanTransaction* assuanTransaction = nullptr; std::list item; std::vector > oldCards; Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: command = item.front().command; nullSlot = !item.front().slot; // we take ownership of the assuan transaction std::swap(assuanTransaction, item.front().assuanTransaction); oldCards = m_cardInfos; } qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot; // now, let's see what we got: if (nullSlot && command == quitTransaction.command) { return; // quit } if ((nullSlot && command == updateTransaction.command)) { std::vector > newCards = update_cardinfo(gpgAgent); newCards.resize(std::max(newCards.size(), oldCards.size())); oldCards.resize(std::max(newCards.size(), oldCards.size())); KDAB_SYNCHRONIZED(m_mutex) m_cardInfos = newCards; std::vector >::const_iterator nit = newCards.begin(), nend = newCards.end(), oit = oldCards.begin(), oend = oldCards.end(); unsigned int idx = 0; bool anyLC = false; bool anyNP = false; bool anyError = false; while (nit != nend && oit != oend) { const auto optr = (*oit).get(); const auto nptr = (*nit).get(); if ((optr && !nptr) || (!optr && nptr) || (optr && nptr && *optr != *nptr)) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: slot" << idx << ": card Changed"; Q_EMIT cardChanged(idx); } if ((*nit)->canLearnKeys()) { anyLC = true; } if ((*nit)->hasNullPin()) { anyNP = true; } if ((*nit)->status() == Card::CardError) { anyError = true; } ++nit; ++oit; ++idx; } Q_EMIT anyCardHasNullPinChanged(anyNP); Q_EMIT anyCardCanLearnKeysChanged(anyLC); if (anyError) { gpgAgent.reset(); } } else { GpgME::Error err; if (assuanTransaction) { (void)gpgagent_transact(gpgAgent, command.constData(), std::unique_ptr(assuanTransaction), err); } else { (void)gpgagent_default_transact(gpgAgent, command.constData(), err); } KDAB_SYNCHRONIZED(m_mutex) // splice 'item' into m_finishedTransactions: m_finishedTransactions.splice(m_finishedTransactions.end(), item); Q_EMIT oneTransactionFinished(err); } } } private: mutable QMutex m_mutex; QWaitCondition m_waitForTransactions; const QString m_gnupgHomePath; // protected by m_mutex: std::vector > m_cardInfos; std::list m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq), q(qq), watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType("Kleo::SmartCard::Card::Status"); qRegisterMetaType("GpgME::Error"); watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::anyCardHasNullPinChanged, q, &ReaderStatus::anyCardHasNullPinChanged); connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } ~Private() { stop(); if (!wait(100)) { terminate(); wait(); } } private: bool anyCardHasNullPinImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->hasNullPin(); }); } bool anyCardCanLearnKeysImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->canLearnKeys(); }); } private: FileSystemWatcher watcher; }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent), d(new Private(this)) { self = this; } ReaderStatus::~ReaderStatus() { self = nullptr; } // slot void ReaderStatus::startMonitoring() { d->start(); } // static ReaderStatus *ReaderStatus::mutableInstance() { return self; } // static const ReaderStatus *ReaderStatus::instance() { return self; } Card::Status ReaderStatus::cardStatus(unsigned int slot) const { return d->cardStatus(slot); } bool ReaderStatus::anyCardHasNullPin() const { return d->anyCardHasNullPinImpl(); } bool ReaderStatus::anyCardCanLearnKeys() const { return d->anyCardCanLearnKeysImpl(); } std::vector ReaderStatus::pinStates(unsigned int slot) const { const auto ci = d->cardInfos(); if (slot < ci.size()) { return ci[slot]->pinStates(); } else { return std::vector(); } } void ReaderStatus::startSimpleTransaction(const QByteArray &command, QObject *receiver, const char *slot) { const Transaction t = { command, receiver, slot, nullptr }; d->addTransaction(t); } void ReaderStatus::startTransaction(const QByteArray &command, QObject *receiver, const char *slot, std::unique_ptr transaction) { const Transaction t = { command, receiver, slot, transaction.release() }; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } std::vector > ReaderStatus::getCards() const { return d->cardInfos(); } #include "readerstatus.moc" diff --git a/src/view/pivcardwidget.cpp b/src/view/pivcardwidget.cpp index 24bac0f21..befe52d44 100644 --- a/src/view/pivcardwidget.cpp +++ b/src/view/pivcardwidget.cpp @@ -1,268 +1,282 @@ /* view/pivcardwiget.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 "pivcardwidget.h" #include "commands/certificatetopivcardcommand.h" #include "commands/changepincommand.h" #include "commands/keytocardcommand.h" #include "commands/pivgeneratecardkeycommand.h" #include "commands/setpivcardapplicationadministrationkeycommand.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; namespace { static QString formatVersion(int value) { if (value < 0) { return QLatin1String("n/a"); } const unsigned int a = ((value >> 24) & 0xff); const unsigned int b = ((value >> 16) & 0xff); const unsigned int c = ((value >> 8) & 0xff); const unsigned int d = ((value ) & 0xff); if (a) { return QStringLiteral("%1.%2.%3.%4").arg(QString::number(a), QString::number(b), QString::number(c), QString::number(d)); } else if (b) { return QStringLiteral("%1.%2.%3").arg(QString::number(b), QString::number(c), QString::number(d)); } else if (c) { return QStringLiteral("%1.%2").arg(QString::number(c), QString::number(d)); } return QString::number(d); } } PIVCardWidget::PIVCardWidget(QWidget *parent): QWidget(parent), mSerialNumber(new QLabel(this)), mVersionLabel(new QLabel(this)) { auto grid = new QGridLayout; int row = 0; // Set up the scroll are auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); auto areaWidget = new QWidget; auto areaVLay = new QVBoxLayout(areaWidget); areaVLay->addLayout(grid); areaVLay->addStretch(1); area->setWidget(areaWidget); auto myLayout = new QVBoxLayout(this); myLayout->addWidget(area); // Version and Serialnumber grid->addWidget(mVersionLabel, row++, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Serial number:")), row, 0); grid->addWidget(mSerialNumber, row++, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); // The keys auto line1 = new QFrame(); line1->setFrameShape(QFrame::HLine); - grid->addWidget(line1, row++, 0, 1, 5); + grid->addWidget(line1, row++, 0, 1, 6); grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:"))), row++, 0); mPIVAuthenticationKey = createKeyWidgets(PIVCard::pivAuthenticationKeyRef()); grid->addWidget(new QLabel(i18n("PIV authentication:")), row, 0); grid->addWidget(mPIVAuthenticationKey.keyGrip, row, 1); - grid->addWidget(mPIVAuthenticationKey.generateButton, row, 2); - grid->addWidget(mPIVAuthenticationKey.writeCertificateButton, row, 3); + grid->addWidget(mPIVAuthenticationKey.keyAlgorithm, row, 2); + grid->addWidget(mPIVAuthenticationKey.generateButton, row, 3); + grid->addWidget(mPIVAuthenticationKey.writeCertificateButton, row, 4); row++; mCardAuthenticationKey = createKeyWidgets(PIVCard::cardAuthenticationKeyRef()); grid->addWidget(new QLabel(i18n("Card authentication:")), row, 0); grid->addWidget(mCardAuthenticationKey.keyGrip, row, 1); - grid->addWidget(mCardAuthenticationKey.generateButton, row, 2); - grid->addWidget(mCardAuthenticationKey.writeCertificateButton, row, 3); + grid->addWidget(mCardAuthenticationKey.keyAlgorithm, row, 2); + grid->addWidget(mCardAuthenticationKey.generateButton, row, 3); + grid->addWidget(mCardAuthenticationKey.writeCertificateButton, row, 4); row++; mDigitalSignatureKey = createKeyWidgets(PIVCard::digitalSignatureKeyRef()); grid->addWidget(new QLabel(i18n("Digital signature:")), row, 0); grid->addWidget(mDigitalSignatureKey.keyGrip, row, 1); - grid->addWidget(mDigitalSignatureKey.generateButton, row, 2); - grid->addWidget(mDigitalSignatureKey.writeCertificateButton, row, 3); + grid->addWidget(mDigitalSignatureKey.keyAlgorithm, row, 2); + grid->addWidget(mDigitalSignatureKey.generateButton, row, 3); + grid->addWidget(mDigitalSignatureKey.writeCertificateButton, row, 4); row++; mKeyManagementKey = createKeyWidgets(PIVCard::keyManagementKeyRef()); grid->addWidget(new QLabel(i18n("Key management:")), row, 0); grid->addWidget(mKeyManagementKey.keyGrip, row, 1); - grid->addWidget(mKeyManagementKey.generateButton, row, 2); - grid->addWidget(mKeyManagementKey.writeCertificateButton, row, 3); - grid->addWidget(mKeyManagementKey.writeKeyButton, row, 4); + grid->addWidget(mKeyManagementKey.keyAlgorithm, row, 2); + grid->addWidget(mKeyManagementKey.generateButton, row, 3); + grid->addWidget(mKeyManagementKey.writeCertificateButton, row, 4); + grid->addWidget(mKeyManagementKey.writeKeyButton, row, 5); row++; auto line2 = new QFrame(); line2->setFrameShape(QFrame::HLine); - grid->addWidget(line2, row++, 0, 1, 5); + grid->addWidget(line2, row++, 0, 1, 6); auto actionLayout = new QHBoxLayout; { auto button = new QPushButton(i18nc("@action:button", "Change PIN")); 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")); 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")); 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); - grid->addLayout(actionLayout, row++, 0, 1, 5); + grid->addLayout(actionLayout, row++, 0, 1, 6); grid->setColumnStretch(4, -1); } PIVCardWidget::KeyWidgets PIVCardWidget::createKeyWidgets(const std::string &keyRef) { KeyWidgets keyWidgets; keyWidgets.keyGrip = new QLabel(this); keyWidgets.keyGrip->setTextInteractionFlags(Qt::TextBrowserInteraction); + keyWidgets.keyAlgorithm = new QLabel(this); keyWidgets.generateButton = new QPushButton(i18nc("@action:button", "Generate"), this); keyWidgets.generateButton->setEnabled(false); connect(keyWidgets.generateButton, &QPushButton::clicked, this, [this, keyRef] () { generateKey(keyRef); }); keyWidgets.writeCertificateButton = new QPushButton(i18nc("@action:button", "Write Certificate")); 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); }); if (keyRef == PIVCard::keyManagementKeyRef()) { keyWidgets.writeKeyButton = new QPushButton(i18nc("@action:button", "Write Key")); 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); }); } return keyWidgets; } PIVCardWidget::~PIVCardWidget() { } void PIVCardWidget::setCard(const PIVCard *card) { mCardSerialNumber = card->serialNumber(); mVersionLabel->setText(i18nc("%1 version number", "PIV v%1 card", formatVersion(card->appVersion()))); if (card->displaySerialNumber() != card->serialNumber()) { mSerialNumber->setText(QStringLiteral("%1 (%2)").arg(QString::fromStdString(card->displaySerialNumber()), QString::fromStdString(card->serialNumber()))); } else { mSerialNumber->setText(QString::fromStdString(card->serialNumber())); } updateKey(PIVCard::pivAuthenticationKeyRef(), card, mPIVAuthenticationKey); updateKey(PIVCard::cardAuthenticationKeyRef(), card, mCardAuthenticationKey); updateKey(PIVCard::digitalSignatureKeyRef(), card, mDigitalSignatureKey); updateKey(PIVCard::keyManagementKeyRef(), card, mKeyManagementKey); } void PIVCardWidget::updateKey(const std::string &keyRef, const PIVCard *card, const KeyWidgets &widgets) { const std::string grip = card->keyGrip(keyRef); - widgets.keyGrip->setText(grip.empty() ? i18nc("@info", "Slot empty") : QString::fromStdString(grip)); - widgets.generateButton->setText(grip.empty() ? i18nc("@action:button", "Generate") : i18nc("@action:button", "Replace")); - widgets.generateButton->setToolTip(grip.empty() ? - i18nc("@info:tooltip %1 display name of a key", "Generate %1", PIVCard::keyDisplayName(keyRef)) : - i18nc("@info:tooltip %1 display name of a key", "Replace %1 with new key", PIVCard::keyDisplayName(keyRef))); + if (grip.empty()) { + widgets.keyGrip->setText(i18nc("@info", "Slot empty")); + 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))); + } else { + widgets.keyGrip->setText(QString::fromStdString(grip)); + const std::string algo = card->keyAlgorithm(keyRef); + widgets.keyAlgorithm->setText(algo.empty() ? i18nc("@info unknown key algorithm", "unknown") : QString::fromStdString(algo)); + 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))); + } widgets.generateButton->setEnabled(true); if (widgets.writeCertificateButton) { widgets.writeCertificateButton->setEnabled(!grip.empty()); } } void PIVCardWidget::generateKey(const std::string &keyref) { auto cmd = new PIVGenerateCardKeyCommand(mCardSerialNumber, this); this->setEnabled(false); connect(cmd, &PIVGenerateCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyref); cmd->start(); } void PIVCardWidget::writeCertificateToCard(const std::string &keyref) { auto cmd = new CertificateToPIVCardCommand(keyref, mCardSerialNumber); this->setEnabled(false); connect(cmd, &CertificateToPIVCardCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::writeKeyToCard(const std::string &keyref) { auto cmd = new KeyToCardCommand(keyref, mCardSerialNumber); this->setEnabled(false); connect(cmd, &KeyToCardCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::changePin(const std::string &keyRef) { auto cmd = new ChangePinCommand(mCardSerialNumber, 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(mCardSerialNumber, this); this->setEnabled(false); connect(cmd, &SetPIVCardApplicationAdministrationKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } diff --git a/src/view/pivcardwidget.h b/src/view/pivcardwidget.h index 35f24e718..dfd03a198 100644 --- a/src/view/pivcardwidget.h +++ b/src/view/pivcardwidget.h @@ -1,63 +1,64 @@ /* view/pivcardwiget.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 VIEW_PIVCARDWIDGET_H #define VIEW_PIVCARDWIDGET_H #include #include class QLabel; class QPushButton; namespace Kleo { namespace SmartCard { class PIVCard; } // namespace SmartCard class PIVCardWidget: public QWidget { Q_OBJECT public: explicit PIVCardWidget(QWidget *parent = nullptr); ~PIVCardWidget(); void setCard(const SmartCard::PIVCard* card); private: struct KeyWidgets { QLabel *keyGrip = nullptr; + QLabel *keyAlgorithm = nullptr; QPushButton *generateButton = nullptr; QPushButton *writeCertificateButton = nullptr; QPushButton *writeKeyButton = nullptr; }; KeyWidgets createKeyWidgets(const std::string &keyRef); void updateKey(const std::string &keyRef, const SmartCard::PIVCard *card, const KeyWidgets &widgets); void generateKey(const std::string &keyref); void writeCertificateToCard(const std::string &keyref); void writeKeyToCard(const std::string &keyref); void changePin(const std::string &keyRef); void setAdminKey(); private: std::string mCardSerialNumber; QLabel *mSerialNumber = nullptr; QLabel *mVersionLabel = nullptr; KeyWidgets mPIVAuthenticationKey; KeyWidgets mCardAuthenticationKey; KeyWidgets mDigitalSignatureKey; KeyWidgets mKeyManagementKey; }; } // namespace Kleo #endif // VIEW_PIVCARDWIDGET_H