Page MenuHome GnuPG

No OneTemporary

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e7d0e1797..c1932db1e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,640 +1,642 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(icons)
add_subdirectory(mimetypes)
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
selftest/registrycheck.cpp selftest/registrycheck.h
utils/gnupg-registry.c
utils/userinfo_win.cpp
utils/windowsprocessdevice.cpp utils/windowsprocessdevice.h
versioninfo.rc kleopatra.w32-manifest
)
else()
set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp)
set(_kleopatra_extra_SRCS)
endif()
set(_kleopatra_uiserver_SRCS
${_kleopatra_extra_uiserver_SRCS}
selftest/uiservercheck.cpp selftest/uiservercheck.h
uiserver/assuanserverconnection.cpp uiserver/assuanserverconnection.h
uiserver/createchecksumscommand.cpp uiserver/createchecksumscommand.h
uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandemailbase.h
uiserver/decryptverifycommandfilesbase.cpp uiserver/decryptverifycommandfilesbase.h
uiserver/echocommand.cpp uiserver/echocommand.h
uiserver/encryptcommand.cpp uiserver/encryptcommand.h
uiserver/importfilescommand.cpp uiserver/importfilescommand.h
uiserver/prepencryptcommand.cpp uiserver/prepencryptcommand.h
uiserver/prepsigncommand.cpp uiserver/prepsigncommand.h
uiserver/selectcertificatecommand.cpp
uiserver/sessiondata.cpp uiserver/sessiondata.h
uiserver/signcommand.cpp uiserver/signcommand.h
uiserver/signencryptfilescommand.cpp
uiserver/uiserver.cpp
uiserver/verifychecksumscommand.cpp uiserver/verifychecksumscommand.h
)
set(_kleopatra_uiserver_extra_libs LibAssuan::LibAssuan LibGpgError::LibGpgError)
if(HAVE_GPG_ERR_SOURCE_KLEO)
add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO)
add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO)
else()
add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1)
add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1)
endif()
if(KPim${KF_MAJOR_VERSION}IdentityManagement_FOUND AND KPim${KF_MAJOR_VERSION}MailTransport_FOUND AND KPim${KF_MAJOR_VERSION}AkonadiMime_FOUND)
set(_kleopatra_mail_libs
KPim${KF_MAJOR_VERSION}::IdentityManagement # Export OpenPGP keys using WKS
KPim${KF_MAJOR_VERSION}::MailTransport
KPim${KF_MAJOR_VERSION}::AkonadiMime
)
add_definitions(-DMAILAKONADI_ENABLED)
endif()
ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui)
set(_kleopatra_SRCS
${_kleopatra_extra_SRCS}
accessibility/accessiblelink.cpp
accessibility/accessiblelink_p.h
accessibility/accessiblerichtextlabel.cpp
accessibility/accessiblerichtextlabel_p.h
accessibility/accessiblevaluelabel.cpp
accessibility/accessiblevaluelabel_p.h
accessibility/accessiblewidgetfactory.cpp
accessibility/accessiblewidgetfactory.h
commands/addsubkeycommand.cpp
commands/addsubkeycommand.h
commands/adduseridcommand.cpp
commands/adduseridcommand.h
commands/authenticatepivcardapplicationcommand.cpp
commands/authenticatepivcardapplicationcommand.h
commands/cardcommand.cpp
commands/cardcommand.h
commands/certificatetopivcardcommand.cpp
commands/certificatetopivcardcommand.h
commands/certifycertificatecommand.cpp
commands/certifycertificatecommand.h
commands/certifygroupcommand.cpp
commands/certifygroupcommand.h
commands/changeexpirycommand.cpp
commands/changeexpirycommand.h
commands/changeownertrustcommand.cpp
commands/changeownertrustcommand.h
commands/changepassphrasecommand.cpp
commands/changepassphrasecommand.h
commands/changepincommand.cpp
commands/changepincommand.h
commands/changeroottrustcommand.cpp
commands/changeroottrustcommand.h
commands/checksumcreatefilescommand.cpp
commands/checksumcreatefilescommand.h
commands/checksumverifyfilescommand.cpp
commands/checksumverifyfilescommand.h
commands/clearcrlcachecommand.cpp
commands/clearcrlcachecommand.h
commands/command.cpp
commands/command.h
commands/createcsrforcardkeycommand.cpp
commands/createcsrforcardkeycommand.h
commands/creategroupcommand.cpp
commands/creategroupcommand.h
commands/createopenpgpkeyfromcardkeyscommand.cpp
commands/createopenpgpkeyfromcardkeyscommand.h
commands/decryptverifyclipboardcommand.cpp
commands/decryptverifyclipboardcommand.h
commands/decryptverifyfilescommand.cpp
commands/decryptverifyfilescommand.h
commands/deletecertificatescommand.cpp
commands/deletecertificatescommand.h
commands/detailscommand.cpp
commands/detailscommand.h
commands/dumpcertificatecommand.cpp
commands/dumpcertificatecommand.h
commands/dumpcrlcachecommand.cpp
commands/dumpcrlcachecommand.h
commands/encryptclipboardcommand.cpp
commands/encryptclipboardcommand.h
commands/exportcertificatecommand.cpp
commands/exportcertificatecommand.h
commands/exportgroupscommand.cpp
commands/exportgroupscommand.h
commands/exportopenpgpcertstoservercommand.cpp
commands/exportopenpgpcertstoservercommand.h
commands/exportopenpgpcerttoprovidercommand.cpp
commands/exportopenpgpcerttoprovidercommand.h
commands/exportpaperkeycommand.cpp
commands/exportpaperkeycommand.h
commands/exportsecretkeycommand.cpp
commands/exportsecretkeycommand.h
commands/exportsecretsubkeycommand.cpp
commands/exportsecretsubkeycommand.h
commands/genrevokecommand.cpp
commands/genrevokecommand.h
commands/gnupgprocesscommand.cpp
commands/gnupgprocesscommand.h
commands/importcertificatefromclipboardcommand.cpp
commands/importcertificatefromclipboardcommand.h
commands/importcertificatefromdatacommand.cpp
commands/importcertificatefromdatacommand.h
commands/importcertificatefromfilecommand.cpp
commands/importcertificatefromfilecommand.h
commands/importcertificatefromkeyservercommand.cpp
commands/importcertificatefromkeyservercommand.h
commands/importcertificatefrompivcardcommand.cpp
commands/importcertificatefrompivcardcommand.h
commands/importcertificatescommand.cpp
commands/importcertificatescommand.h
commands/importcrlcommand.cpp
commands/importcrlcommand.h
commands/importpaperkeycommand.cpp
commands/importpaperkeycommand.h
commands/keytocardcommand.cpp
commands/keytocardcommand.h
commands/lookupcertificatescommand.cpp
commands/lookupcertificatescommand.h
commands/newcertificatesigningrequestcommand.cpp
commands/newcertificatesigningrequestcommand.h
commands/newopenpgpcertificatecommand.cpp
commands/newopenpgpcertificatecommand.h
commands/openpgpgeneratecardkeycommand.cpp
commands/openpgpgeneratecardkeycommand.h
commands/pivgeneratecardkeycommand.cpp
commands/pivgeneratecardkeycommand.h
commands/refreshcertificatecommand.cpp
commands/refreshcertificatecommand.h
commands/refreshopenpgpcertscommand.cpp
commands/refreshopenpgpcertscommand.h
commands/refreshx509certscommand.cpp
commands/refreshx509certscommand.h
commands/reloadkeyscommand.cpp
commands/reloadkeyscommand.h
commands/revokecertificationcommand.cpp
commands/revokecertificationcommand.h
commands/revokekeycommand.cpp
commands/revokekeycommand.h
commands/revokeuseridcommand.cpp
commands/revokeuseridcommand.h
commands/selftestcommand.cpp
commands/selftestcommand.h
commands/setinitialpincommand.cpp
commands/setinitialpincommand.h
commands/setpivcardapplicationadministrationkeycommand.cpp
commands/setpivcardapplicationadministrationkeycommand.h
commands/setprimaryuseridcommand.cpp
commands/setprimaryuseridcommand.h
commands/signclipboardcommand.cpp
commands/signclipboardcommand.h
commands/signencryptfilescommand.cpp
commands/signencryptfilescommand.h
commands/signencryptfoldercommand.cpp
commands/signencryptfoldercommand.h
commands/viewemailfilescommand.cpp
commands/viewemailfilescommand.h
conf/configuredialog.cpp
conf/configuredialog.h
conf/groupsconfigdialog.cpp
conf/groupsconfigdialog.h
conf/groupsconfigwidget.cpp
conf/groupsconfigwidget.h
crypto/autodecryptverifyfilescontroller.cpp
crypto/autodecryptverifyfilescontroller.h
crypto/certificateresolver.cpp
crypto/certificateresolver.h
crypto/checksumsutils_p.cpp
crypto/checksumsutils_p.h
crypto/controller.cpp
crypto/controller.h
crypto/createchecksumscontroller.cpp
crypto/createchecksumscontroller.h
crypto/decryptverifyemailcontroller.cpp
crypto/decryptverifyemailcontroller.h
crypto/decryptverifyfilescontroller.cpp
crypto/decryptverifyfilescontroller.h
crypto/decryptverifytask.cpp
crypto/decryptverifytask.h
crypto/encryptemailcontroller.cpp
crypto/encryptemailcontroller.h
crypto/encryptemailtask.cpp
crypto/encryptemailtask.h
crypto/gui/certificatelineedit.cpp
crypto/gui/certificatelineedit.h
crypto/gui/certificateselectionline.cpp
crypto/gui/certificateselectionline.h
crypto/gui/decryptverifyfilesdialog.cpp
crypto/gui/decryptverifyfilesdialog.h
crypto/gui/decryptverifyfileswizard.cpp
crypto/gui/decryptverifyfileswizard.h
crypto/gui/decryptverifyoperationwidget.cpp
crypto/gui/decryptverifyoperationwidget.h
crypto/gui/encryptemailwizard.cpp
crypto/gui/encryptemailwizard.h
crypto/gui/newresultpage.cpp
crypto/gui/newresultpage.h
crypto/gui/objectspage.cpp
crypto/gui/objectspage.h
crypto/gui/resolverecipientspage.cpp
crypto/gui/resolverecipientspage.h
crypto/gui/resultitemwidget.cpp
crypto/gui/resultitemwidget.h
crypto/gui/resultlistwidget.cpp
crypto/gui/resultlistwidget.h
crypto/gui/resultpage.cpp
crypto/gui/resultpage.h
crypto/gui/signemailwizard.cpp
crypto/gui/signemailwizard.h
crypto/gui/signencryptemailconflictdialog.cpp
crypto/gui/signencryptemailconflictdialog.h
crypto/gui/signencryptfileswizard.cpp
crypto/gui/signencryptfileswizard.h
crypto/gui/signencryptwidget.cpp
crypto/gui/signencryptwidget.h
crypto/gui/signencryptwizard.cpp
crypto/gui/signencryptwizard.h
crypto/gui/signerresolvepage.cpp
crypto/gui/signerresolvepage.h
crypto/gui/signingcertificateselectiondialog.cpp
crypto/gui/signingcertificateselectiondialog.h
crypto/gui/signingcertificateselectionwidget.cpp
crypto/gui/signingcertificateselectionwidget.h
crypto/gui/unknownrecipientwidget.cpp
crypto/gui/unknownrecipientwidget.h
crypto/gui/verifychecksumsdialog.cpp
crypto/gui/verifychecksumsdialog.h
crypto/gui/wizard.cpp
crypto/gui/wizard.h
crypto/gui/wizardpage.cpp
crypto/gui/wizardpage.h
crypto/newsignencryptemailcontroller.cpp
crypto/newsignencryptemailcontroller.h
crypto/recipient.cpp
crypto/recipient.h
crypto/sender.cpp
crypto/sender.h
crypto/signemailcontroller.cpp
crypto/signemailcontroller.h
crypto/signemailtask.cpp
crypto/signemailtask.h
crypto/signencryptfilescontroller.cpp
crypto/signencryptfilescontroller.h
crypto/signencrypttask.cpp
crypto/signencrypttask.h
crypto/task.cpp
crypto/task.h
crypto/taskcollection.cpp
crypto/taskcollection.h
crypto/verifychecksumscontroller.cpp
crypto/verifychecksumscontroller.h
dialogs/addsubkeydialog.cpp
dialogs/addsubkeydialog.h
dialogs/adduseriddialog.cpp
dialogs/adduseriddialog.h
dialogs/animatedexpander.cpp
dialogs/animatedexpander.h
+ dialogs/cardinfotab.cpp
+ dialogs/cardinfotab.h
dialogs/certificatedetailsdialog.cpp
dialogs/certificatedetailsdialog.h
dialogs/certificatedetailsinputwidget.cpp
dialogs/certificatedetailsinputwidget.h
dialogs/certificatedetailswidget.cpp
dialogs/certificatedetailswidget.h
dialogs/certificatedumpwidget.cpp
dialogs/certificatedumpwidget.h
dialogs/certificateselectiondialog.cpp
dialogs/certificateselectiondialog.h
dialogs/certifycertificatedialog.cpp
dialogs/certifycertificatedialog.h
dialogs/certifywidget.cpp
dialogs/certifywidget.h
dialogs/createcsrforcardkeydialog.cpp
dialogs/createcsrforcardkeydialog.h
dialogs/deletecertificatesdialog.cpp
dialogs/deletecertificatesdialog.h
dialogs/editgroupdialog.cpp
dialogs/editgroupdialog.h
dialogs/expirydialog.cpp
dialogs/expirydialog.h
dialogs/exportdialog.cpp
dialogs/exportdialog.h
dialogs/gencardkeydialog.cpp
dialogs/gencardkeydialog.h
dialogs/groupdetailsdialog.cpp
dialogs/groupdetailsdialog.h
dialogs/lookupcertificatesdialog.cpp
dialogs/lookupcertificatesdialog.h
dialogs/nameandemailwidget.cpp
dialogs/nameandemailwidget.h
dialogs/newopenpgpcertificatedetailsdialog.cpp
dialogs/newopenpgpcertificatedetailsdialog.h
dialogs/pivcardapplicationadministrationkeyinputdialog.cpp
dialogs/pivcardapplicationadministrationkeyinputdialog.h
dialogs/revokekeydialog.cpp
dialogs/revokekeydialog.h
dialogs/selftestdialog.cpp
dialogs/selftestdialog.h
dialogs/setinitialpindialog.cpp
dialogs/setinitialpindialog.h
dialogs/subkeyswidget.cpp
dialogs/subkeyswidget.h
dialogs/trustchainwidget.cpp
dialogs/trustchainwidget.h
dialogs/updatenotification.cpp
dialogs/updatenotification.h
dialogs/useridswidget.cpp
dialogs/useridswidget.h
dialogs/weboftrustwidget.cpp
dialogs/weboftrustwidget.h
interfaces/anchorprovider.h
interfaces/focusfirstchild.h
newcertificatewizard/advancedsettingsdialog.cpp
newcertificatewizard/advancedsettingsdialog_p.h
newcertificatewizard/enterdetailspage.cpp
newcertificatewizard/enterdetailspage_p.h
newcertificatewizard/keyalgo.cpp
newcertificatewizard/keyalgo_p.h
newcertificatewizard/keycreationpage.cpp
newcertificatewizard/keycreationpage_p.h
newcertificatewizard/listwidget.cpp
newcertificatewizard/listwidget.h
newcertificatewizard/newcertificatewizard.cpp
newcertificatewizard/newcertificatewizard.h
newcertificatewizard/resultpage.cpp
newcertificatewizard/resultpage_p.h
newcertificatewizard/wizardpage.cpp
newcertificatewizard/wizardpage_p.h
selftest/compliancecheck.cpp
selftest/compliancecheck.h
selftest/enginecheck.cpp
selftest/enginecheck.h
selftest/gpgagentcheck.cpp
selftest/gpgagentcheck.h
selftest/gpgconfcheck.cpp
selftest/gpgconfcheck.h
selftest/libkleopatrarccheck.cpp
selftest/libkleopatrarccheck.h
selftest/selftest.cpp
selftest/selftest.h
smartcard/algorithminfo.h
smartcard/card.cpp
smartcard/card.h
smartcard/deviceinfowatcher.cpp
smartcard/deviceinfowatcher.h
smartcard/keypairinfo.cpp
smartcard/keypairinfo.h
smartcard/netkeycard.cpp
smartcard/netkeycard.h
smartcard/openpgpcard.cpp
smartcard/openpgpcard.h
smartcard/p15card.cpp
smartcard/p15card.h
smartcard/pivcard.cpp
smartcard/pivcard.h
smartcard/readerstatus.cpp
smartcard/readerstatus.h
smartcard/utils.cpp
smartcard/utils.h
utils/accessibility.cpp
utils/accessibility.h
utils/action_data.cpp
utils/action_data.h
utils/applicationstate.cpp
utils/applicationstate.h
utils/archivedefinition.cpp
utils/archivedefinition.h
utils/certificatepair.h
utils/clipboardmenu.cpp
utils/clipboardmenu.h
utils/debug-helpers.cpp
utils/debug-helpers.h
utils/dragqueen.cpp
utils/dragqueen.h
utils/email.cpp
utils/email.h
utils/emptypassphraseprovider.cpp
utils/emptypassphraseprovider.h
utils/expiration.cpp
utils/expiration.h
utils/filedialog.cpp
utils/filedialog.h
utils/gui-helper.cpp
utils/gui-helper.h
utils/headerview.cpp
utils/headerview.h
utils/input.cpp
utils/input.h
utils/iodevicelogger.cpp
utils/iodevicelogger.h
utils/kdpipeiodevice.cpp
utils/kdpipeiodevice.h
utils/keyexportdraghandler.cpp
utils/keyexportdraghandler.h
utils/keyparameters.cpp
utils/keyparameters.h
utils/kuniqueservice.cpp
utils/kuniqueservice.h
utils/log.cpp
utils/log.h
utils/memory-helpers.h
utils/migration.cpp
utils/migration.h
utils/multivalidator.cpp
utils/multivalidator.h
utils/output.cpp
utils/output.h
utils/overwritedialog.cpp
utils/overwritedialog.h
utils/path-helper.cpp
utils/path-helper.h
utils/scrollarea.cpp
utils/scrollarea.h
utils/systemtrayicon.cpp
utils/systemtrayicon.h
utils/tags.cpp
utils/tags.h
utils/types.cpp
utils/types.h
utils/userinfo.cpp
utils/userinfo.h
utils/validation.cpp
utils/validation.h
utils/writecertassuantransaction.cpp
utils/writecertassuantransaction.h
utils/wsastarter.cpp
utils/wsastarter.h
view/anchorcache.cpp
view/anchorcache_p.h
view/errorlabel.cpp
view/errorlabel.h
view/formtextinput.cpp
view/formtextinput.h
view/htmllabel.cpp
view/htmllabel.h
view/infofield.cpp
view/infofield.h
view/keycacheoverlay.cpp
view/keycacheoverlay.h
view/keylistcontroller.cpp
view/keylistcontroller.h
view/keytreeview.cpp
view/keytreeview.h
view/netkeywidget.cpp
view/netkeywidget.h
view/nullpinwidget.cpp
view/nullpinwidget.h
view/openpgpkeycardwidget.cpp
view/openpgpkeycardwidget.h
view/overlaywidget.cpp
view/overlaywidget.h
view/p15cardwidget.cpp
view/p15cardwidget.h
view/padwidget.cpp
view/padwidget.h
view/pgpcardwidget.cpp
view/pgpcardwidget.h
view/pivcardwidget.cpp
view/pivcardwidget.h
view/progressoverlay.cpp
view/progressoverlay.h
view/searchbar.cpp
view/searchbar.h
view/smartcardwidget.cpp
view/smartcardwidget.h
view/tabwidget.cpp
view/tabwidget.h
view/textoverlay.cpp
view/textoverlay.h
view/urllabel.cpp
view/urllabel.h
view/waitwidget.cpp
view/waitwidget.h
view/welcomewidget.cpp
view/welcomewidget.h
aboutdata.cpp
aboutdata.h
kleopatra.qrc
kleopatraapplication.cpp
kleopatraapplication.h
main.cpp
mainwindow.cpp
mainwindow.h
systrayicon.cpp
systrayicon.h
)
if(WIN32)
configure_file (versioninfo.rc.in versioninfo.rc)
set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS})
configure_file (kleopatra.w32-manifest.in kleopatra.w32-manifest)
set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/kleopatra.w32-manifest ${_kleopatra_SRCS})
endif()
set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp conf/kleopageconfigdialog.h ${_kleopatra_SRCS})
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/setinitialpindialog.ui
dialogs/trustchainwidget.ui
newcertificatewizard/listwidget.ui
)
kconfig_add_kcfg_files(_kleopatra_SRCS
kcfg/emailoperationspreferences.kcfgc
kcfg/fileoperationspreferences.kcfgc
kcfg/settings.kcfgc
kcfg/smimevalidationpreferences.kcfgc
kcfg/tagspreferences.kcfgc
kcfg/tooltippreferences.kcfgc
)
file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png")
ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS})
add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS})
# For the ConfigureDialog & KCMs
target_link_libraries(kleopatra_bin kcm_kleopatra_static)
#if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
# set_target_properties(kleopatra_bin PROPERTIES UNITY_BUILD ON)
#endif()
set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra)
if (WIN32)
set(_kleopatra_platform_libs "secur32")
endif ()
target_link_libraries(kleopatra_bin
Gpgmepp
KPim${KF_MAJOR_VERSION}::Libkleo
KPim${KF_MAJOR_VERSION}::Mime
KPim${KF_MAJOR_VERSION}::MimeTreeParserWidgets
KF${KF_MAJOR_VERSION}::Codecs
KF${KF_MAJOR_VERSION}::CoreAddons
KF${KF_MAJOR_VERSION}::Crash
KF${KF_MAJOR_VERSION}::I18n
KF${KF_MAJOR_VERSION}::IconThemes
KF${KF_MAJOR_VERSION}::ItemModels
KF${KF_MAJOR_VERSION}::KIOCore
KF${KF_MAJOR_VERSION}::KIOWidgets
KF${KF_MAJOR_VERSION}::WindowSystem
KF${KF_MAJOR_VERSION}::XmlGui
Qt::Network
Qt::PrintSupport # Printing secret keys
kleopatraclientcore
${_kleopatra_extra_libs}
${_kleopatra_mail_libs}
${_kleopatra_uiserver_extra_libs}
${_kleopatra_dbusaddons_libs}
${_kleopatra_platform_libs}
)
if (QT_MAJOR_VERSION STREQUAL "6")
target_link_libraries(kleopatra_bin Qt6::Core5Compat)
target_link_libraries(kleopatra_bin QGpgmeQt6)
else()
target_link_libraries(kleopatra_bin QGpgme)
endif()
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-mime.xml DESTINATION ${KDE_INSTALL_MIMEDIR})
install(
PROGRAMS data/kleopatra_signencryptfiles.desktop
data/kleopatra_signencryptfolders.desktop
data/kleopatra_decryptverifyfiles.desktop
data/kleopatra_decryptverifyfolders.desktop
DESTINATION ${KDE_INSTALL_DATADIR}/kio/servicemenus
)
diff --git a/src/dialogs/cardinfotab.cpp b/src/dialogs/cardinfotab.cpp
new file mode 100644
index 000000000..49070cea8
--- /dev/null
+++ b/src/dialogs/cardinfotab.cpp
@@ -0,0 +1,181 @@
+// SPDX-FileCopyrightText: 2024 g10 Code GmbH
+// SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "cardinfotab.h"
+
+#include "smartcard/card.h"
+#include "smartcard/readerstatus.h"
+#include "view/smartcardwidget.h"
+
+#include <Libkleo/Formatting>
+#include <Libkleo/KeyCache>
+#include <Libkleo/KeyHelpers>
+#include <Libkleo/TreeWidget>
+#include <Libkleo/UserIDListModel>
+
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KSeparator>
+
+#include <QDialogButtonBox>
+#include <QHeaderView>
+#include <QLabel>
+#include <QMenu>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+#include <QGpgME/Protocol>
+
+#include <gpgme++/key.h>
+
+#include "kleopatra_debug.h"
+
+using namespace Kleo;
+using namespace SmartCard;
+
+class CardInfoTab::Private
+{
+ friend class ::Kleo::CardInfoTab;
+ CardInfoTab *const q;
+
+ void loadData();
+
+private:
+ GpgME::Key key;
+ TreeWidget *subkeysTree = nullptr;
+ QLabel *placeholderLabel = nullptr;
+ QPushButton *reloadButton = nullptr;
+
+public:
+ Private(CardInfoTab *qq)
+ : q{qq}
+ {
+ auto vLay = new QVBoxLayout(q);
+ vLay->setContentsMargins({});
+ vLay->setSpacing(0);
+
+ subkeysTree = new TreeWidget{q};
+ subkeysTree->setAccessibleName(i18n("Subkeys"));
+ subkeysTree->setAllColumnsShowFocus(false);
+ subkeysTree->setSelectionMode(QAbstractItemView::SingleSelection);
+ subkeysTree->setRootIsDecorated(false);
+
+ subkeysTree->setHeaderLabels({
+ i18nc("@title:column", "Keygrip"),
+ i18nc("@title:column", "Token"),
+ i18nc("@title:column", "Type"),
+ i18nc("@title:column", "Serial Number"),
+ i18nc("@title:column", "Owner"),
+ });
+
+ vLay->addWidget(subkeysTree);
+
+ placeholderLabel = new QLabel(i18nc("@info", "Smartcard information is only available for your own certificates."));
+ placeholderLabel->setVisible(false);
+ placeholderLabel->setAlignment(Qt::AlignHCenter);
+ vLay->addWidget(placeholderLabel);
+
+ auto separator = new KSeparator(q);
+ vLay->addWidget(separator);
+
+ auto bbox = new QHBoxLayout;
+
+ reloadButton = new QPushButton(i18nc("@action:button", "Reload"));
+ bbox->addWidget(reloadButton);
+
+ bbox->addStretch(1);
+ vLay->addLayout(bbox);
+ }
+};
+
+CardInfoTab::CardInfoTab(QWidget *parent)
+ : QWidget{parent}
+ , d{std::make_unique<Private>(this)}
+{
+ connect(d->reloadButton, &QPushButton::clicked, this, []() {
+ ReaderStatus::mutableInstance()->updateStatus();
+ });
+ connect(ReaderStatus::instance(), &ReaderStatus::cardAdded, this, [this]() {
+ d->loadData();
+ });
+ connect(ReaderStatus::instance(), &ReaderStatus::cardChanged, this, [this]() {
+ d->loadData();
+ });
+ connect(ReaderStatus::instance(), &ReaderStatus::cardRemoved, this, [this]() {
+ d->loadData();
+ });
+}
+
+CardInfoTab::~CardInfoTab() = default;
+
+GpgME::Key CardInfoTab::key() const
+{
+ return d->key;
+}
+
+void CardInfoTab::Private::loadData()
+{
+ subkeysTree->clear();
+ for (const auto &subkey : key.subkeys()) {
+ const auto &cards = KeyCache::instance()->cardsForSubkey(subkey);
+
+ for (const auto &info : cards) {
+ auto availableCard = SmartCard::ReaderStatus::instance()->getCardWithKeyRef(info.serialNumber.toStdString(), info.keyRef.toStdString());
+ auto item = new QTreeWidgetItem;
+ item->setData(0, Qt::DisplayRole, QString::fromLatin1(subkey.keyGrip()));
+ item->setData(1, Qt::DisplayRole, info.serialNumber);
+ if (availableCard) {
+ const QString manufacturer = QString::fromStdString(availableCard->manufacturer());
+ const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown");
+ item->setData(
+ 2,
+ Qt::DisplayRole,
+ manufacturerIsUnknown
+ ? i18nc("Unknown <type> <version> (card)", "Unknown %1 v%2", availableCard->displayAppName(), availableCard->displayAppVersion())
+ : i18nc("<Manufacturer> <type> <version>",
+ "%1 %2 v%3",
+ manufacturer,
+ availableCard->displayAppName(),
+ availableCard->displayAppVersion()));
+ item->setData(3, Qt::DisplayRole, availableCard->displaySerialNumber());
+ item->setData(4,
+ Qt::DisplayRole,
+ availableCard->cardHolder().size() > 0 ? availableCard->cardHolder() : i18nc("unknown cardholder", "unknown"));
+ item->setData(5, Qt::UserRole, QString::fromStdString(availableCard->appName()));
+ } else {
+ item->setData(2, Qt::DisplayRole, i18n("n/a"));
+ if (!info.displaySerialNumber.isEmpty()) {
+ item->setData(3, Qt::DisplayRole, info.displaySerialNumber);
+ } else {
+ item->setData(3, Qt::DisplayRole, i18n("n/a"));
+ }
+ item->setData(4, Qt::DisplayRole, i18n("n/a"));
+ }
+ subkeysTree->addTopLevelItem(item);
+ }
+ }
+ for (int i = 0; i < subkeysTree->columnCount(); i++) {
+ subkeysTree->resizeColumnToContents(i);
+ }
+}
+
+void CardInfoTab::setKey(const GpgME::Key &key)
+{
+ if (!key.hasSecret()) {
+ d->subkeysTree->setVisible(false);
+ d->placeholderLabel->setVisible(true);
+ d->reloadButton->setEnabled(false);
+ return;
+ }
+
+ d->key = key;
+ d->subkeysTree->header()->resizeSections(QHeaderView::ResizeToContents);
+ d->subkeysTree->restoreColumnLayout(QStringLiteral("CardInfoTab"));
+ d->loadData();
+ for (int i = 0; i < d->subkeysTree->columnCount(); i++) {
+ d->subkeysTree->resizeColumnToContents(i);
+ }
+}
+
+#include "moc_cardinfotab.cpp"
diff --git a/src/dialogs/cardinfotab.h b/src/dialogs/cardinfotab.h
new file mode 100644
index 000000000..a46eeb983
--- /dev/null
+++ b/src/dialogs/cardinfotab.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: 2024 g10 Code GmbH
+// SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QWidget>
+
+#include <memory>
+
+namespace GpgME
+{
+class Key;
+class KeyListResult;
+}
+
+namespace Kleo
+{
+
+class CardInfoTab : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit CardInfoTab(QWidget *parent = nullptr);
+ ~CardInfoTab() override;
+
+ void setKey(const GpgME::Key &key);
+ GpgME::Key key() const;
+
+private:
+ class Private;
+ const std::unique_ptr<Private> d;
+};
+} // namespace Kleo
diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp
index 0b15ded17..3feb68b34 100644
--- a/src/dialogs/certificatedetailswidget.cpp
+++ b/src/dialogs/certificatedetailswidget.cpp
@@ -1,666 +1,672 @@
/*
dialogs/certificatedetailswidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-FileCopyrightText: 2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certificatedetailswidget.h"
+#include "cardinfotab.h"
#include "certificatedumpwidget.h"
#include "dialogs/weboftrustwidget.h"
#include "kleopatra_debug.h"
#include "subkeyswidget.h"
#include "trustchainwidget.h"
#include "useridswidget.h"
#include "commands/changeexpirycommand.h"
#include "commands/detailscommand.h"
#include "utils/accessibility.h"
#include "utils/tags.h"
#include "view/infofield.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Compliance>
#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/TreeWidget>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <QGpgME/Debug>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QClipboard>
#include <QDateTime>
#include <QGridLayout>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QLocale>
#include <QMenu>
#include <QPushButton>
#include <QStringBuilder>
#include <QTreeWidget>
#include <QVBoxLayout>
#include <map>
#if __has_include(<ranges>)
#include <ranges>
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
#define USE_RANGES
#endif
#endif
#include <set>
Q_DECLARE_METATYPE(GpgME::UserID)
using namespace Kleo;
class CertificateDetailsWidget::Private
{
public:
Private(CertificateDetailsWidget *qq);
void setupCommonProperties();
void setUpSMIMEAdressList();
void setupPGPProperties();
void setupSMIMEProperties();
void refreshCertificate();
void changeExpiration();
void keysMayHaveChanged();
QIcon trustLevelIcon(const GpgME::UserID &uid) const;
QString trustLevelText(const GpgME::UserID &uid) const;
void showIssuerCertificate();
void updateKey();
void setUpdatedKey(const GpgME::Key &key);
void keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &, const QString &, const GpgME::Error &);
void copyFingerprintToClipboard();
void setUpdateInProgress(bool updateInProgress);
void setTabVisible(QWidget *tab, bool visible);
private:
CertificateDetailsWidget *const q;
public:
GpgME::Key key;
bool updateInProgress = false;
private:
InfoField *attributeField(const QString &attributeName)
{
const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName);
if (keyValuePairIt != ui.smimeAttributeFields.end()) {
return (*keyValuePairIt).second.get();
}
return nullptr;
}
private:
struct UI {
UserIdsWidget *userIDs = nullptr;
std::map<QString, std::unique_ptr<InfoField>> smimeAttributeFields;
std::unique_ptr<InfoField> smimeTrustLevelField;
std::unique_ptr<InfoField> validFromField;
std::unique_ptr<InfoField> expiresField;
QAction *changeExpirationAction = nullptr;
std::unique_ptr<InfoField> fingerprintField;
QAction *copyFingerprintAction = nullptr;
std::unique_ptr<InfoField> smimeIssuerField;
QAction *showIssuerCertificateAction = nullptr;
std::unique_ptr<InfoField> complianceField;
std::unique_ptr<InfoField> trustedIntroducerField;
std::unique_ptr<InfoField> primaryUserIdField;
std::unique_ptr<InfoField> privateKeyInfoField;
QListWidget *smimeAddressList = nullptr;
QTabWidget *tabWidget = nullptr;
SubKeysWidget *subKeysWidget = nullptr;
WebOfTrustWidget *webOfTrustWidget = nullptr;
TrustChainWidget *trustChainWidget = nullptr;
CertificateDumpWidget *certificateDumpWidget = nullptr;
+ CardInfoTab *cardInfoTab = nullptr;
void setupUi(QWidget *parent)
{
auto mainLayout = new QVBoxLayout{parent};
{
auto gridLayout = new QGridLayout;
gridLayout->setColumnStretch(1, 1);
int row = -1;
row++;
primaryUserIdField = std::make_unique<InfoField>(i18n("User ID:"), parent);
gridLayout->addWidget(primaryUserIdField->label(), row, 0);
gridLayout->addLayout(primaryUserIdField->layout(), row, 1);
for (const auto &attribute : DN::attributeOrder()) {
const auto attributeLabel = DN::attributeNameToLabel(attribute);
if (attributeLabel.isEmpty()) {
continue;
}
const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel);
const auto &[it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique<InfoField>(labelWithColon, parent));
if (inserted) {
row++;
const auto &field = it->second;
gridLayout->addWidget(field->label(), row, 0);
gridLayout->addLayout(field->layout(), row, 1);
}
}
row++;
smimeTrustLevelField = std::make_unique<InfoField>(i18n("Trust level:"), parent);
gridLayout->addWidget(smimeTrustLevelField->label(), row, 0);
gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1);
row++;
validFromField = std::make_unique<InfoField>(i18n("Valid from:"), parent);
gridLayout->addWidget(validFromField->label(), row, 0);
gridLayout->addLayout(validFromField->layout(), row, 1);
row++;
expiresField = std::make_unique<InfoField>(i18n("Valid until:"), parent);
changeExpirationAction = new QAction{parent};
changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the end of the validity period"));
Kleo::setAccessibleName(changeExpirationAction, i18nc("@action:button", "Change Validity"));
expiresField->setAction(changeExpirationAction);
gridLayout->addWidget(expiresField->label(), row, 0);
gridLayout->addLayout(expiresField->layout(), row, 1);
row++;
fingerprintField = std::make_unique<InfoField>(i18n("Fingerprint:"), parent);
if (QGuiApplication::clipboard()) {
copyFingerprintAction = new QAction{parent};
copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard"));
Kleo::setAccessibleName(copyFingerprintAction, i18nc("@action:button", "Copy fingerprint"));
fingerprintField->setAction(copyFingerprintAction);
}
gridLayout->addWidget(fingerprintField->label(), row, 0);
gridLayout->addLayout(fingerprintField->layout(), row, 1);
row++;
smimeIssuerField = std::make_unique<InfoField>(i18n("Issuer:"), parent);
showIssuerCertificateAction = new QAction{parent};
showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate"));
Kleo::setAccessibleName(showIssuerCertificateAction, i18nc("@action:button", "Show certificate"));
smimeIssuerField->setAction(showIssuerCertificateAction);
gridLayout->addWidget(smimeIssuerField->label(), row, 0);
gridLayout->addLayout(smimeIssuerField->layout(), row, 1);
row++;
complianceField = std::make_unique<InfoField>(i18n("Compliance:"), parent);
gridLayout->addWidget(complianceField->label(), row, 0);
gridLayout->addLayout(complianceField->layout(), row, 1);
row++;
trustedIntroducerField = std::make_unique<InfoField>(i18n("Trusted introducer for:"), parent);
gridLayout->addWidget(trustedIntroducerField->label(), row, 0);
trustedIntroducerField->setToolTip(i18n("See certifications for details."));
gridLayout->addLayout(trustedIntroducerField->layout(), row, 1);
row++;
privateKeyInfoField = std::make_unique<InfoField>(i18n("Private Key:"), parent);
gridLayout->addWidget(privateKeyInfoField->label(), row, 0);
gridLayout->addLayout(privateKeyInfoField->layout(), row, 1);
mainLayout->addLayout(gridLayout);
}
tabWidget = new QTabWidget(parent);
mainLayout->addWidget(tabWidget);
userIDs = new UserIdsWidget(parent);
tabWidget->addTab(userIDs, i18nc("@title:tab", "User IDs"));
smimeAddressList = new QListWidget{parent};
smimeAddressList->setAccessibleName(i18n("Related addresses"));
smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers);
smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection);
tabWidget->addTab(smimeAddressList, i18nc("@title:tab", "Related Addresses"));
subKeysWidget = new SubKeysWidget(parent);
tabWidget->addTab(subKeysWidget, i18nc("@title:tab", "Subkeys"));
webOfTrustWidget = new WebOfTrustWidget(parent);
tabWidget->addTab(webOfTrustWidget, i18nc("@title:tab", "Certifications"));
trustChainWidget = new TrustChainWidget(parent);
tabWidget->addTab(trustChainWidget, i18nc("@title:tab", "Trust Chain Details"));
+ cardInfoTab = new CardInfoTab(parent);
+ tabWidget->addTab(cardInfoTab, i18nc("@title:tab", "Smartcard"));
+
certificateDumpWidget = new CertificateDumpWidget(parent);
tabWidget->addTab(certificateDumpWidget, i18nc("@title:tab", "Certificate Dump"));
}
} ui;
};
CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq)
: q{qq}
{
ui.setupUi(q);
connect(ui.changeExpirationAction, &QAction::triggered, q, [this]() {
changeExpiration();
});
connect(ui.showIssuerCertificateAction, &QAction::triggered, q, [this]() {
showIssuerCertificate();
});
if (ui.copyFingerprintAction) {
connect(ui.copyFingerprintAction, &QAction::triggered, q, [this]() {
copyFingerprintToClipboard();
});
}
connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() {
keysMayHaveChanged();
});
connect(ui.userIDs, &UserIdsWidget::updateKey, q, [this]() {
updateKey();
});
}
void CertificateDetailsWidget::Private::setupCommonProperties()
{
const bool isOpenPGP = key.protocol() == GpgME::OpenPGP;
const bool isSMIME = key.protocol() == GpgME::CMS;
const bool isOwnKey = key.hasSecret();
for (const auto &[_, field] : ui.smimeAttributeFields) {
field->setVisible(isSMIME);
}
ui.smimeTrustLevelField->setVisible(isSMIME);
// ui.validFromField->setVisible(true); // always visible
// ui.expiresField->setVisible(true); // always visible
if (isOpenPGP && isOwnKey) {
ui.expiresField->setAction(ui.changeExpirationAction);
} else {
ui.expiresField->setAction(nullptr);
}
// ui.fingerprintField->setVisible(true); // always visible
ui.smimeIssuerField->setVisible(isSMIME);
ui.complianceField->setVisible(DeVSCompliance::isCompliant());
ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties()
// update availability of buttons
ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key));
// update values of protocol-independent UI elements
ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key));
ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Valid until:", "unlimited")), Formatting::accessibleExpirationDate(key));
ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()), Formatting::accessibleHexID(key.primaryFingerprint()));
QString storage;
const auto &subkey = key.subkey(0);
if (!key.hasSecret()) {
storage = i18nc("not applicable", "n/a");
} else if (key.subkey(0).isCardKey()) {
if (const char *serialNo = subkey.cardSerialNumber()) {
storage = i18nc("As in 'this secret key is stored on smart card <serial number>'", "smart card %1", QString::fromUtf8(serialNo));
} else {
storage = i18nc("As in 'this secret key is stored on a smart card'", "smart card");
}
} else if (!subkey.isSecret()) {
storage = i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline");
} else {
storage = i18nc("As in 'this secret key is stored on this computer'", "on this computer");
}
ui.privateKeyInfoField->setValue(storage);
if (DeVSCompliance::isCompliant()) {
ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key));
}
+ ui.cardInfoTab->setKey(key);
}
void CertificateDetailsWidget::Private::setUpSMIMEAdressList()
{
ui.smimeAddressList->clear();
const auto *const emailField = attributeField(QStringLiteral("EMAIL"));
// add email address from primary user ID if not listed already as attribute field
if (!emailField) {
const auto ownerId = key.userID(0);
const Kleo::DN dn(ownerId.id());
const QString dnEmail = dn[QStringLiteral("EMAIL")];
if (!dnEmail.isEmpty()) {
ui.smimeAddressList->addItem(dnEmail);
}
}
if (key.numUserIDs() > 1) {
// iterate over the secondary user IDs
#ifdef USE_RANGES
for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) {
#else
const auto uids = key.userIDs();
for (auto it = std::next(uids.begin()); it != uids.end(); ++it) {
const auto &uid = *it;
#endif
const auto name = Kleo::Formatting::prettyName(uid);
const auto email = Kleo::Formatting::prettyEMail(uid);
QString itemText;
if (name.isEmpty() && !email.isEmpty()) {
// skip email addresses already listed in email attribute field
if (emailField && email == emailField->value()) {
continue;
}
itemText = email;
} else {
// S/MIME certificates sometimes contain urls where both
// name and mail is empty. In that case we print whatever
// the uid is as name.
//
// Can be ugly like (3:uri24:http://ca.intevation.org), but
// this is better then showing an empty entry.
itemText = QString::fromUtf8(uid.id());
}
// avoid duplicate entries in the list
if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) {
ui.smimeAddressList->addItem(itemText);
}
}
}
if (ui.smimeAddressList->count() == 0) {
ui.tabWidget->setTabVisible(1, false);
}
}
void CertificateDetailsWidget::Private::changeExpiration()
{
auto cmd = new Kleo::Commands::ChangeExpiryCommand(key);
QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() {
ui.changeExpirationAction->setEnabled(true);
});
ui.changeExpirationAction->setEnabled(false);
cmd->start();
}
namespace
{
void ensureThatKeyDetailsAreLoaded(GpgME::Key &key)
{
if (key.userID(0).numSignatures() == 0) {
key.update();
}
}
}
void CertificateDetailsWidget::Private::keysMayHaveChanged()
{
auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint());
if (!newKey.isNull()) {
ensureThatKeyDetailsAreLoaded(newKey);
setUpdatedKey(newKey);
}
}
QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const
{
if (updateInProgress) {
return QIcon::fromTheme(QStringLiteral("emblem-question"));
}
switch (uid.validity()) {
case GpgME::UserID::Unknown:
case GpgME::UserID::Undefined:
return QIcon::fromTheme(QStringLiteral("emblem-question"));
case GpgME::UserID::Never:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case GpgME::UserID::Marginal:
return QIcon::fromTheme(QStringLiteral("emblem-warning"));
case GpgME::UserID::Full:
case GpgME::UserID::Ultimate:
return QIcon::fromTheme(QStringLiteral("emblem-success"));
}
return {};
}
QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const
{
return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid);
}
namespace
{
auto isGood(const GpgME::UserID::Signature &signature)
{
return signature.status() == GpgME::UserID::Signature::NoError //
&& !signature.isInvalid() //
&& 0x10 <= signature.certClass() && signature.certClass() <= 0x13;
}
auto accumulateTrustDomains(const std::vector<GpgME::UserID::Signature> &signatures)
{
return std::accumulate(std::begin(signatures), std::end(signatures), std::set<QString>(), [](auto domains, const auto &signature) {
if (isGood(signature) && signature.isTrustSignature()) {
domains.insert(Formatting::trustSignatureDomain(signature));
}
return domains;
});
}
auto accumulateTrustDomains(const std::vector<GpgME::UserID> &userIds)
{
return std::accumulate(std::begin(userIds), std::end(userIds), std::set<QString>(), [](auto domains, const auto &userID) {
const auto newDomains = accumulateTrustDomains(userID.signatures());
std::copy(std::begin(newDomains), std::end(newDomains), std::inserter(domains, std::end(domains)));
return domains;
});
}
}
void CertificateDetailsWidget::Private::setTabVisible(QWidget *tab, bool visible)
{
ui.tabWidget->setTabVisible(ui.tabWidget->indexOf(tab), visible);
}
void CertificateDetailsWidget::Private::setupPGPProperties()
{
setTabVisible(ui.userIDs, true);
setTabVisible(ui.smimeAddressList, false);
setTabVisible(ui.subKeysWidget, true);
setTabVisible(ui.webOfTrustWidget, true);
setTabVisible(ui.trustChainWidget, false);
setTabVisible(ui.certificateDumpWidget, false);
ui.userIDs->setKey(key);
ui.subKeysWidget->setKey(key);
ui.webOfTrustWidget->setKey(key);
const auto trustDomains = accumulateTrustDomains(key.userIDs());
ui.trustedIntroducerField->setVisible(!trustDomains.empty());
ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", "));
ui.primaryUserIdField->setValue(Formatting::prettyUserID(key.userID(0)));
ui.primaryUserIdField->setVisible(true);
}
static QString formatDNToolTip(const Kleo::DN &dn)
{
QString html = QStringLiteral("<table border=\"0\" cell-spacing=15>");
const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) {
const QString val = dn[attr];
if (!val.isEmpty()) {
html += QStringLiteral(
"<tr><th style=\"text-align: left; white-space: nowrap\">%1:</th>"
"<td style=\"white-space: nowrap\">%2</td>"
"</tr>")
.arg(lbl, val);
}
};
appendRow(i18n("Common Name"), QStringLiteral("CN"));
appendRow(i18n("Organization"), QStringLiteral("O"));
appendRow(i18n("Street"), QStringLiteral("STREET"));
appendRow(i18n("City"), QStringLiteral("L"));
appendRow(i18n("State"), QStringLiteral("ST"));
appendRow(i18n("Country"), QStringLiteral("C"));
html += QStringLiteral("</table>");
return html;
}
void CertificateDetailsWidget::Private::setupSMIMEProperties()
{
setTabVisible(ui.userIDs, false);
setTabVisible(ui.smimeAddressList, true);
setTabVisible(ui.subKeysWidget, false);
setTabVisible(ui.webOfTrustWidget, false);
setTabVisible(ui.trustChainWidget, true);
setTabVisible(ui.certificateDumpWidget, true);
ui.trustChainWidget->setKey(key);
const auto ownerId = key.userID(0);
const Kleo::DN dn(ownerId.id());
for (const auto &[attributeName, field] : ui.smimeAttributeFields) {
const QString attributeValue = dn[attributeName];
field->setValue(attributeValue);
field->setVisible(!attributeValue.isEmpty());
}
ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId));
ui.smimeTrustLevelField->setValue(trustLevelText(ownerId));
const Kleo::DN issuerDN(key.issuerName());
const QString issuerCN = issuerDN[QStringLiteral("CN")];
const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN;
ui.smimeIssuerField->setValue(issuer);
ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN));
ui.showIssuerCertificateAction->setEnabled(!key.isRoot());
ui.primaryUserIdField->setVisible(false);
ui.certificateDumpWidget->setKey(key);
setUpSMIMEAdressList();
}
void CertificateDetailsWidget::Private::showIssuerCertificate()
{
// there is either one or no parent key
const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
if (parentKeys.empty()) {
KMessageBox::error(q, i18n("The issuer certificate could not be found locally."));
return;
}
auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front());
cmd->setParentWidget(q);
cmd->start();
}
void CertificateDetailsWidget::Private::copyFingerprintToClipboard()
{
if (auto clipboard = QGuiApplication::clipboard()) {
clipboard->setText(QString::fromLatin1(key.primaryFingerprint()));
}
}
CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
CertificateDetailsWidget::~CertificateDetailsWidget() = default;
void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &keys, const QString &, const GpgME::Error &)
{
setUpdateInProgress(false);
if (keys.size() != 1) {
qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update.";
return;
}
// As we listen for keysmayhavechanged we get the update
// after updating the keycache.
KeyCache::mutableInstance()->insert(keys);
}
void CertificateDetailsWidget::Private::updateKey()
{
key.update();
setUpdatedKey(key);
}
void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k)
{
key = k;
setupCommonProperties();
if (key.protocol() == GpgME::OpenPGP) {
setupPGPProperties();
} else {
setupSMIMEProperties();
}
}
void CertificateDetailsWidget::setKey(const GpgME::Key &key)
{
if (key.protocol() == GpgME::CMS) {
// For everything but S/MIME this should be quick
// and we don't need to show another status.
d->setUpdateInProgress(true);
}
d->setUpdatedKey(key);
// Run a keylistjob with full details (TOFU / Validate)
QGpgME::KeyListJob *job =
key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) : QGpgME::smime()->keyListJob(false, true, true);
auto ctx = QGpgME::Job::context(job);
ctx->addKeyListMode(GpgME::WithTofu);
ctx->addKeyListMode(GpgME::SignatureNotations);
if (key.hasSecret()) {
ctx->addKeyListMode(GpgME::WithSecret);
}
// Windows QGpgME new style connect problem makes this necessary.
connect(job,
SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)),
this,
SLOT(keyListDone(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)));
job->start(QStringList() << QLatin1String(key.primaryFingerprint()));
}
GpgME::Key CertificateDetailsWidget::key() const
{
return d->key;
}
void CertificateDetailsWidget::Private::setUpdateInProgress(bool updateInProgress)
{
this->updateInProgress = updateInProgress;
ui.userIDs->setUpdateInProgress(updateInProgress);
}
#include "moc_certificatedetailswidget.cpp"
diff --git a/src/smartcard/card.cpp b/src/smartcard/card.cpp
index f0eaf6aaf..5dd9d938a 100644
--- a/src/smartcard/card.cpp
+++ b/src/smartcard/card.cpp
@@ -1,346 +1,356 @@
/* smartcard/card.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-License-Identifier: GPL-2.0-or-later
*/
#include "card.h"
#include "readerstatus.h"
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
namespace
{
static QString formatVersion(int value)
{
if (value < 0) {
return QString();
}
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);
}
}
Card::Card()
{
}
Card::~Card()
{
}
void Card::setStatus(Status s)
{
mStatus = s;
}
Card::Status Card::status() const
{
return mStatus;
}
void Card::setSerialNumber(const std::string &sn)
{
mSerialNumber = sn;
}
std::string Card::serialNumber() const
{
return mSerialNumber;
}
QString Card::displaySerialNumber() const
{
return mDisplaySerialNumber;
}
void Card::setDisplaySerialNumber(const QString &serialNumber)
{
mDisplaySerialNumber = serialNumber;
}
std::string Card::appName() const
{
return mAppName;
}
void Card::setAppName(const std::string &name)
{
mAppName = name;
}
void Card::setAppVersion(int version)
{
mAppVersion = version;
}
int Card::appVersion() const
{
return mAppVersion;
}
QString Card::displayAppVersion() const
{
return formatVersion(mAppVersion);
}
void Card::setManufacturer(const std::string &value)
{
if (!manufacturer().empty()) {
qCDebug(KLEOPATRA_LOG) << "Card manufacturer is already set; overwriting existing value";
mCardInfo.erase("MANUFACTURER");
}
mCardInfo.insert({"MANUFACTURER", value});
}
std::string Card::manufacturer() const
{
return cardInfo("MANUFACTURER");
}
std::string Card::cardType() const
{
return mCardType;
}
int Card::cardVersion() const
{
return mCardVersion;
}
QString Card::displayCardVersion() const
{
return formatVersion(mCardVersion);
}
QString Card::cardHolder() const
{
return mCardHolder;
}
void Card::setSigningKeyRef(const std::string &keyRef)
{
mSigningKeyRef = keyRef;
}
std::string Card::signingKeyRef() const
{
return mSigningKeyRef;
}
bool Card::hasSigningKey() const
{
return !keyInfo(mSigningKeyRef).grip.empty();
}
void Card::setEncryptionKeyRef(const std::string &keyRef)
{
mEncryptionKeyRef = keyRef;
}
std::string Card::encryptionKeyRef() const
{
return mEncryptionKeyRef;
}
bool Card::hasEncryptionKey() const
{
return !keyInfo(mEncryptionKeyRef).grip.empty();
}
void Card::setAuthenticationKeyRef(const std::string &keyRef)
{
mAuthenticationKeyRef = keyRef;
}
std::string Card::authenticationKeyRef() const
{
return mAuthenticationKeyRef;
}
bool Card::hasAuthenticationKey() const
{
return !keyInfo(mAuthenticationKeyRef).grip.empty();
}
std::vector<Card::PinState> Card::pinStates() const
{
return mPinStates;
}
void Card::setPinStates(const std::vector<PinState> &pinStates)
{
mPinStates = pinStates;
}
bool Card::hasNullPin() const
{
return mHasNullPin;
}
void Card::setHasNullPin(bool value)
{
mHasNullPin = value;
}
bool Card::operator==(const Card &other) const
{
return mHasNullPin == other.mHasNullPin && mStatus == other.mStatus && mSerialNumber == other.mSerialNumber && mAppName == other.mAppName
&& mAppVersion == other.mAppVersion && mCardType == other.mCardType && mCardVersion == other.mCardVersion && mCardHolder == other.mCardHolder
&& mSigningKeyRef == other.mSigningKeyRef && mEncryptionKeyRef == other.mEncryptionKeyRef && mAuthenticationKeyRef == other.mAuthenticationKeyRef
&& mPinStates == other.mPinStates && mErrMsg == other.mErrMsg && mKeyInfos == other.mKeyInfos && mCardInfo == other.mCardInfo;
}
bool Card::operator!=(const Card &other) const
{
return !operator==(other);
}
void Card::setErrorMsg(const QString &msg)
{
mErrMsg = msg;
}
QString Card::errorMsg() const
{
return mErrMsg;
}
void Card::setInitialKeyInfos(const std::vector<KeyPairInfo> &infos)
{
mKeyInfos = infos;
}
const std::vector<KeyPairInfo> &Card::keyInfos() const
{
return mKeyInfos;
}
const KeyPairInfo &Card::keyInfo(const std::string &keyRef) const
{
static const KeyPairInfo nullKey;
for (const KeyPairInfo &k : mKeyInfos) {
if (k.keyRef == keyRef) {
return k;
}
}
return nullKey;
}
void Card::setCardInfo(const std::vector<std::pair<std::string, std::string>> &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();
parseCardInfo(pair.first, pair.second);
}
}
namespace
{
static int parseHexEncodedVersionTuple(const std::string &s)
{
// s is a hex-encoded, unsigned int-packed version tuple,
// i.e. each byte represents one part of the version tuple
bool ok;
const auto version = QByteArray::fromStdString(s).toUInt(&ok, 16);
return ok ? version : -1;
}
}
void Card::parseCardInfo(const std::string &name, const std::string &value)
{
if (name == "APPVERSION") {
mAppVersion = parseHexEncodedVersionTuple(value);
} else if (name == "CARDTYPE") {
mCardType = value;
} else if (name == "CARDVERSION") {
mCardVersion = parseHexEncodedVersionTuple(value);
} else if (name == "DISP-NAME") {
auto list = QString::fromUtf8(QByteArray::fromStdString(value)).split(QStringLiteral("<<"), Qt::SkipEmptyParts);
std::reverse(list.begin(), list.end());
mCardHolder = list.join(QLatin1Char(' ')).replace(QLatin1Char('<'), QLatin1Char(' '));
} else if (name == "KEYPAIRINFO") {
const KeyPairInfo info = KeyPairInfo::fromStatusLine(value);
if (info.grip.empty()) {
qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(value);
setStatus(Card::CardError);
} else {
updateKeyInfo(info);
}
} else if (name == "KEY-FPR") {
// handle OpenPGP key fingerprints
const auto values = QString::fromStdString(value).split(QLatin1Char(' '));
if (values.size() < 2) {
qCWarning(KLEOPATRA_LOG) << "Invalid KEY-FPR status line" << QString::fromStdString(value);
setStatus(Card::CardError);
}
const auto keyNumber = values[0];
const std::string keyRef = "OPENPGP." + keyNumber.toStdString();
const auto fpr = values[1].toStdString();
if (keyNumber == QLatin1Char('1') || keyNumber == QLatin1Char('2') || keyNumber == QLatin1Char('3')) {
addCardInfo("KLEO-FPR-" + keyRef, fpr);
} else {
// Maybe more keyslots in the future?
qCDebug(KLEOPATRA_LOG) << "Unhandled keyslot" << keyNumber;
}
} else if (name == "MANUFACTURER") {
// the value of 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
// for PKCS#15 cards the manufacturer ID is always 0, e.g.
// 0 www.atos.net/cardos [R&S]
const auto startOfManufacturerName = value.find(' ');
if (startOfManufacturerName != std::string::npos) {
addCardInfo(name, value.substr(startOfManufacturerName + 1));
}
} else {
mCardInfo.insert({name, value});
}
}
void Card::addCardInfo(const std::string &name, const std::string &value)
{
mCardInfo.insert({name, value});
}
std::string Card::cardInfo(const std::string &name) const
{
const auto range = mCardInfo.equal_range(name);
return range.first != range.second ? range.first->second : std::string();
}
void Card::updateKeyInfo(const KeyPairInfo &keyPairInfo)
{
for (KeyPairInfo &k : mKeyInfos) {
if (k.keyRef == keyPairInfo.keyRef) {
k.update(keyPairInfo);
return;
}
}
mKeyInfos.push_back(keyPairInfo);
}
std::string Card::keyFingerprint(const std::string &keyRef) const
{
return cardInfo("KLEO-FPR-" + keyRef);
}
+
+void Card::setDisplayAppName(const QString &displayAppName)
+{
+ mDisplayAppName = displayAppName;
+}
+
+QString Card::displayAppName() const
+{
+ return mDisplayAppName;
+}
diff --git a/src/smartcard/card.h b/src/smartcard/card.h
index 86e9a7502..bcaf789f9 100644
--- a/src/smartcard/card.h
+++ b/src/smartcard/card.h
@@ -1,141 +1,144 @@
#pragma once
/* smartcard/card.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-License-Identifier: GPL-2.0-or-later
*/
#include "keypairinfo.h"
#include <map>
#include <string>
#include <vector>
#include <QString>
namespace Kleo
{
namespace SmartCard
{
/** Class representing an application on a smartcard or similar hardware token. */
class Card
{
public:
enum PinState {
UnknownPinState,
NullPin,
PinBlocked,
NoPin,
PinOk,
NumPinStates
};
enum Status {
NoCard,
CardPresent,
CardActive,
CardUsable,
_NumScdStates,
CardError = _NumScdStates,
NumStates
};
Card();
virtual ~Card();
virtual bool operator==(const Card &other) const;
bool operator!=(const Card &other) const;
void setStatus(Status s);
Status status() const;
void setSerialNumber(const std::string &sn);
std::string serialNumber() const;
void setCardInfo(const std::vector<std::pair<std::string, std::string>> &infos);
QString displaySerialNumber() const;
void setDisplaySerialNumber(const QString &sn);
std::string appName() const;
+ QString displayAppName() const;
void setAppVersion(int version);
int appVersion() const;
QString displayAppVersion() const;
void setManufacturer(const std::string &manufacturer);
std::string manufacturer() const;
std::string cardType() const;
int cardVersion() const;
QString displayCardVersion() const;
QString cardHolder() const;
void setSigningKeyRef(const std::string &keyRef);
std::string signingKeyRef() const;
bool hasSigningKey() const;
void setEncryptionKeyRef(const std::string &keyRef);
std::string encryptionKeyRef() const;
bool hasEncryptionKey() const;
void setAuthenticationKeyRef(const std::string &keyRef);
std::string authenticationKeyRef() const;
bool hasAuthenticationKey() const;
std::vector<PinState> pinStates() const;
void setPinStates(const std::vector<PinState> &pinStates);
bool hasNullPin() const;
void setHasNullPin(bool value);
QString errorMsg() const;
void setErrorMsg(const QString &msg);
const std::vector<KeyPairInfo> &keyInfos() const;
const KeyPairInfo &keyInfo(const std::string &keyRef) const;
std::string keyFingerprint(const std::string &keyRef) const;
protected:
void setAppName(const std::string &name);
+ void setDisplayAppName(const QString &displayAppName);
void setInitialKeyInfos(const std::vector<KeyPairInfo> &infos);
void addCardInfo(const std::string &name, const std::string &value);
std::string cardInfo(const std::string &name) const;
private:
void parseCardInfo(const std::string &name, const std::string &value);
void updateKeyInfo(const KeyPairInfo &keyPairInfo);
private:
bool mHasNullPin = false;
Status mStatus = NoCard;
std::string mSerialNumber;
QString mDisplaySerialNumber;
std::string mAppName;
int mAppVersion = -1;
std::string mCardType;
int mCardVersion = -1;
QString mCardHolder;
std::string mSigningKeyRef;
std::string mEncryptionKeyRef;
std::string mAuthenticationKeyRef;
std::vector<PinState> mPinStates;
QString mErrMsg;
std::vector<KeyPairInfo> mKeyInfos;
std::multimap<std::string, std::string> mCardInfo;
+ QString mDisplayAppName;
};
} // namespace Smartcard
} // namespace Kleopatra
diff --git a/src/smartcard/netkeycard.cpp b/src/smartcard/netkeycard.cpp
index dd3e1b79b..b4c6fa8fd 100644
--- a/src/smartcard/netkeycard.cpp
+++ b/src/smartcard/netkeycard.cpp
@@ -1,73 +1,74 @@
/* smartcard/netkeycard.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "netkeycard.h"
#include "keypairinfo.h"
#include "kleopatra_debug.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/Predicates>
#include <gpgme++/context.h>
#include <gpgme++/error.h>
#include <gpgme++/keylistresult.h>
#include <memory>
#include <string>
using namespace Kleo;
using namespace Kleo::SmartCard;
// static
const std::string NetKeyCard::AppName = "nks";
NetKeyCard::NetKeyCard(const Card &card)
: Card(card)
{
setAppName(AppName);
+ setDisplayAppName(QStringLiteral("NetKey"));
}
// static
std::string NetKeyCard::nksPinKeyRef()
{
return std::string("PW1.CH");
}
// static
std::string NetKeyCard::sigGPinKeyRef()
{
return std::string("PW1.CH.SIG");
}
// State 0 -> NKS PIN Retry counter
// State 1 -> NKS PUK Retry counter
// State 2 -> SigG PIN Retry counter
// State 3 -> SigG PUK Retry counter
bool NetKeyCard::hasNKSNullPin() const
{
const auto states = pinStates();
if (states.size() < 2) {
qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << states.size();
return false;
}
return states[0] == Card::NullPin;
}
bool NetKeyCard::hasSigGNullPin() const
{
const auto states = pinStates();
if (states.size() < 4) {
qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << states.size();
return false;
}
return states[2] == Card::NullPin;
}
diff --git a/src/smartcard/openpgpcard.cpp b/src/smartcard/openpgpcard.cpp
index 6e26820ea..5cfb051f1 100644
--- a/src/smartcard/openpgpcard.cpp
+++ b/src/smartcard/openpgpcard.cpp
@@ -1,172 +1,173 @@
/* smartcard/openpgpcard.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, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
/* Code in this file is partly based on the GNU Privacy Assistant
* (cm-openpgp.c) git rev. 0a78795146661234070681737b3e08228616441f
*
* Whis is:
* SPDX-FileCopyrightText: 2008, 2009 g 10 Code GmbH
*
* And may be licensed under the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*/
#include "openpgpcard.h"
#include "algorithminfo.h"
#include <utils/qt-cxx20-compat.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <KLocalizedString>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
// static
const std::string OpenPGPCard::AppName = "openpgp";
OpenPGPCard::OpenPGPCard(const Card &card)
: Card(card)
{
setAppName(AppName);
+ setDisplayAppName(QStringLiteral("OpenPGP"));
setInitialKeyInfos(OpenPGPCard::supportedKeys());
}
// static
std::string OpenPGPCard::pgpSigKeyRef()
{
return std::string("OPENPGP.1");
}
// static
std::string OpenPGPCard::pgpEncKeyRef()
{
return std::string("OPENPGP.2");
}
// static
std::string OpenPGPCard::pgpAuthKeyRef()
{
return std::string("OPENPGP.3");
}
// static
std::string OpenPGPCard::pinKeyRef()
{
return std::string("OPENPGP.1");
}
// static
std::string OpenPGPCard::adminPinKeyRef()
{
return std::string("OPENPGP.3");
}
// static
std::string OpenPGPCard::resetCodeKeyRef()
{
return std::string("OPENPGP.2");
}
// static
const std::vector<KeyPairInfo> &OpenPGPCard::supportedKeys()
{
static const std::vector<KeyPairInfo> keyInfos = {
{OpenPGPCard::pgpSigKeyRef(), "", "sc", "", ""},
{OpenPGPCard::pgpEncKeyRef(), "", "e", "", ""},
{OpenPGPCard::pgpAuthKeyRef(), "", "a", "", ""},
};
return keyInfos;
}
// static
QString OpenPGPCard::keyDisplayName(const std::string &keyRef)
{
static const QMap<std::string, QString> displayNames = {
{OpenPGPCard::pgpSigKeyRef(), i18n("Signature")},
{OpenPGPCard::pgpEncKeyRef(), i18n("Encryption")},
{OpenPGPCard::pgpAuthKeyRef(), i18n("Authentication")},
};
return displayNames.value(keyRef);
}
// static
std::string OpenPGPCard::getAlgorithmName(const std::string &algorithm, const std::string &keyRef)
{
static const std::map<std::string, std::string> ecdhAlgorithmMapping = {
{"curve25519", "cv25519"},
{"curve448", "cv448"},
};
static const std::map<std::string, std::string> eddsaAlgorithmMapping = {
{"curve25519", "ed25519"},
{"curve448", "ed448"},
};
if (keyRef == OpenPGPCard::pgpEncKeyRef()) {
const auto it = ecdhAlgorithmMapping.find(algorithm);
if (it != ecdhAlgorithmMapping.end()) {
return it->second;
}
} else {
const auto it = eddsaAlgorithmMapping.find(algorithm);
if (it != eddsaAlgorithmMapping.end()) {
return it->second;
}
}
return algorithm;
}
void OpenPGPCard::setSupportedAlgorithms(const std::vector<std::string> &algorithms)
{
const auto &availableAlgos = Kleo::availableAlgorithms();
const auto &ignoredAlgos = Kleo::ignoredAlgorithms();
mAlgorithms.clear();
Kleo::copy_if(algorithms, std::back_inserter(mAlgorithms), [&availableAlgos](const auto &algo) {
return Kleo::contains(availableAlgos, algo);
});
if (mAlgorithms.size() < algorithms.size()) {
std::vector<std::string> unsupportedAlgos;
Kleo::copy_if(algorithms, std::back_inserter(unsupportedAlgos), [&availableAlgos, &ignoredAlgos](const auto &algo) {
return !Kleo::contains(ignoredAlgos, algo) && !Kleo::contains(availableAlgos, algo);
});
if (unsupportedAlgos.size() > 0) {
qCWarning(KLEOPATRA_LOG).nospace() << "OpenPGPCard::" << __func__ << " Unsupported algorithms: " << unsupportedAlgos
<< " (supported: " << availableAlgos << ")";
}
}
}
std::string OpenPGPCard::pubkeyUrl() const
{
return cardInfo("PUBKEY-URL");
}
std::vector<AlgorithmInfo> OpenPGPCard::supportedAlgorithms() const
{
std::vector<AlgorithmInfo> algos;
std::transform(mAlgorithms.cbegin(), mAlgorithms.cend(), std::back_inserter(algos), [](const auto &algo) {
return AlgorithmInfo{algo, Formatting::prettyAlgorithmName(algo)};
});
return algos;
}
bool OpenPGPCard::isSupportedAlgorithm(const std::string &algorithm) const
{
return Kleo::contains(mAlgorithms, algorithm);
}
diff --git a/src/smartcard/p15card.cpp b/src/smartcard/p15card.cpp
index ae57b684f..fcbb1de36 100644
--- a/src/smartcard/p15card.cpp
+++ b/src/smartcard/p15card.cpp
@@ -1,21 +1,22 @@
/* smartcard/p15card.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Andre Heinecke <aheinecke@g10code.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "p15card.h"
using namespace Kleo::SmartCard;
// static
const std::string P15Card::AppName = "p15";
P15Card::P15Card(const Card &card)
: Card(card)
{
setAppName(AppName);
+ setDisplayAppName(QStringLiteral("PKCS#15"));
}
diff --git a/src/smartcard/pivcard.cpp b/src/smartcard/pivcard.cpp
index cd11377eb..c8be0a5fa 100644
--- a/src/smartcard/pivcard.cpp
+++ b/src/smartcard/pivcard.cpp
@@ -1,126 +1,127 @@
/* smartcard/pivcard.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pivcard.h"
#include "algorithminfo.h"
#include "keypairinfo.h"
#include <KLocalizedString>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
// static
const std::string PIVCard::AppName = "piv";
PIVCard::PIVCard(const Card &card)
: Card(card)
{
setAppName(AppName);
+ setDisplayAppName(QStringLiteral("PIV"));
setInitialKeyInfos(PIVCard::supportedKeys());
}
// 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<KeyPairInfo> &PIVCard::supportedKeys()
{
static const std::vector<KeyPairInfo> keyInfos = {
{PIVCard::pivAuthenticationKeyRef(), "", "a", "", ""},
{PIVCard::cardAuthenticationKeyRef(), "", "a", "", ""},
{PIVCard::digitalSignatureKeyRef(), "", "sc", "", ""},
{PIVCard::keyManagementKeyRef(), "", "e", "", ""},
};
return keyInfos;
}
// static
QString PIVCard::keyDisplayName(const std::string &keyRef)
{
static const QMap<std::string, QString> 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<AlgorithmInfo> 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::certificateData(const std::string &keyRef) const
{
return cardInfo("KLEO-CERTIFICATE-" + keyRef);
}
void PIVCard::setCertificateData(const std::string &keyRef, const std::string &data)
{
addCardInfo("KLEO-CERTIFICATE-" + keyRef, data);
}
diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp
index f7132eac0..6fd63695f 100644
--- a/src/smartcard/readerstatus.cpp
+++ b/src/smartcard/readerstatus.cpp
@@ -1,1297 +1,1308 @@
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "readerstatus.h"
#include "deviceinfowatcher.h"
#include <utils/qt-cxx20-compat.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Assuan>
#include <Libkleo/FileSystemWatcher>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/Stl_Util>
#include <QGpgME/Debug>
#include <gpgme++/context.h>
#include <gpgme++/defaultassuantransaction.h>
#include <gpgme++/engineinfo.h>
#include <gpg-error.h>
#include "netkeycard.h"
#include "openpgpcard.h"
#include "p15card.h"
#include "pivcard.h"
#include <QMutex>
#include <QPointer>
#include <QProcess>
#include <QRegularExpression>
#include <QThread>
#include <QWaitCondition>
#include "utils/kdtoolsglobal.h"
#include "kleopatra_debug.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))
Q_DECLARE_METATYPE(GpgME::Error)
namespace
{
static bool gpgHasMultiCardMultiAppSupport()
{
return !(engineInfo(GpgME::GpgEngine).engineVersion() < "2.3.0");
}
static QDebug operator<<(QDebug s, const std::vector<std::pair<std::string, std::string>> &v)
{
using pair = std::pair<std::string, std::string>;
s << '(';
for (const pair &p : v) {
s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n';
}
return s << ')';
}
struct CardApp {
std::string serialNumber;
std::string appName;
};
static void
logUnexpectedStatusLine(const std::pair<std::string, std::string> &line, const std::string &prefix = std::string(), const std::string &command = std::string())
{
qCWarning(KLEOPATRA_LOG) << (!prefix.empty() ? QString::fromStdString(prefix + ": ") : QString()) << "Unexpected status line"
<< (!command.empty() ? QString::fromStdString(" on " + command + ":") : QLatin1String(":")) << QString::fromStdString(line.first)
<< QString::fromStdString(line.second);
}
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;
}
}
}
static const std::string scd_getattr_status(std::shared_ptr<Context> &gpgAgent, const char *what, Error &err)
{
std::string cmd = "SCD GETATTR ";
cmd += what;
return Assuan::sendStatusCommand(gpgAgent, cmd.c_str(), err);
}
static const std::string getAttribute(std::shared_ptr<Context> &gpgAgent, const char *attribute, const char *versionHint)
{
Error err;
const auto result = scd_getattr_status(gpgAgent, attribute, err);
if (err) {
if (err.code() == GPG_ERR_INV_NAME) {
qCDebug(KLEOPATRA_LOG) << "Querying for attribute" << attribute << "not yet supported; needs GnuPG" << versionHint;
} else {
qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR " << attribute << " failed:" << err;
}
return std::string();
}
return result;
}
enum GetCardsAndAppsOptions {
WithReportedAppOrder,
WithStableAppOrder,
};
static std::vector<CardApp> getCardsAndApps(std::shared_ptr<Context> &gpgAgent, GetCardsAndAppsOptions options, Error &err)
{
std::vector<CardApp> result;
if (gpgHasMultiCardMultiAppSupport()) {
const std::string command = "SCD GETINFO all_active_apps";
const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err);
if (err) {
return result;
}
for (const auto &statusLine : statusLines) {
if (statusLine.first == "SERIALNO") {
const auto serialNumberAndApps = QByteArray::fromStdString(statusLine.second).split(' ');
if (serialNumberAndApps.size() >= 2) {
const auto serialNumber = serialNumberAndApps[0];
auto apps = serialNumberAndApps.mid(1);
if (options == WithStableAppOrder) {
// sort the apps to get a stable order independently of the currently selected application
std::sort(apps.begin(), apps.end());
}
for (const auto &app : apps) {
qCDebug(KLEOPATRA_LOG) << "getCardsAndApps(): Found card" << serialNumber << "with app" << app;
result.push_back({serialNumber.toStdString(), app.toStdString()});
}
} else {
logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command);
}
} else {
logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command);
}
}
} else {
// use SCD SERIALNO to get the currently active card
const auto serialNumber = Assuan::sendStatusCommand(gpgAgent, "SCD SERIALNO", err);
if (err) {
return result;
}
// use SCD GETATTR APPTYPE to find out which app is active
auto appName = scd_getattr_status(gpgAgent, "APPTYPE", err);
std::transform(appName.begin(), appName.end(), appName.begin(), [](unsigned char c) {
return std::tolower(c);
});
if (err) {
return result;
}
result.push_back({serialNumber, appName});
}
return result;
}
static std::string switchCard(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber, Error &err)
{
const std::string command = "SCD SWITCHCARD " + serialNumber;
const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err);
if (err) {
return std::string();
}
if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second == serialNumber) {
return serialNumber;
}
qCWarning(KLEOPATRA_LOG) << "switchCard():" << command << "returned" << statusLines << "(expected:"
<< "SERIALNO " + serialNumber << ")";
return std::string();
}
static std::string switchApp(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber, const std::string &appName, Error &err)
{
const std::string command = "SCD SWITCHAPP " + appName;
const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err);
if (err) {
return std::string();
}
if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second.find(serialNumber + ' ' + appName) == 0) {
return appName;
}
qCWarning(KLEOPATRA_LOG) << "switchApp():" << command << "returned" << statusLines << "(expected:"
<< "SERIALNO " + serialNumber + ' ' + appName + "..."
<< ")";
return std::string();
}
static std::vector<std::string> getCardApps(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber, Error &err)
{
const auto cardApps = getCardsAndApps(gpgAgent, WithReportedAppOrder, err);
if (err) {
return {};
}
std::vector<std::string> apps;
kdtools::transform_if(
cardApps.begin(),
cardApps.end(),
std::back_inserter(apps),
[](const auto &cardApp) {
return cardApp.appName;
},
[serialNumber](const auto &cardApp) {
return cardApp.serialNumber == serialNumber;
});
qCDebug(KLEOPATRA_LOG) << __func__ << "apps:" << apps;
return apps;
}
static void switchCardBackToOpenPGPApp(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber, Error &err)
{
if (!gpgHasMultiCardMultiAppSupport()) {
return;
}
const auto apps = getCardApps(gpgAgent, serialNumber, err);
if (err || apps.empty() || apps[0] == OpenPGPCard::AppName) {
return;
}
if (Kleo::contains(apps, OpenPGPCard::AppName)) {
switchApp(gpgAgent, serialNumber, OpenPGPCard::AppName, 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 std::vector<std::string> get_openpgp_card_supported_algorithms_announced_by_card(std::shared_ptr<Context> &gpgAgent)
{
static constexpr std::string_view cardSlotPrefix = "OPENPGP.1 ";
static const std::map<std::string_view, std::string_view> algoMapping = {
{"cv25519", "curve25519"},
{"cv448", "curve448"},
{"ed25519", "curve25519"},
{"ed448", "curve448"},
{"x448", "curve448"},
};
Error err;
const auto lines = Assuan::sendStatusLinesCommand(gpgAgent, "SCD GETATTR KEY-ATTR-INFO", err);
if (err) {
return {};
}
std::vector<std::string> algos;
kdtools::transform_if(
lines.cbegin(),
lines.cend(),
std::back_inserter(algos),
[](const auto &line) {
auto algo = line.second.substr(cardSlotPrefix.size());
// map a few algorithms to the standard names used by us
const auto mapping = algoMapping.find(algo);
if (mapping != algoMapping.end()) {
algo = mapping->second;
}
return algo;
},
[](const auto &line) {
// only consider KEY-ATTR-INFO status lines for the first card slot;
// for now, we assume that all card slots support the same algorithms
return line.first == "KEY-ATTR-INFO" && line.second.starts_with(cardSlotPrefix);
});
// remove duplicate algorithms
std::sort(algos.begin(), algos.end());
algos.erase(std::unique(algos.begin(), algos.end()), algos.end());
qCDebug(KLEOPATRA_LOG) << __func__ << "returns" << algos;
return algos;
}
static std::vector<std::string> get_openpgp_card_supported_algorithms(Card *card, std::shared_ptr<Context> &gpgAgent)
{
// first ask the smart card for the supported algorithms
const std::vector<std::string> announcedAlgos = get_openpgp_card_supported_algorithms_announced_by_card(gpgAgent);
if (!announcedAlgos.empty()) {
return announcedAlgos;
}
// otherwise, fall back to hard-coded lists
if ((card->cardType() == "yubikey") && (card->cardVersion() >= 0x050203)) {
return {
"rsa2048",
"rsa3072",
"rsa4096",
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
"curve25519",
};
} else if ((card->cardType() == "zeitcontrol") && (card->appVersion() >= 0x0304)) {
return {
"rsa2048",
"rsa3072",
"rsa4096",
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
};
}
return {"rsa2048", "rsa3072", "rsa4096"};
}
static bool isOpenPGPCardSerialNumber(const std::string &serialNumber)
{
return serialNumber.size() == 32 && serialNumber.substr(0, 12) == "D27600012401";
}
static const std::string getDisplaySerialNumber(std::shared_ptr<Context> &gpgAgent, Error &err)
{
const auto displaySerialNumber = scd_getattr_status(gpgAgent, "$DISPSERIALNO", err);
if (err && err.code() != GPG_ERR_INV_NAME) {
qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR $DISPSERIALNO failed:" << err;
}
return displaySerialNumber;
}
static void setDisplaySerialNumber(Card *card, std::shared_ptr<Context> &gpgAgent)
{
static const QRegularExpression leadingZeros(QStringLiteral("^0*"));
Error err;
const QString displaySerialNumber = QString::fromStdString(getDisplaySerialNumber(gpgAgent, err));
if (err) {
card->setDisplaySerialNumber(QString::fromStdString(card->serialNumber()));
return;
}
if (isOpenPGPCardSerialNumber(card->serialNumber()) && displaySerialNumber.size() == 12) {
// add a space between manufacturer id and card id for OpenPGP cards
card->setDisplaySerialNumber(displaySerialNumber.left(4) + QLatin1Char(' ') + displaySerialNumber.right(8));
} else {
card->setDisplaySerialNumber(displaySerialNumber);
}
return;
}
static void learnCardKeyStubs(const Card *card, std::shared_ptr<Context> &gpg_agent)
{
for (const KeyPairInfo &keyInfo : card->keyInfos()) {
if (!keyInfo.grip.empty()) {
Error err;
const auto command = std::string("READKEY --card --no-data -- ") + keyInfo.keyRef;
(void)Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err);
if (err) {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
}
}
}
}
static void handle_openpgp_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto pgpCard = new OpenPGPCard(*ci);
const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err.code()) {
ci->setStatus(Card::CardError);
return;
}
pgpCard->setCardInfo(info);
if (pgpCard->manufacturer().empty()) {
// fallback in case MANUFACTURER is not yet included in the card info
pgpCard->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber()));
}
setDisplaySerialNumber(pgpCard, gpg_agent);
learnCardKeyStubs(pgpCard, gpg_agent);
pgpCard->setSupportedAlgorithms(get_openpgp_card_supported_algorithms(pgpCard, gpg_agent));
ci.reset(pgpCard);
}
static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr<Context> &gpg_agent)
{
Error err;
const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef;
const auto keyPairInfoLines = Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err);
if (err) {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
return;
}
// this adds the key algorithm (and the key creation date, but that seems to be unset for PIV) to the existing key pair information
pivCard->setCardInfo(keyPairInfoLines);
}
static void readCertificateFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr<Context> &gpg_agent)
{
Error err;
const std::string command = std::string("SCD READCERT ") + keyRef;
const std::string certificateData = Assuan::sendDataCommand(gpg_agent, command.c_str(), err);
if (err && err.code() != GPG_ERR_NOT_FOUND) {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
return;
}
if (certificateData.empty()) {
qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): No certificate stored on card";
return;
}
qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Found certificate stored on card";
pivCard->setCertificateData(keyRef, certificateData);
}
static void handle_piv_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto pivCard = new PIVCard(*ci);
const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err) {
ci->setStatus(Card::CardError);
return;
}
pivCard->setCardInfo(info);
setDisplaySerialNumber(pivCard, gpg_agent);
for (const KeyPairInfo &keyInfo : pivCard->keyInfos()) {
if (!keyInfo.grip.empty()) {
readKeyPairInfoFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent);
readCertificateFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent);
}
}
learnCardKeyStubs(pivCard, gpg_agent);
ci.reset(pivCard);
}
static void handle_p15_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto p15Card = new P15Card(*ci);
auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err) {
ci->setStatus(Card::CardError);
return;
}
const auto fprs = Assuan::sendStatusLinesCommand(gpg_agent, "SCD GETATTR KEY-FPR", err);
if (!err) {
info.insert(info.end(), fprs.begin(), fprs.end());
}
p15Card->setCardInfo(info);
learnCardKeyStubs(p15Card, gpg_agent);
setDisplaySerialNumber(p15Card, gpg_agent);
ci.reset(p15Card);
}
static void handle_netkey_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto nkCard = new NetKeyCard(*ci);
ci.reset(nkCard);
ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err)));
if (err.code()) {
qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR NKS-VERSION failed:" << err;
ci->setErrorMsg(QStringLiteral("NKS-VERSION failed: ") + Formatting::errorAsString(err));
return;
}
if (ci->appVersion() < 3) {
qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 (or later) card, giving up. Version:" << ci->appVersion();
ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion()));
return;
}
setDisplaySerialNumber(nkCard, gpg_agent);
// 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) << "Running SCD GETATTR CHV-STATUS failed:" << err;
ci->setErrorMsg(QStringLiteral("CHV-Status failed: ") + Formatting::errorAsString(err));
return;
}
std::vector<Card::PinState> 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);
const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err);
if (err) {
ci->setStatus(Card::CardError);
return;
}
nkCard->setCardInfo(info);
learnCardKeyStubs(nkCard, gpg_agent);
}
static std::shared_ptr<Card> get_card_status(const std::string &serialNumber, const std::string &appName, std::shared_ptr<Context> &gpg_agent)
{
qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')';
auto ci = std::shared_ptr<Card>(new Card());
if (gpgHasMultiCardMultiAppSupport()) {
// select card
Error err;
const auto result = switchCard(gpg_agent, serialNumber, err);
if (err) {
if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) {
ci->setStatus(Card::NoCard);
} else {
ci->setStatus(Card::CardError);
}
return ci;
}
if (result.empty()) {
qCWarning(KLEOPATRA_LOG) << "get_card_status: switching card failed";
ci->setStatus(Card::CardError);
return ci;
}
ci->setStatus(Card::CardPresent);
} else {
ci->setStatus(Card::CardPresent);
}
if (gpgHasMultiCardMultiAppSupport()) {
// select app
Error err;
const auto result = switchApp(gpg_agent, serialNumber, appName, err);
if (err) {
if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) {
ci->setStatus(Card::NoCard);
} else {
ci->setStatus(Card::CardError);
}
return ci;
}
if (result.empty()) {
qCWarning(KLEOPATRA_LOG) << "get_card_status: switching app failed";
ci->setStatus(Card::CardError);
return ci;
}
}
ci->setSerialNumber(serialNumber);
ci->setSigningKeyRef(getAttribute(gpg_agent, "$SIGNKEYID", "2.2.18"));
ci->setEncryptionKeyRef(getAttribute(gpg_agent, "$ENCRKEYID", "2.2.18"));
// Handle different card types
if (appName == NetKeyCard::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end";
handle_netkey_card(ci, gpg_agent);
} else if (appName == OpenPGPCard::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end";
ci->setAuthenticationKeyRef(OpenPGPCard::pgpAuthKeyRef());
handle_openpgp_card(ci, gpg_agent);
} else if (appName == PIVCard::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end";
handle_piv_card(ci, gpg_agent);
} else if (appName == P15Card::AppName) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found P15 card" << ci->serialNumber().c_str() << "end";
handle_p15_card(ci, gpg_agent);
} else {
qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << appName;
}
if (gpgHasMultiCardMultiAppSupport() && appName != OpenPGPCard::AppName) {
// switch the card app back to OpenPGP; errors are ignored
GpgME::Error dummy;
switchCardBackToOpenPGPApp(gpg_agent, serialNumber, dummy);
}
return ci;
}
static bool isCardNotPresentError(const GpgME::Error &err)
{
// see fixup_scd_errors() in gpg-card.c
return err
&& ((err.code() == GPG_ERR_CARD_NOT_PRESENT)
|| ((err.code() == GPG_ERR_ENODEV || err.code() == GPG_ERR_CARD_REMOVED) && (err.sourceID() == GPG_ERR_SOURCE_SCD)));
}
static std::vector<std::shared_ptr<Card>> update_cardinfo(std::shared_ptr<Context> &gpgAgent)
{
qCDebug(KLEOPATRA_LOG) << "update_cardinfo()";
// ensure that a card is present and that all cards are properly set up
{
Error err;
const char *command = (gpgHasMultiCardMultiAppSupport()) ? "SCD SERIALNO --all" : "SCD SERIALNO";
const std::string serialno = Assuan::sendStatusCommand(gpgAgent, command, err);
if (err) {
if (isCardNotPresentError(err)) {
qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present";
return std::vector<std::shared_ptr<Card>>();
} else {
qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err;
auto ci = std::shared_ptr<Card>(new Card());
ci->setStatus(Card::CardError);
return std::vector<std::shared_ptr<Card>>(1, ci);
}
}
}
Error err;
const std::vector<CardApp> cardApps = getCardsAndApps(gpgAgent, WithStableAppOrder, err);
if (err) {
if (isCardNotPresentError(err)) {
qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present";
return std::vector<std::shared_ptr<Card>>();
} else {
qCWarning(KLEOPATRA_LOG) << "Getting active apps on all inserted cards failed:" << err;
auto ci = std::shared_ptr<Card>(new Card());
ci->setStatus(Card::CardError);
return std::vector<std::shared_ptr<Card>>(1, ci);
}
}
std::vector<std::shared_ptr<Card>> cards;
for (const auto &cardApp : cardApps) {
const auto card = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent);
cards.push_back(card);
}
return cards;
}
} // namespace
struct Transaction {
CardApp cardApp;
QByteArray command;
QPointer<QObject> receiver;
ReaderStatus::TransactionFunc slot;
AssuanTransaction *assuanTransaction;
};
static const Transaction learnCMSTransaction = {{"__all__", "__cms__"}, "__learn__", nullptr, nullptr, nullptr};
static const Transaction updateTransaction = {{"__all__", "__all__"}, "__update__", nullptr, nullptr, nullptr};
static const Transaction quitTransaction = {{"__all__", "__all__"}, "__quit__", nullptr, nullptr, nullptr};
namespace
{
static void logProcessOutput(const char *prefix, const char *label, const QByteArray &output)
{
if (output.isEmpty()) {
return;
}
for (const auto &line : output.split('\n')) {
qCDebug(KLEOPATRA_LOG) << prefix << label << line;
}
}
class ReaderStatusThread : public QThread
{
Q_OBJECT
public:
explicit ReaderStatusThread(QObject *parent = nullptr)
: QThread(parent)
, m_transactions(1, updateTransaction) // force initial scan
{
connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished);
}
std::vector<std::shared_ptr<Card>> 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 firstCardWithNullPinChanged(const std::string &serialNumber);
void cardAdded(const std::string &serialNumber, const std::string &appName);
void cardChanged(const std::string &serialNumber, const std::string &appName);
void cardRemoved(const std::string &serialNumber, const std::string &appName);
void updateFinished();
void oneTransactionFinished(const GpgME::Error &err);
void startingLearnCards(GpgME::Protocol protocol);
void cardsLearned(GpgME::Protocol protocol);
public Q_SLOTS:
void deviceStatusChanged(const QByteArray &details)
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::deviceStatusChanged(" << details << ")";
addTransaction(updateTransaction);
}
void ping()
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()";
addTransaction(updateTransaction);
}
void learnCardsCMS()
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::learnCardsCMS()";
addTransaction(learnCMSTransaction);
}
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<Transaction> ft;
KDAB_SYNCHRONIZED(m_mutex)
ft.splice(ft.begin(), m_finishedTransactions);
for (const Transaction &t : std::as_const(ft))
if (t.receiver && t.slot) {
QMetaObject::invokeMethod(
t.receiver,
[&t, &err]() {
t.slot(err);
},
Qt::DirectConnection);
}
}
private:
void learnCMSCards()
{
QProcess process;
connect(&process, &QProcess::readyReadStandardOutput, &process, [&process]() {
logProcessOutput("learnCMSCards", "stdout:", process.readAllStandardOutput());
});
connect(&process, &QProcess::readyReadStandardError, &process, [&process]() {
logProcessOutput("learnCMSCards", "stderr:", process.readAllStandardError());
});
process.start(gpgSmPath(), {QStringLiteral("--learn-card"), QStringLiteral("-v")});
qCDebug(KLEOPATRA_LOG) << __func__ << "Running" << process.program() << process.arguments().join(QLatin1Char{' '});
if (!process.waitForStarted()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Starting" << process.program() << "failed";
return;
}
// ensure that gpgsm doesn't wait for input
process.closeWriteChannel();
const bool success = process.waitForFinished(60000); // 60 seconds should be more than enough
logProcessOutput(__func__, "stdout:", process.readAllStandardOutput());
logProcessOutput(__func__, "stderr:", process.readAllStandardError());
if (success) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Running" << process.program() << process.arguments().join(QLatin1Char{' '}) << "succeeded";
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "Running" << process.program() << process.arguments().join(QLatin1Char{' '})
<< "failed; error:" << process.error() << ", exit code:" << process.exitCode();
}
}
void run() override
{
while (true) {
std::shared_ptr<Context> gpgAgent;
CardApp cardApp;
QByteArray command;
bool nullSlot = false;
AssuanTransaction *assuanTransaction = nullptr;
std::list<Transaction> item;
std::vector<std::shared_ptr<Card>> oldCards;
while (!KeyCache::instance()->initialized()) {
qCDebug(KLEOPATRA_LOG) << "Waiting for Keycache to be initialized.";
sleep(1);
}
Error err;
std::unique_ptr<Context> c = Context::createForEngine(AssuanEngine, &err);
if (err.code() == GPG_ERR_NOT_SUPPORTED) {
return;
}
gpgAgent = std::shared_ptr<Context>(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:
cardApp = item.front().cardApp;
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) {
bool anyError = false;
if (cardApp.serialNumber == "__all__" || cardApp.appName == "__all__") {
std::vector<std::shared_ptr<Card>> newCards = update_cardinfo(gpgAgent);
KDAB_SYNCHRONIZED(m_mutex)
{
m_cardInfos = newCards;
}
std::string firstCardWithNullPin;
for (const auto &newCard : newCards) {
const auto serialNumber = newCard->serialNumber();
const auto appName = newCard->appName();
const auto matchingOldCard =
std::find_if(oldCards.cbegin(), oldCards.cend(), [serialNumber, appName](const std::shared_ptr<Card> &card) {
return card->serialNumber() == serialNumber && card->appName() == appName;
});
if (matchingOldCard == oldCards.cend()) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added";
Q_EMIT cardAdded(serialNumber, appName);
} else {
if (*newCard != **matchingOldCard) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed";
Q_EMIT cardChanged(serialNumber, appName);
}
oldCards.erase(matchingOldCard);
}
if (newCard->hasNullPin() && firstCardWithNullPin.empty()) {
firstCardWithNullPin = newCard->serialNumber();
}
if (newCard->status() == Card::CardError) {
anyError = true;
}
}
for (const auto &oldCard : oldCards) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << oldCard->serialNumber() << "with app" << oldCard->appName() << "was removed";
Q_EMIT cardRemoved(oldCard->serialNumber(), oldCard->appName());
}
Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin);
} else {
auto updatedCard = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent);
const auto serialNumber = updatedCard->serialNumber();
const auto appName = updatedCard->appName();
bool cardWasAdded = false;
bool cardWasChanged = false;
KDAB_SYNCHRONIZED(m_mutex)
{
const auto matchingCard = std::find_if(m_cardInfos.begin(), m_cardInfos.end(), [serialNumber, appName](const auto &card) {
return card->serialNumber() == serialNumber && card->appName() == appName;
});
if (matchingCard == m_cardInfos.end()) {
m_cardInfos.push_back(updatedCard);
cardWasAdded = true;
} else {
cardWasChanged = (*updatedCard != **matchingCard);
m_cardInfos[std::distance(m_cardInfos.begin(), matchingCard)] = updatedCard;
}
if (updatedCard->status() == Card::CardError) {
anyError = true;
}
}
if (cardWasAdded) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added";
Q_EMIT cardAdded(serialNumber, appName);
} else if (cardWasChanged) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed";
Q_EMIT cardChanged(serialNumber, appName);
}
}
if (anyError) {
gpgAgent.reset();
}
Q_EMIT updateFinished();
} else if (nullSlot && command == learnCMSTransaction.command) {
Q_EMIT startingLearnCards(GpgME::CMS);
learnCMSCards();
Q_EMIT cardsLearned(GpgME::CMS);
} else {
GpgME::Error err;
if (gpgHasMultiCardMultiAppSupport()) {
switchCard(gpgAgent, cardApp.serialNumber, err);
if (!err) {
switchApp(gpgAgent, cardApp.serialNumber, cardApp.appName, err);
}
}
if (!err) {
if (assuanTransaction) {
(void)Assuan::sendCommand(gpgAgent, command.constData(), std::unique_ptr<AssuanTransaction>(assuanTransaction), err);
} else {
(void)Assuan::sendCommand(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;
// protected by m_mutex:
std::vector<std::shared_ptr<Card>> m_cardInfos;
std::list<Transaction> 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<Card::Status>("Kleo::SmartCard::Card::Status");
qRegisterMetaType<GpgME::Error>("GpgME::Error");
connect(this, &::ReaderStatusThread::cardAdded, q, &ReaderStatus::cardAdded);
connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged);
connect(this, &::ReaderStatusThread::cardRemoved, q, &ReaderStatus::cardRemoved);
connect(this, &::ReaderStatusThread::updateFinished, q, &ReaderStatus::updateFinished);
connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged, q, &ReaderStatus::firstCardWithNullPinChanged);
connect(this, &::ReaderStatusThread::startingLearnCards, q, &ReaderStatus::onStartingLearnCards);
connect(this, &::ReaderStatusThread::cardsLearned, q, &ReaderStatus::onCardsLearned);
if (DeviceInfoWatcher::isSupported()) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using new DeviceInfoWatcher";
connect(&devInfoWatcher, &DeviceInfoWatcher::statusChanged, this, &::ReaderStatusThread::deviceStatusChanged);
} else {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using deprecated FileSystemWatcher";
watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status")));
watcher.addPath(Kleo::gnupgHomeDirectory());
watcher.setDelay(100);
connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping);
}
}
~Private() override
{
stop();
if (!wait(100)) {
terminate();
wait();
}
}
private:
std::string firstCardWithNullPinImpl() const
{
const auto cis = cardInfos();
const auto firstWithNullPin = std::find_if(cis.cbegin(), cis.cend(), [](const std::shared_ptr<Card> &ci) {
return ci->hasNullPin();
});
return firstWithNullPin != cis.cend() ? (*firstWithNullPin)->serialNumber() : std::string();
}
private:
FileSystemWatcher watcher;
DeviceInfoWatcher devInfoWatcher;
std::shared_ptr<KeyCacheAutoRefreshSuspension> keyCacheAutoRefreshSuspension;
bool learnCMSTransactionScheduled = false;
};
ReaderStatus::ReaderStatus(QObject *parent)
: QObject(parent)
, d(new Private(this))
{
self = this;
qRegisterMetaType<std::string>("std::string");
}
ReaderStatus::~ReaderStatus()
{
self = nullptr;
}
// slot
void ReaderStatus::startMonitoring()
{
qCDebug(KLEOPATRA_LOG) << __func__;
if (!KeyCache::instance()->initialized()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "waiting for key cache ...";
connect(KeyCache::instance().get(), &KeyCache::keyListingDone, this, &ReaderStatus::startMonitoring);
return;
}
disconnect(KeyCache::instance().get(), &KeyCache::keyListingDone, this, &ReaderStatus::startMonitoring);
qCDebug(KLEOPATRA_LOG) << __func__ << "key cache is ready";
d->start();
if (DeviceInfoWatcher::isSupported()) {
connect(&d->devInfoWatcher, &DeviceInfoWatcher::startOfGpgAgentRequested, this, &ReaderStatus::startOfGpgAgentRequested);
d->devInfoWatcher.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);
}
std::string ReaderStatus::firstCardWithNullPin() const
{
return d->firstCardWithNullPinImpl();
}
void ReaderStatus::startSimpleTransaction(const std::shared_ptr<Card> &card, const QByteArray &command, QObject *receiver, const TransactionFunc &slot)
{
const CardApp cardApp = {card->serialNumber(), card->appName()};
const Transaction t = {cardApp, command, receiver, slot, nullptr};
d->addTransaction(t);
}
void ReaderStatus::startTransaction(const std::shared_ptr<Card> &card,
const QByteArray &command,
QObject *receiver,
const TransactionFunc &slot,
std::unique_ptr<AssuanTransaction> transaction)
{
const CardApp cardApp = {card->serialNumber(), card->appName()};
const Transaction t = {cardApp, command, receiver, slot, transaction.release()};
d->addTransaction(t);
}
void ReaderStatus::updateStatus()
{
d->ping();
}
void ReaderStatus::updateCard(const std::string &serialNumber, const std::string &appName)
{
const CardApp cardApp = {serialNumber, appName};
const Transaction t = {cardApp, updateTransaction.command, nullptr, nullptr, nullptr};
d->addTransaction(t);
}
void ReaderStatus::learnCards(GpgME::Protocol protocol)
{
qCDebug(KLEOPATRA_LOG) << __func__ << "- procotol:" << Formatting::displayName(protocol);
if (protocol == GpgME::CMS && !d->learnCMSTransactionScheduled) {
d->learnCMSTransactionScheduled = true;
d->learnCardsCMS();
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "unsupported protocol";
}
}
std::vector<std::shared_ptr<Card>> ReaderStatus::getCards() const
{
return d->cardInfos();
}
std::shared_ptr<Card> ReaderStatus::getCard(const std::string &serialNumber, const std::string &appName) const
{
for (const auto &card : d->cardInfos()) {
if (card->serialNumber() == serialNumber && card->appName() == appName) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << serialNumber << "and app" << appName;
return card;
}
}
qCWarning(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << serialNumber << "and app" << appName;
return std::shared_ptr<Card>();
}
// static
std::string ReaderStatus::switchCard(std::shared_ptr<Context> &ctx, const std::string &serialNumber, Error &err)
{
return ::switchCard(ctx, serialNumber, err);
}
// static
std::string ReaderStatus::switchApp(std::shared_ptr<Context> &ctx, const std::string &serialNumber, const std::string &appName, Error &err)
{
return ::switchApp(ctx, serialNumber, appName, err);
}
// static
Error ReaderStatus::switchCardAndApp(const std::string &serialNumber, const std::string &appName)
{
Error err;
if (!(engineInfo(GpgEngine).engineVersion() < "2.3.0")) {
std::unique_ptr<Context> c = Context::createForEngine(AssuanEngine, &err);
if (err.code() == GPG_ERR_NOT_SUPPORTED) {
return err;
}
auto assuanContext = std::shared_ptr<Context>(c.release());
const auto resultSerialNumber = switchCard(assuanContext, serialNumber, err);
if (err || resultSerialNumber != serialNumber) {
qCWarning(KLEOPATRA_LOG) << "Switching to card" << QString::fromStdString(serialNumber) << "failed";
if (!err) {
err = Error::fromCode(GPG_ERR_UNEXPECTED);
}
return err;
}
const auto resultAppName = switchApp(assuanContext, serialNumber, appName, err);
if (err || resultAppName != appName) {
qCWarning(KLEOPATRA_LOG) << "Switching card to" << QString::fromStdString(appName) << "app failed";
if (!err) {
err = Error::fromCode(GPG_ERR_UNEXPECTED);
}
return err;
}
}
return err;
}
// static
Error ReaderStatus::switchCardBackToOpenPGPApp(const std::string &serialNumber)
{
Error err;
if (gpgHasMultiCardMultiAppSupport()) {
std::unique_ptr<Context> c = Context::createForEngine(AssuanEngine, &err);
if (err.code() == GPG_ERR_NOT_SUPPORTED) {
return err;
}
auto assuanContext = std::shared_ptr<Context>(c.release());
::switchCardBackToOpenPGPApp(assuanContext, serialNumber, err);
}
return err;
}
void ReaderStatus::onStartingLearnCards(GpgME::Protocol protocol)
{
qCDebug(KLEOPATRA_LOG) << __func__;
// suspend automatic refreshes of the key cache while smart card keys are learned
d->keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
Q_EMIT startingLearnCards(protocol);
}
void ReaderStatus::onCardsLearned(GpgME::Protocol protocol)
{
qCDebug(KLEOPATRA_LOG) << __func__;
Q_EMIT cardsLearned(protocol);
d->learnCMSTransactionScheduled = false;
d->keyCacheAutoRefreshSuspension.reset();
// force a reload of the key cache to ensure that all learned certificates are loaded
KeyCache::mutableInstance()->reload(protocol, KeyCache::ForceReload);
}
+std::shared_ptr<Card> ReaderStatus::getCardWithKeyRef(const std::string &serialNumber, const std::string &keyRef) const
+{
+ for (const auto &card : SmartCard::ReaderStatus::instance()->getCards()) {
+ if (card->serialNumber() == serialNumber
+ && (card->authenticationKeyRef() == keyRef || card->signingKeyRef() == keyRef || card->encryptionKeyRef() == keyRef)) {
+ return card;
+ }
+ }
+ return nullptr;
+}
+
#include "readerstatus.moc"
diff --git a/src/smartcard/readerstatus.h b/src/smartcard/readerstatus.h
index 005195280..6a1ecca24 100644
--- a/src/smartcard/readerstatus.h
+++ b/src/smartcard/readerstatus.h
@@ -1,97 +1,98 @@
/* -*- mode: c++; c-basic-offset:4 -*-
smartcard/readerstatus.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QMetaType>
#include <QObject>
#include "card.h"
#include <gpgme++/global.h>
#include <memory>
#include <vector>
namespace GpgME
{
class AssuanTransaction;
class Context;
class Error;
}
namespace Kleo
{
namespace SmartCard
{
class ReaderStatus : public QObject
{
Q_OBJECT
public:
explicit ReaderStatus(QObject *parent = nullptr);
~ReaderStatus() override;
static const ReaderStatus *instance();
static ReaderStatus *mutableInstance();
using TransactionFunc = std::function<void(const GpgME::Error &)>;
void startSimpleTransaction(const std::shared_ptr<Card> &card, const QByteArray &cmd, QObject *receiver, const TransactionFunc &slot);
void startTransaction(const std::shared_ptr<Card> &card,
const QByteArray &cmd,
QObject *receiver,
const TransactionFunc &slot,
std::unique_ptr<GpgME::AssuanTransaction> transaction);
Card::Status cardStatus(unsigned int slot) const;
std::string firstCardWithNullPin() const;
std::vector<std::shared_ptr<Card>> getCards() const;
std::shared_ptr<Card> getCard(const std::string &serialNumber, const std::string &appName) const;
+ std::shared_ptr<Card> getCardWithKeyRef(const std::string &serialNumber, const std::string &keyRef) const;
template<typename T>
std::shared_ptr<T> getCard(const std::string &serialNumber) const
{
return std::dynamic_pointer_cast<T>(getCard(serialNumber, T::AppName));
}
static std::string switchCard(std::shared_ptr<GpgME::Context> &ctx, const std::string &serialNumber, GpgME::Error &err);
static std::string switchApp(std::shared_ptr<GpgME::Context> &ctx, const std::string &serialNumber, const std::string &appName, GpgME::Error &err);
static GpgME::Error switchCardAndApp(const std::string &serialNumber, const std::string &appName);
static GpgME::Error switchCardBackToOpenPGPApp(const std::string &serialNumber);
public Q_SLOTS:
void updateStatus();
void updateCard(const std::string &serialNumber, const std::string &appName);
void learnCards(GpgME::Protocol protocol);
void startMonitoring();
Q_SIGNALS:
void firstCardWithNullPinChanged(const std::string &serialNumber);
void cardAdded(const std::string &serialNumber, const std::string &appName);
void cardChanged(const std::string &serialNumber, const std::string &appName);
void cardRemoved(const std::string &serialNumber, const std::string &appName);
void updateFinished();
void startingLearnCards(GpgME::Protocol protocol);
void cardsLearned(GpgME::Protocol protocol);
void startOfGpgAgentRequested();
private:
void onStartingLearnCards(GpgME::Protocol protocol);
void onCardsLearned(GpgME::Protocol protocol);
class Private;
std::shared_ptr<Private> d;
};
} // namespace SmartCard
} // namespace Kleo
Q_DECLARE_METATYPE(Kleo::SmartCard::Card::Status)

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jul 12, 10:25 AM (1 d, 12 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
fb/e3/2010054dfb91af29ae9674b0d36d

Event Timeline