diff --git a/src/dialogs/cardinfotab.cpp b/src/dialogs/cardinfotab.cpp index 77203a08d..090299d9a 100644 --- a/src/dialogs/cardinfotab.cpp +++ b/src/dialogs/cardinfotab.cpp @@ -1,178 +1,177 @@ // SPDX-FileCopyrightText: 2024 g10 Code GmbH // SPDX-FileContributor: Tobias Fella // SPDX-License-Identifier: GPL-2.0-or-later #include "cardinfotab.h" #include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "view/smartcardwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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 &cardSerial : cards) { - std::shared_ptr availableCard; - for (const auto &c : SmartCard::ReaderStatus::instance()->getCards()) { - if (c->serialNumber() == cardSerial.toStdString()) { - availableCard = c; - break; - } - } + for (const auto &[cardSerial, keyRef] : cards) { + auto availableCard = SmartCard::ReaderStatus::instance()->getCardWithKeyRef(cardSerial.toStdString(), keyRef.toStdString()); auto item = new QTreeWidgetItem; item->setData(0, Qt::DisplayRole, QString::fromLatin1(subkey.keyGrip())); item->setData(1, Qt::DisplayRole, cardSerial); if (availableCard) { const QString manufacturer = QString::fromStdString(availableCard->manufacturer()); const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1StringView("unknown"); - item->setData(2, - Qt::DisplayRole, - manufacturerIsUnknown ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", availableCard->displayAppVersion()) - : i18nc("First placeholder is manufacturer, second placeholder is a version number", - "%1 OpenPGP v%2 card", - manufacturer, - availableCard->displayAppVersion())); + item->setData( + 2, + Qt::DisplayRole, + manufacturerIsUnknown + ? i18nc("Unknown card", "Unknown %1 v%2 card", availableCard->displayAppName(), availableCard->displayAppVersion()) + : i18nc(" card", + "%1 %2 v%3 card", + manufacturer, + availableCard->displayAppName(), + availableCard->displayAppVersion())); item->setData(3, Qt::DisplayRole, availableCard->displaySerialNumber()); - item->setData(4, Qt::DisplayRole, availableCard->cardHolder()); + 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")); 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/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::pinStates() const { return mPinStates; } void Card::setPinStates(const std::vector &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 &infos) { mKeyInfos = infos; } const std::vector &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> &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 #include #include #include 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> &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 pinStates() const; void setPinStates(const std::vector &pinStates); bool hasNullPin() const; void setHasNullPin(bool value); QString errorMsg() const; void setErrorMsg(const QString &msg); const std::vector &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 &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 mPinStates; QString mErrMsg; std::vector mKeyInfos; std::multimap 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 #include #include #include #include #include #include #include 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 f5a18e05a..70a47f77b 100644 --- a/src/smartcard/openpgpcard.cpp +++ b/src/smartcard/openpgpcard.cpp @@ -1,170 +1,171 @@ /* 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 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 #include #include #include #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 &OpenPGPCard::supportedKeys() { static const std::vector keyInfos = { {OpenPGPCard::pgpSigKeyRef(), "", "sc", "", ""}, {OpenPGPCard::pgpEncKeyRef(), "", "e", "", ""}, {OpenPGPCard::pgpAuthKeyRef(), "", "a", "", ""}, }; return keyInfos; } // static QString OpenPGPCard::keyDisplayName(const std::string &keyRef) { static const QMap 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 ecdhAlgorithmMapping = { {"curve25519", "cv25519"}, {"curve448", "cv448"}, }; static const std::map 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 &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 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 OpenPGPCard::supportedAlgorithms() const { std::vector 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 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 SPDX-License-Identifier: GPL-2.0-or-later */ #include "pivcard.h" #include "algorithminfo.h" #include "keypairinfo.h" #include #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 &PIVCard::supportedKeys() { static const std::vector 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 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 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 e1dfc6eaa..9e4fd4c94 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 SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "readerstatus.h" #include "deviceinfowatcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "netkeycard.h" #include "openpgpcard.h" #include "p15card.h" #include "pivcard.h" #include #include #include #include #include #include #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> &v) { using pair = std::pair; 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 &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 + ":") : QLatin1StringView(":")) << 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 &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 &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 getCardsAndApps(std::shared_ptr &gpgAgent, GetCardsAndAppsOptions options, Error &err) { std::vector 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 &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 &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 getCardApps(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { const auto cardApps = getCardsAndApps(gpgAgent, WithReportedAppOrder, err); if (err) { return {}; } std::vector 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 &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 get_openpgp_card_supported_algorithms_announced_by_card(std::shared_ptr &gpgAgent) { static constexpr std::string_view cardSlotPrefix = "OPENPGP.1 "; static const std::map 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 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 get_openpgp_card_supported_algorithms(Card *card, std::shared_ptr &gpgAgent) { // first ask the smart card for the supported algorithms const std::vector 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 &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 &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 &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 &ci, std::shared_ptr &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 &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 &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 &ci, std::shared_ptr &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 &ci, std::shared_ptr &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 &ci, std::shared_ptr &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 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 get_card_status(const std::string &serialNumber, const std::string &appName, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr(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> update_cardinfo(std::shared_ptr &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>(); } else { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector>(1, ci); } } } Error err; const std::vector cardApps = getCardsAndApps(gpgAgent, WithStableAppOrder, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector>(); } else { qCWarning(KLEOPATRA_LOG) << "Getting active apps on all inserted cards failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector>(1, ci); } } std::vector> 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 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> 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 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 gpgAgent; CardApp cardApp; QByteArray command; bool nullSlot = false; AssuanTransaction *assuanTransaction = nullptr; std::list item; std::vector> oldCards; while (!KeyCache::instance()->initialized()) { qCDebug(KLEOPATRA_LOG) << "Waiting for Keycache to be initialized."; sleep(1); } Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: 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> 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) { 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), 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> m_cardInfos; std::list m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq) , q(qq) , watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType("Kleo::SmartCard::Card::Status"); qRegisterMetaType("GpgME::Error"); 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 &ci) { return ci->hasNullPin(); }); return firstWithNullPin != cis.cend() ? (*firstWithNullPin)->serialNumber() : std::string(); } private: FileSystemWatcher watcher; DeviceInfoWatcher devInfoWatcher; std::shared_ptr keyCacheAutoRefreshSuspension; bool learnCMSTransactionScheduled = false; }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent) , d(new Private(this)) { self = this; qRegisterMetaType("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, 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, const QByteArray &command, QObject *receiver, const TransactionFunc &slot, std::unique_ptr 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> ReaderStatus::getCards() const { return d->cardInfos(); } std::shared_ptr 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(); } // static std::string ReaderStatus::switchCard(std::shared_ptr &ctx, const std::string &serialNumber, Error &err) { return ::switchCard(ctx, serialNumber, err); } // static std::string ReaderStatus::switchApp(std::shared_ptr &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 c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr(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 c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr(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 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" #include "moc_readerstatus.cpp" 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 #include #include "card.h" #include #include #include 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 startSimpleTransaction(const std::shared_ptr &card, const QByteArray &cmd, QObject *receiver, const TransactionFunc &slot); void startTransaction(const std::shared_ptr &card, const QByteArray &cmd, QObject *receiver, const TransactionFunc &slot, std::unique_ptr transaction); Card::Status cardStatus(unsigned int slot) const; std::string firstCardWithNullPin() const; std::vector> getCards() const; std::shared_ptr getCard(const std::string &serialNumber, const std::string &appName) const; + std::shared_ptr getCardWithKeyRef(const std::string &serialNumber, const std::string &keyRef) const; template std::shared_ptr getCard(const std::string &serialNumber) const { return std::dynamic_pointer_cast(getCard(serialNumber, T::AppName)); } static std::string switchCard(std::shared_ptr &ctx, const std::string &serialNumber, GpgME::Error &err); static std::string switchApp(std::shared_ptr &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 d; }; } // namespace SmartCard } // namespace Kleo Q_DECLARE_METATYPE(Kleo::SmartCard::Card::Status)