Page MenuHome GnuPG

No OneTemporary

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 <tobias.fella@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "cardinfotab.h"
#include "smartcard/openpgpcard.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 &cardSerial : cards) {
- std::shared_ptr<SmartCard::Card> 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 <type> <version> card", "Unknown %1 v%2 card", availableCard->displayAppName(), availableCard->displayAppVersion())
+ : i18nc("<Manufacturer> <type> <version> 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::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 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 <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 <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 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "readerstatus.h"
#include "deviceinfowatcher.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 + ":") : 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<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"
#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 <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
Thu, Feb 26, 6:42 PM (18 h, 51 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
1b/63/2d4035582b0acb7a1e2672f5cbd9

Event Timeline