diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp index 8093d0af4..e53f957e4 100644 --- a/src/commands/keytocardcommand.cpp +++ b/src/commands/keytocardcommand.cpp @@ -1,458 +1,452 @@ /* commands/keytocardcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keytocardcommand.h" #include "cardcommand_p.h" #include "commands/authenticatepivcardapplicationcommand.h" #include "dialogs/certificateselectiondialog.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace Kleo::SmartCard; using namespace GpgME; class KeyToCardCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::KeyToCardCommand; KeyToCardCommand *q_func() const { return static_cast(q); } public: explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey, const std::string &serialno); explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialno); ~Private(); private: void start(); void startKeyToOpenPGPCard(); Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &card); void startKeyToPIVCard(); void authenticate(); void authenticationFinished(); void authenticationCanceled(); private: GpgME::Subkey subkey; std::string cardSlot; bool overwriteExistingAlreadyApproved = false; bool hasBeenCanceled = false; }; KeyToCardCommand::Private *KeyToCardCommand::d_func() { return static_cast(d.get()); } const KeyToCardCommand::Private *KeyToCardCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey_, const std::string &serialno) : CardCommand::Private(qq, serialno, nullptr), subkey(subkey_) { } KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialno) : CardCommand::Private(qq, serialno, nullptr) , cardSlot(slot) { } KeyToCardCommand::Private::~Private() { } void KeyToCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()"; const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } - switch (card->appType()) { - case SmartCard::Card::OpenPGPApplication: { + if (card->appName() == SmartCard::OpenPGPCard::AppName) { startKeyToOpenPGPCard(); - } - break; - case SmartCard::Card::PivApplication: { + } else if (card->appName() == SmartCard::PIVCard::AppName) { startKeyToPIVCard(); - } - break; - default: { + } else { error(i18n("Sorry! Transferring keys to this card is not supported.")); finished(); return; } - } } namespace { static int getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent) { // Check if we need to ask the user for the slot if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) { // Signing only return 1; } if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) { // Encrypt only return 2; } if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) { // Auth only return 3; } // Multiple uses, ask user. QStringList options; if (subKey.canSign() || subKey.canCertify()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Signature (%1)", 1); } if (subKey.canEncrypt()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Encryption (%1)", 2); } if (subKey.canAuthenticate()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Authentication (%1)", 3); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"), i18n("Please select the card slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); const int slot = options.indexOf(choice) + 1; return ok ? slot : -1; } } void KeyToCardCommand::Private::startKeyToOpenPGPCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()"; const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); if (!pgpCard) { error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (subkey.isNull()) { finished(); return; } if (subkey.parent().protocol() != GpgME::OpenPGP) { error(i18n("Sorry! This key cannot be transferred to an OpenPGP card.")); finished(); return; } const auto slot = getOpenPGPCardSlotForKey(subkey, parentWidgetOrView()); if (slot < 1) { finished(); return; } // Check if we need to do the overwrite warning. std::string existingKey; QString encKeyWarning; if (slot == 1) { existingKey = pgpCard->sigFpr(); } else if (slot == 2) { existingKey = pgpCard->encFpr(); encKeyWarning = i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."); } else if (slot == 3) { existingKey = pgpCard->authFpr(); } if (!existingKey.empty()) { const QString message = i18nc("@info", "

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

" "

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

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

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

" "

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

") + i18n("The existing key has the key grip:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + decryptionWarning; const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message, i18nc("@title:window", "Overwrite existing key"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { finished(); return; } overwriteExistingAlreadyApproved = true; } } const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3") .arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber())) .arg(QString::fromStdString(cardSlot)); ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), q_func(), "keyToPIVCardDone"); } void KeyToCardCommand::Private::authenticate() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()"; auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView()); connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() { authenticationFinished(); }); connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() { authenticationCanceled(); }); cmd->start(); } void KeyToCardCommand::Private::authenticationFinished() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()"; if (!hasBeenCanceled) { startKeyToPIVCard(); } } void KeyToCardCommand::Private::authenticationCanceled() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()"; hasBeenCanceled = true; canceled(); } KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialno) : CardCommand(new Private(this, key, serialno)) { } KeyToCardCommand::KeyToCardCommand(const std::string& cardSlot, const std::string &serialno) : CardCommand(new Private(this, cardSlot, serialno)) { } KeyToCardCommand::~KeyToCardCommand() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()"; } bool KeyToCardCommand::supported() { return true; } void KeyToCardCommand::keyToOpenPGPCardDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Moving the key to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { /* TODO DELETE_KEY is too strong, because it also deletes the stub * of the secret key. I could not find out how GnuPG does this. Question * to GnuPG Developers is pending an answer if (KMessageBox::questionYesNo(d->parentWidgetOrView(), i18n("Do you want to delete the key on this computer?"), i18nc("@title:window", "Key transferred to card")) == KMessageBox::Yes) { const QString cmd = QStringLiteral("DELETE_KEY --force %1").arg(d->subkey.keyGrip()); // Using readerstatus is a bit overkill but it's an easy way to talk to the agent. ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), this, "deleteDone"); } */ d->information(i18nc("@info", "Successfully copied the key to the card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } d->finished(); } void KeyToCardCommand::keyToPIVCardDone(const GpgME::Error &err) { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::keyToPIVCardDone():" << err.asString() << "(" << err.code() << ")"; if (err) { // gpgme 1.13 reports "BAD PIN" instead of "NO AUTH" if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) { d->authenticate(); return; } d->error(i18nc("@info", "Copying the key pair to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { d->information(i18nc("@info", "Successfully copied the key pair to the card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } d->finished(); } void KeyToCardCommand::deleteDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Failed to delete the key: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } d->finished(); } void KeyToCardCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()"; d->start(); } void KeyToCardCommand::doCancel() { } #undef q_func #undef d_func diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index 69ad77814..9841f03d4 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,262 +1,263 @@ /* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "subkeyswidget.h" #include "ui_subkeyswidget.h" +#include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "commands/changeexpirycommand.h" #include "commands/keytocardcommand.h" #include "commands/importpaperkeycommand.h" #include "exportdialog.h" #include #include #include #include #include #include #include #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_EXPORT_FLAGS #endif #if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 # define CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS #endif #include Q_DECLARE_METATYPE(GpgME::Subkey) using namespace Kleo; class SubKeysWidget::Private { public: Private(SubKeysWidget *q) : q(q) { ui.setupUi(q); ui.subkeysTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.subkeysTree, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { tableContextMenuRequested(p); }); } GpgME::Key key; Ui::SubKeysWidget ui; void tableContextMenuRequested(const QPoint &p); private: SubKeysWidget *const q; }; void SubKeysWidget::Private::tableContextMenuRequested(const QPoint &p) { auto item = ui.subkeysTree->itemAt(p); if (!item) { return; } const auto subkey = item->data(0, Qt::UserRole).value(); QMenu *menu = new QMenu(q); connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); bool hasActions = false; #ifdef CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS if (subkey.parent().protocol() == GpgME::OpenPGP && subkey.parent().hasSecret()) { hasActions = true; menu->addAction(i18n("Change Expiry Date..."), q, [this, subkey]() { auto cmd = new Kleo::Commands::ChangeExpiryCommand(subkey.parent()); if (subkey.keyID() != key.keyID()) { // do not set the primary key as subkey cmd->setSubkey(subkey); } ui.subkeysTree->setEnabled(false); connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); key.update(); q->setKey(key); }); cmd->setParentWidget(q); cmd->start(); } ); } #endif // CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS #ifdef GPGME_HAS_EXPORT_FLAGS if (subkey.parent().protocol() == GpgME::OpenPGP && subkey.canAuthenticate()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export OpenSSH key"), q, [this, subkey]() { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(subkey, static_cast (GpgME::Context::ExportSSH)); dlg->exec(); }); } #endif // GPGME_HAS_EXPORT_FLAGS if (!subkey.isSecret()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-import")), i18n("Restore printed backup"), q, [this, subkey] () { auto cmd = new Kleo::Commands::ImportPaperKeyCommand(subkey.parent()); ui.subkeysTree->setEnabled(false); connect(cmd, &Kleo::Commands::ImportPaperKeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } if (subkey.isSecret() && Kleo::Commands::KeyToCardCommand::supported()) { const auto cards = SmartCard::ReaderStatus::instance()->getCards(); - if (cards.size() && cards[0]->appType() == SmartCard::Card::OpenPGPApplication) { + if (cards.size() && cards[0]->appName() == SmartCard::OpenPGPCard::AppName) { const auto card = cards[0]; if (!subkey.cardSerialNumber() || card->serialNumber() != subkey.cardSerialNumber()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), i18n("Transfer to smartcard"), q, [this, subkey, card]() { auto cmd = new Kleo::Commands::KeyToCardCommand(subkey, card->serialNumber()); ui.subkeysTree->setEnabled(false); connect(cmd, &Kleo::Commands::KeyToCardCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } } } if (hasActions) { menu->popup(ui.subkeysTree->viewport()->mapToGlobal(p)); } else { delete menu; } } SubKeysWidget::SubKeysWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } SubKeysWidget::~SubKeysWidget() { } void SubKeysWidget::setKey(const GpgME::Key &key) { d->key = key; const auto currentItem = d->ui.subkeysTree->currentItem(); const QByteArray selectedKeyFingerprint = currentItem ? QByteArray(currentItem->data(0, Qt::UserRole).value().fingerprint()) : QByteArray(); d->ui.subkeysTree->clear(); for (const auto &subkey : key.subkeys()) { auto item = new QTreeWidgetItem(); item->setData(0, Qt::DisplayRole, Formatting::prettyID(subkey.keyID())); item->setData(0, Qt::UserRole, QVariant::fromValue(subkey)); item->setData(1, Qt::DisplayRole, Kleo::Formatting::type(subkey)); item->setData(2, Qt::DisplayRole, Kleo::Formatting::creationDateString(subkey)); item->setData(3, Qt::DisplayRole, Kleo::Formatting::expirationDateString(subkey)); item->setData(4, Qt::DisplayRole, Kleo::Formatting::validityShort(subkey)); switch (subkey.publicKeyAlgorithm()) { case GpgME::Subkey::AlgoECDSA: case GpgME::Subkey::AlgoEDDSA: case GpgME::Subkey::AlgoECDH: item->setData(5, Qt::DisplayRole, QString::fromStdString(subkey.algoName())); break; default: item->setData(5, Qt::DisplayRole, QString::number(subkey.length())); } item->setData(6, Qt::DisplayRole, Kleo::Formatting::usageString(subkey)); item->setData(7, Qt::DisplayRole, subkey.keyID() == key.keyID() ? QStringLiteral("✓") : QString()); d->ui.subkeysTree->addTopLevelItem(item); if (subkey.fingerprint() == selectedKeyFingerprint) { d->ui.subkeysTree->setCurrentItem(item); } } const auto subkey = key.subkey(0); if (const char *card = subkey.cardSerialNumber()) { d->ui.stored->setText(i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { d->ui.stored->setText(i18nc("stored...", "on this computer")); } d->ui.subkeysTree->resizeColumnToContents(0); } GpgME::Key SubKeysWidget::key() const { return d->key; } SubKeysDialog::SubKeysDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Subkeys Details")); auto l = new QVBoxLayout(this); l->addWidget(new SubKeysWidget(this)); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::clicked, this, &QDialog::accept); l->addWidget(bbox); readConfig(); } SubKeysDialog::~SubKeysDialog() { writeConfig(); } void SubKeysDialog::readConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "SubKeysDialog"); const QSize size = dialog.readEntry("Size", QSize(820, 280)); if (size.isValid()) { resize(size); } } void SubKeysDialog::writeConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "SubKeysDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } void SubKeysDialog::setKey(const GpgME::Key &key) { auto w = findChild(); Q_ASSERT(w); w->setKey(key); } GpgME::Key SubKeysDialog::key() const { auto w = findChild(); Q_ASSERT(w); return w->key(); } diff --git a/src/smartcard/card.cpp b/src/smartcard/card.cpp index 660a9f081..eac09fc2d 100644 --- a/src/smartcard/card.cpp +++ b/src/smartcard/card.cpp @@ -1,128 +1,128 @@ /* 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" using namespace Kleo; using namespace Kleo::SmartCard; Card::Card(): mCanLearn(false), mHasNullPin(false), mStatus(Status::NoCard), - mAppType(UnknownApplication), - mAppVersion(-1) { + mAppVersion(-1) +{ } 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; } -Card::AppType Card::appType() const +std::string Card::appName() const { - return mAppType; + return mAppName; } -void Card::setAppType(AppType t) +void Card::setAppName(const std::string &name) { - mAppType = t; + mAppName = name; } void Card::setAppVersion(int version) { mAppVersion = version; } int Card::appVersion() const { return mAppVersion; } std::vector Card::pinStates() const { return mPinStates; } void Card::setPinStates(const std::vector &pinStates) { mPinStates = pinStates; } void Card::setSlot(int slot) { mSlot = slot; } int Card::slot() const { return mSlot; } bool Card::hasNullPin() const { return mHasNullPin; } void Card::setHasNullPin(bool value) { mHasNullPin = value; } bool Card::canLearnKeys() const { return mCanLearn; } void Card::setCanLearnKeys(bool value) { mCanLearn = value; } bool Card::operator == (const Card& other) const { return mStatus == other.status() && mSerialNumber == other.serialNumber() - && mAppType == other.appType() + && mAppName == other.appName() && mAppVersion == other.appVersion() && mPinStates == other.pinStates() && mSlot == other.slot() && mCanLearn == other.canLearnKeys() && mHasNullPin == other.hasNullPin(); } bool Card::operator != (const Card& other) const { return !operator==(other); } void Card::setErrorMsg(const QString &msg) { mErrMsg = msg; } QString Card::errorMsg() const { return mErrMsg; } diff --git a/src/smartcard/card.h b/src/smartcard/card.h index 75b503ad9..6785a52c1 100644 --- a/src/smartcard/card.h +++ b/src/smartcard/card.h @@ -1,107 +1,95 @@ #ifndef SMARTCARD_CARD_H #define SMARTCARD_CARD_H /* 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 #include #include namespace Kleo { namespace SmartCard { /** Class to work with Smartcards or other Hardware tokens. */ class Card { public: - enum AppType { - UnknownApplication, - OpenPGPApplication, - PivApplication, - NksApplication, - P15Application, - DinSigApplication, - GeldkarteApplication, - - NumAppTypes - }; - 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; virtual void setSerialNumber(const std::string &sn); std::string serialNumber() const; - AppType appType() const; - void setAppType(AppType type); + std::string appName() const; + void setAppName(const std::string &name); void setAppVersion(int version); int appVersion() const; std::vector pinStates() const; void setPinStates(const std::vector &pinStates); void setSlot(int slot); int slot() const; bool hasNullPin() const; void setHasNullPin(bool value); bool canLearnKeys() const; void setCanLearnKeys(bool value); QString errorMsg() const; void setErrorMsg(const QString &msg); private: bool mCanLearn; bool mHasNullPin; Status mStatus; std::string mSerialNumber; - AppType mAppType; + std::string mAppName; int mAppVersion; std::vector mPinStates; int mSlot; QString mErrMsg; }; } // namespace Smartcard } // namespace Kleopatra #endif // SMARTCARD_CARD_H diff --git a/src/smartcard/netkeycard.cpp b/src/smartcard/netkeycard.cpp index ed088c929..ccccac2ae 100644 --- a/src/smartcard/netkeycard.cpp +++ b/src/smartcard/netkeycard.cpp @@ -1,106 +1,109 @@ /* 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 "kleopatra_debug.h" #include #include #include #include #include using namespace Kleo; using namespace Kleo::SmartCard; +// static +const std::string NetKeyCard::AppName = "nks"; + namespace { static std::string parse_keypairinfo(const std::string &kpi) { static const char hexchars[] = "0123456789abcdefABCDEF"; return '&' + kpi.substr(0, kpi.find_first_not_of(hexchars)); } static GpgME::Key parse_keypairinfo_and_lookup_key(GpgME::Context *ctx, const std::string &kpi) { if (!ctx) { return GpgME::Key(); } const std::string pattern = parse_keypairinfo(kpi); qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: pattern=" << pattern.c_str(); if (const auto err = ctx->startKeyListing(pattern.c_str())) { qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: startKeyListing failed:" << err.asString(); return GpgME::Key(); } GpgME::Error e; const auto key = ctx->nextKey(e); ctx->endKeyListing(); qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: e=" << e.code() << "; key.isNull()" << key.isNull(); return key; } } // namespace NetKeyCard::NetKeyCard() { - setAppType(Card::NksApplication); + setAppName(AppName); } void NetKeyCard::setKeyPairInfo(const std::vector &infos) { // check that any of the keys are new const std::unique_ptr klc(GpgME::Context::createForProtocol(GpgME::CMS)); if (!klc.get()) { return; } klc->setKeyListMode(GpgME::Ephemeral); klc->addKeyListMode(GpgME::Validate); setCanLearnKeys(false); mKeys.clear(); for (const auto &info: infos) { const auto key = parse_keypairinfo_and_lookup_key(klc.get(), info); if (key.isNull()) { setCanLearnKeys(true); } mKeys.push_back(key); } } // 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; } std::vector NetKeyCard::keys() const { return mKeys; } diff --git a/src/smartcard/netkeycard.h b/src/smartcard/netkeycard.h index e14184323..c850b8c85 100644 --- a/src/smartcard/netkeycard.h +++ b/src/smartcard/netkeycard.h @@ -1,40 +1,42 @@ #ifndef SMARTCARD_NETKEYCARD_H #define SMARTCARD_NETKEYCARD_H -/* smartcard/openpgpcard.h +/* smartcard/netkeycard.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "card.h" #include namespace Kleo { namespace SmartCard { -/** Class to work with OpenPGP Smartcards or compatible tokens */ +/** Class to work with NetKey smartcards or compatible tokens */ class NetKeyCard: public Card { public: - NetKeyCard (); + NetKeyCard(); - void setKeyPairInfo (const std::vector &infos); + static const std::string AppName; + + void setKeyPairInfo(const std::vector &infos); bool hasSigGNullPin() const; bool hasNKSNullPin() const; std::vector keys() const; private: std::vector mKeys; }; } // namespace Smartcard } // namespace Kleopatra #endif // SMARTCARD_CARD_H diff --git a/src/smartcard/openpgpcard.cpp b/src/smartcard/openpgpcard.cpp index 2814d27ab..820ddb668 100644 --- a/src/smartcard/openpgpcard.cpp +++ b/src/smartcard/openpgpcard.cpp @@ -1,196 +1,199 @@ /* 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-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 "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; +// static +const std::string OpenPGPCard::AppName = "openpgp"; + OpenPGPCard::OpenPGPCard() { - setAppType(Card::OpenPGPApplication); + setAppName(AppName); } OpenPGPCard::OpenPGPCard(const std::string &serialno): OpenPGPCard() { setSerialNumber(serialno); } // 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"); } std::string OpenPGPCard::sigFpr() const { return mMetaInfo.value("SIGKEY-FPR"); } std::string OpenPGPCard::encFpr() const { return mMetaInfo.value("ENCKEY-FPR"); } std::string OpenPGPCard::authFpr() const { return mMetaInfo.value("AUTHKEY-FPR"); } void OpenPGPCard::setKeyPairInfo(const std::vector< std::pair > &infos) { qCDebug(KLEOPATRA_LOG) << "Card" << serialNumber().c_str() << "info:"; for (const auto &pair: infos) { qCDebug(KLEOPATRA_LOG) << pair.first.c_str() << ":" << pair.second.c_str(); if (pair.first == "KEY-FPR" || pair.first == "KEY-TIME") { // Key fpr and key time need to be distinguished, the number // of the key decides the usage. const auto values = QString::fromStdString(pair.second).split(QLatin1Char(' ')); if (values.size() < 2) { qCWarning(KLEOPATRA_LOG) << "Invalid entry."; setStatus(Card::CardError); continue; } const auto usage = values[0]; const auto fpr = values[1].toStdString(); if (usage == QLatin1Char('1')) { mMetaInfo.insert(std::string("SIG") + pair.first, fpr); } else if (usage == QLatin1Char('2')) { mMetaInfo.insert(std::string("ENC") + pair.first, fpr); } else if (usage == QLatin1Char('3')) { mMetaInfo.insert(std::string("AUTH") + pair.first, fpr); } else { // Maybe more keyslots in the future? qCDebug(KLEOPATRA_LOG) << "Unhandled keyslot"; } } else if (pair.first == "KEYPAIRINFO") { // Fun, same as above but the other way around. const auto values = QString::fromStdString(pair.second).split(QLatin1Char(' ')); if (values.size() < 2) { qCWarning(KLEOPATRA_LOG) << "Invalid entry."; setStatus(Card::CardError); continue; } const auto usage = values[1]; const auto grip = values[0].toStdString(); if (usage == QLatin1String("OPENPGP.1")) { mMetaInfo.insert(std::string("SIG") + pair.first, grip); } else if (usage == QLatin1String("OPENPGP.2")) { mMetaInfo.insert(std::string("ENC") + pair.first, grip); } else if (usage == QLatin1String("OPENPGP.3")) { mMetaInfo.insert(std::string("AUTH") + pair.first, grip); } else { // Maybe more keyslots in the future? qCDebug(KLEOPATRA_LOG) << "Unhandled keyslot"; } } else { mMetaInfo.insert(pair.first, pair.second); } } } void OpenPGPCard::setSerialNumber(const std::string &serialno) { char version_buffer[6]; const char *version = ""; Card::setSerialNumber(serialno); const bool isProperOpenPGPCardSerialNumber = serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401"; if (isProperOpenPGPCardSerialNumber) { /* Reformat the version number to be better human readable. */ const char *string = serialno.c_str(); char *p = version_buffer; if (string[12] != '0') { *p++ = string[12]; } *p++ = string[13]; *p++ = '.'; if (string[14] != '0') { *p++ = string[14]; } *p++ = string[15]; *p++ = '\0'; version = version_buffer; } mIsV2 = !((*version == '1' || *version == '0') && version[1] == '.'); mCardVersion = version; } bool OpenPGPCard::operator == (const Card& rhs) const { const OpenPGPCard *other = dynamic_cast(&rhs); if (!other) { return false; } return Card::operator ==(rhs) && sigFpr() == other->sigFpr() && encFpr() == other->encFpr() && authFpr() == other->authFpr() && manufacturer() == other->manufacturer() && cardVersion() == other->cardVersion() && cardHolder() == other->cardHolder() && pubkeyUrl() == other->pubkeyUrl(); } void OpenPGPCard::setManufacturer(const std::string &manufacturer) { mManufacturer = manufacturer; } std::string OpenPGPCard::manufacturer() const { return mManufacturer; } std::string OpenPGPCard::cardVersion() const { return mCardVersion; } std::string OpenPGPCard::cardHolder() const { auto list = QString::fromStdString(mMetaInfo.value("DISP-NAME")).split(QStringLiteral("<<")); std::reverse(list.begin(), list.end()); return list.join(QLatin1Char(' ')).toStdString(); } std::string OpenPGPCard::pubkeyUrl() const { return mMetaInfo.value("PUBKEY-URL"); } diff --git a/src/smartcard/openpgpcard.h b/src/smartcard/openpgpcard.h index 2cb227252..56cb8fa11 100644 --- a/src/smartcard/openpgpcard.h +++ b/src/smartcard/openpgpcard.h @@ -1,57 +1,59 @@ #ifndef SMARTCARD_OPENPGPCARD_H #define SMARTCARD_OPENPGPCARD_H /* smartcard/openpgpcard.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 #include "card.h" namespace Kleo { namespace SmartCard { -/** Class to work with OpenPGP Smartcards or compatible tokens */ +/** Class to work with OpenPGP smartcards or compatible tokens */ class OpenPGPCard: public Card { public: - OpenPGPCard (); - OpenPGPCard (const std::string &serialno); + OpenPGPCard(); + OpenPGPCard(const std::string &serialno); + + static const std::string AppName; static std::string pinKeyRef(); static std::string adminPinKeyRef(); static std::string resetCodeKeyRef(); void setSerialNumber(const std::string &sn) override; std::string encFpr() const; std::string sigFpr() const; std::string authFpr() const; void setKeyPairInfo (const std::vector< std::pair > &infos); bool operator == (const Card& other) const override; void setManufacturer(const std::string &manufacturer); std::string manufacturer() const; std::string cardVersion() const; std::string cardHolder() const; std::string pubkeyUrl() const; private: bool mIsV2 = false; std::string mCardVersion; QMap mMetaInfo; std::string mManufacturer; }; } // namespace Smartcard } // namespace Kleopatra #endif // SMARTCARD_CARD_H diff --git a/src/smartcard/pivcard.cpp b/src/smartcard/pivcard.cpp index ee5119762..5e56c85bd 100644 --- a/src/smartcard/pivcard.cpp +++ b/src/smartcard/pivcard.cpp @@ -1,192 +1,195 @@ /* smartcard/pivcard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "pivcard.h" #include "keypairinfo.h" #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; +// static +const std::string PIVCard::AppName = "piv"; + PIVCard::PIVCard() { - setAppType(Card::PivApplication); + setAppName(AppName); } PIVCard::PIVCard(const std::string &serialno): PIVCard() { setSerialNumber(serialno); } // static std::string PIVCard::pivAuthenticationKeyRef() { return std::string("PIV.9A"); } // static std::string PIVCard::cardAuthenticationKeyRef() { return std::string("PIV.9E"); } // static std::string PIVCard::digitalSignatureKeyRef() { return std::string("PIV.9C"); } // static std::string PIVCard::keyManagementKeyRef() { return std::string("PIV.9D"); } // static std::string PIVCard::pinKeyRef() { return std::string("PIV.80"); } // static std::string PIVCard::pukKeyRef() { return std::string("PIV.81"); } // static const std::vector & PIVCard::supportedKeys() { static const std::vector keyRefs = { PIVCard::pivAuthenticationKeyRef(), PIVCard::cardAuthenticationKeyRef(), PIVCard::digitalSignatureKeyRef(), PIVCard::keyManagementKeyRef() }; return keyRefs; } // static QString PIVCard::keyDisplayName(const std::string &keyRef) { static const QMap displayNames = { { PIVCard::pivAuthenticationKeyRef(), i18n("PIV Authentication Key") }, { PIVCard::cardAuthenticationKeyRef(), i18n("Card Authentication Key") }, { PIVCard::digitalSignatureKeyRef(), i18n("Digital Signature Key") }, { PIVCard::keyManagementKeyRef(), i18n("Key Management Key") }, }; return displayNames.value(keyRef); } // static std::vector< std::pair > PIVCard::supportedAlgorithms(const std::string &keyRef) { if (keyRef == PIVCard::keyManagementKeyRef()) { return { { "rsa2048", i18n("RSA key transport (2048 bits)") }, { "nistp256", i18n("ECDH (Curve P-256)") }, { "nistp384", i18n("ECDH (Curve P-384)") } }; } else if (keyRef == PIVCard::digitalSignatureKeyRef()) { return { { "rsa2048", i18n("RSA (2048 bits)") }, { "nistp256", i18n("ECDSA (Curve P-256)") }, { "nistp384", i18n("ECDSA (Curve P-384)") } }; } // NIST SP 800-78-4 does not allow Curve P-384 for PIV Authentication key or Card Authentication key return { { "rsa2048", i18n("RSA (2048 bits)") }, { "nistp256", i18n("ECDSA (Curve P-256)") }, }; } std::string PIVCard::keyGrip(const std::string& keyRef) const { return mMetaInfo.value("KEYPAIRINFO-" + keyRef); } namespace { static int parseAppVersion(const std::string &s) { // s is a hex-encoded, unsigned int-packed version tuple bool ok; const auto appVersion = QByteArray::fromStdString(s).toUInt(&ok, 16); return ok ? appVersion : -1; } } void PIVCard::setCardInfo(const std::vector< std::pair > &infos) { qCDebug(KLEOPATRA_LOG) << "Card" << serialNumber().c_str() << "info:"; for (const auto &pair: infos) { qCDebug(KLEOPATRA_LOG) << pair.first.c_str() << ":" << pair.second.c_str(); if (pair.first == "APPVERSION") { setAppVersion(parseAppVersion(pair.second)); } else if (pair.first == "KEYPAIRINFO") { const KeyPairInfo info = KeyPairInfo::fromStatusLine(pair.second); if (info.grip.empty()) { qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(pair.second); setStatus(Card::CardError); continue; } mMetaInfo.insert("KEYPAIRINFO-" + info.keyRef, info.grip); } else { mMetaInfo.insert(pair.first, pair.second); } } } std::string PIVCard::displaySerialNumber() const { return mDisplaySerialNumber; } void PIVCard::setDisplaySerialNumber(const std::string &serialno) { mDisplaySerialNumber = serialno; } std::string PIVCard::keyAlgorithm(const std::string &keyRef) const { return mMetaInfo.value("KLEO-KEYALGO-" + keyRef); } void PIVCard::setKeyAlgorithm(const std::string &keyRef, const std::string &algorithm) { mMetaInfo.insert("KLEO-KEYALGO-" + keyRef, algorithm); } std::string PIVCard::certificateData(const std::string &keyRef) const { return mMetaInfo.value("KLEO-CERTIFICATE-" + keyRef); } void PIVCard::setCertificateData(const std::string &keyRef, const std::string &data) { mMetaInfo.insert("KLEO-CERTIFICATE-" + keyRef, data); } bool PIVCard::operator == (const Card& rhs) const { const PIVCard *other = dynamic_cast(&rhs); if (!other) { return false; } return Card::operator ==(rhs) && mMetaInfo == other->mMetaInfo; } diff --git a/src/smartcard/pivcard.h b/src/smartcard/pivcard.h index d941115cc..0e44b3e02 100644 --- a/src/smartcard/pivcard.h +++ b/src/smartcard/pivcard.h @@ -1,61 +1,63 @@ /* smartcard/pivcard.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SMARTCARD_PIVCARD_H #define SMARTCARD_PIVCARD_H #include "card.h" #include namespace Kleo { namespace SmartCard { /** Class to work with PIV smartcards or compatible tokens */ class PIVCard: public Card { public: - PIVCard (); - PIVCard (const std::string &serialno); + PIVCard(); + PIVCard(const std::string &serialno); + + static const std::string AppName; static std::string pivAuthenticationKeyRef(); static std::string cardAuthenticationKeyRef(); static std::string digitalSignatureKeyRef(); static std::string keyManagementKeyRef(); static std::string pinKeyRef(); static std::string pukKeyRef(); static const std::vector & supportedKeys(); static QString keyDisplayName(const std::string &keyRef); static std::vector< std::pair > supportedAlgorithms(const std::string &keyRef); std::string keyGrip(const std::string &keyRef) const; void setCardInfo (const std::vector< std::pair > &infos); std::string displaySerialNumber() const; void setDisplaySerialNumber(const std::string &sn); std::string keyAlgorithm(const std::string &keyRef) const; void setKeyAlgorithm(const std::string &keyRef, const std::string &algorithm); std::string certificateData(const std::string &keyRef) const; void setCertificateData(const std::string &keyRef, const std::string &data); bool operator == (const Card& other) const override; private: std::string mDisplaySerialNumber; QMap mMetaInfo; }; } // namespace Smartcard } // namespace Kleopatra #endif // SMARTCARD_PIVCARD_H diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index fb465294f..0368455e1 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,838 +1,813 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "readerstatus.h" #include "keypairinfo.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "openpgpcard.h" #include "netkeycard.h" #include "pivcard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/kdtoolsglobal.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace GpgME; static ReaderStatus *self = nullptr; #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) static const char *flags[] = { "NOCARD", "PRESENT", "ACTIVE", "USABLE", }; static_assert(sizeof flags / sizeof * flags == Card::_NumScdStates, ""); static const char *prettyFlags[] = { "NoCard", "CardPresent", "CardActive", "CardUsable", "CardError", }; static_assert(sizeof prettyFlags / sizeof * prettyFlags == Card::NumStates, ""); #if 0 We need this once we have support for multiple readers in scdaemons interface. static unsigned int parseFileName(const QString &fileName, bool *ok) { QRegExp rx(QLatin1String("reader_(\\d+)\\.status")); if (ok) { *ok = false; } if (rx.exactMatch(QFileInfo(fileName).fileName())) { return rx.cap(1).toUInt(ok, 10); } return 0; } #endif Q_DECLARE_METATYPE(GpgME::Error) namespace { static QDebug operator<<(QDebug s, const std::vector< std::pair > &v) { typedef std::pair pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n'; } return s << ')'; } -static const char *app_types[] = { - "_", // will hopefully never be used as an app-type :) - "openpgp", - "piv", - "nks", - "p15", - "dinsig", - "geldkarte", -}; -static_assert(sizeof app_types / sizeof * app_types == Card::NumAppTypes, ""); - -static Card::AppType parse_app_type(const std::string &s) -{ - qCDebug(KLEOPATRA_LOG) << "parse_app_type(" << s.c_str() << ")"; - const char **it = std::find_if(std::begin(app_types), std::end(app_types), - [&s](const char *type) { - return ::strcasecmp(s.c_str(), type) == 0; - }); - if (it == std::end(app_types)) { - qCDebug(KLEOPATRA_LOG) << "App type not found"; - return Card::UnknownApplication; - } - return static_cast(it - std::begin(app_types)); -} - static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } template static std::unique_ptr gpgagent_transact(std::shared_ptr &gpgAgent, const char *command, std::unique_ptr transaction, Error &err) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")"; err = gpgAgent->assuanTransact(command, std::move(transaction)); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "):" << QString::fromLocal8Bit(err.asString()); if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) { qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context"; gpgAgent.reset(); } return std::unique_ptr(); } std::unique_ptr t = gpgAgent->takeLastAssuanTransaction(); return std::unique_ptr(dynamic_cast(t.release())); } static std::unique_ptr gpgagent_default_transact(std::shared_ptr &gpgAgent, const char *command, Error &err) { return gpgagent_transact(gpgAgent, command, std::unique_ptr(new DefaultAssuanTransaction), err); } static const std::string gpgagent_data(std::shared_ptr gpgAgent, const char *command, Error &err) { const std::unique_ptr t = gpgagent_default_transact(gpgAgent, command, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): got" << QString::fromStdString(t->data()); return t->data(); } else { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): t == NULL"; return std::string(); } } static const std::vector< std::pair > gpgagent_statuslines(std::shared_ptr gpgAgent, const char *what, Error &err) { const std::unique_ptr t = gpgagent_default_transact(gpgAgent, what, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): got" << t->statusLines(); return t->statusLines(); } else { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): t == NULL"; return std::vector >(); } } static const std::string gpgagent_status(const std::shared_ptr &gpgAgent, const char *what, Error &err) { const auto lines = gpgagent_statuslines (gpgAgent, what, err); // The status is only the last attribute // e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO // it would only be FOO const char *p = strrchr(what, ' '); const char *needle = (p + 1) ? (p + 1) : what; for (const auto &pair: lines) { if (pair.first == needle) { return pair.second; } } return std::string(); } static const std::string scd_getattr_status(std::shared_ptr &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return gpgagent_status(gpgAgent, cmd.c_str(), err); } static const char * get_openpgp_card_manufacturer_from_serial_number(const std::string &serialno) { qCDebug(KLEOPATRA_LOG) << "get_openpgp_card_manufacturer_from_serial_number(" << serialno.c_str() << ")"; const bool isProperOpenPGPCardSerialNumber = serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401"; if (isProperOpenPGPCardSerialNumber) { const char *sn = serialno.c_str(); const int manufacturerId = xtoi_2(sn + 16)*256 + xtoi_2(sn + 18); switch (manufacturerId) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid"; case 0x0005: return "ZeitControl"; case 0x0006: return "Yubico"; case 0x0007: return "OpenKMS"; case 0x0008: return "LogoEmail"; case 0x002A: return "Magrathea"; case 0x1337: return "Warsaw Hackerspace"; case 0xF517: return "FSIJ"; /* 0x0000 and 0xFFFF are defined as test cards per spec, 0xFF00 to 0xFFFE are assigned for use with randomly created serial numbers. */ case 0x0000: case 0xffff: return "test card"; default: return (manufacturerId & 0xff00) == 0xff00 ? "unmanaged S/N range" : "unknown"; } } else { return "unknown"; } } static const std::string get_manufacturer(std::shared_ptr &gpgAgent, Error &err) { // The result of SCD GETATTR MANUFACTURER is the manufacturer ID as unsigned number // optionally followed by the name of the manufacturer, e.g. // 6 Yubico // 65534 unmanaged S/N range const auto manufacturerIdAndName = scd_getattr_status(gpgAgent, "MANUFACTURER", err); if (err.code()) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): Querying for attribute MANUFACTURER not yet supported; needs GnuPG 2.2.21+"; } else { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; } return std::string(); } const auto startOfManufacturerName = manufacturerIdAndName.find(' '); if (startOfManufacturerName == std::string::npos) { // only ID without manufacturer name return "unknown"; } const auto manufacturerName = manufacturerIdAndName.substr(startOfManufacturerName + 1); return manufacturerName; } static void handle_openpgp_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto ret = new OpenPGPCard(); ret->setSerialNumber(ci->serialNumber()); ret->setManufacturer(get_manufacturer(gpg_agent, err)); if (err.code()) { // fallback, e.g. if gpg does not yet support querying for the MANUFACTURER attribute ret->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code()) { ci->setStatus(Card::CardError); return; } ret->setKeyPairInfo(info); ci.reset(ret); } static void 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 = gpgagent_statuslines(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "readKeyPairInfoFromPIVCard(): Error on " << QString::fromStdString(command) << ":" << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; return; } for (const auto &pair: keyPairInfoLines) { if (pair.first == "KEYPAIRINFO") { const KeyPairInfo info = KeyPairInfo::fromStatusLine(pair.second); if (info.grip.empty()) { qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(pair.second); continue; } pivCard->setKeyAlgorithm(keyRef, info.algorithm); } else { qCWarning(KLEOPATRA_LOG) << "readKeyPairInfoFromPIVCard(): Unexpected status line on " << QString::fromStdString(command) << ":" << QString::fromStdString(pair.first) << QString::fromStdString(pair.second); } } } 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 = gpgagent_data(gpg_agent, command.c_str(), err); if (err && err.code() != GPG_ERR_NOT_FOUND) { qCWarning(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Error on " << QString::fromStdString(command) << ":" << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; 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(); pivCard->setSerialNumber(ci->serialNumber()); const auto displaySerialnumber = scd_getattr_status(gpg_agent, "$DISPSERIALNO", err); if (err) { qCWarning(KLEOPATRA_LOG) << "handle_piv_card(): Error on GETATTR $DISPSERIALNO:" << "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))"; } pivCard->setDisplaySerialNumber(err ? ci->serialNumber() : displaySerialnumber); const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } pivCard->setCardInfo(info); for (const std::string &keyRef : PIVCard::supportedKeys()) { if (!pivCard->keyGrip(keyRef).empty()) { readKeyPairInfoFromPIVCard(keyRef, pivCard, gpg_agent); readCertificateFromPIVCard(keyRef, pivCard, gpg_agent); } } ci.reset(pivCard); } static void handle_netkey_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto nkCard = new NetKeyCard(); nkCard->setSerialNumber(ci->serialNumber()); ci.reset(nkCard); ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err))); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "NKS-VERSION resulted in error" << err.asString(); ci->setErrorMsg(QStringLiteral ("NKS-VERSION failed: ") + QString::fromUtf8(err.asString())); return; } if (ci->appVersion() != 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 card, giving up. Version:" << ci->appVersion(); ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion())); return; } // the following only works for NKS v3... const auto chvStatus = QString::fromStdString( scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QLatin1Char(' ')); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "no CHV-STATUS" << err.asString(); ci->setErrorMsg(QStringLiteral ("CHV-Status failed: ") + QString::fromUtf8(err.asString())); return; } std::vector states; states.reserve(chvStatus.count()); // CHV Status for NKS v3 is // Pin1 (Normal pin) Pin2 (Normal PUK) // SigG1 SigG PUK. int num = 0; for (const auto &state: chvStatus) { const auto parsed = parse_pin_state (state); states.push_back(parsed); if (parsed == Card::NullPin) { if (num == 0) { ci->setHasNullPin(true); } } ++num; } nkCard->setPinStates(states); // check for keys to learn: const std::unique_ptr result = gpgagent_default_transact(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code() || !result.get()) { if (err) { ci->setErrorMsg(QString::fromLatin1(err.asString())); } else { ci->setErrorMsg(QStringLiteral("Invalid internal state. No result.")); } return; } const std::vector keyPairInfos = result->statusLine("KEYPAIRINFO"); if (keyPairInfos.empty()) { return; } nkCard->setKeyPairInfo(keyPairInfos); } static std::shared_ptr get_card_status(unsigned int slot, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << slot << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr (new Card()); if (slot != 0 || !gpg_agent) { // In the future scdaemon should support multiple slots but // not yet (2.1.18) return ci; } Error err; ci->setSerialNumber(gpgagent_status(gpg_agent, "SCD SERIALNO", err)); if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); return ci; } if (err.code()) { ci->setStatus(Card::CardError); return ci; } ci->setStatus(Card::CardPresent); - const auto verbatimType = scd_getattr_status(gpg_agent, "APPTYPE", err); - ci->setAppType(parse_app_type(verbatimType)); + const auto appName = scd_getattr_status(gpg_agent, "APPTYPE", err); + ci->setAppName(appName); if (err.code()) { return ci; } // Handle different card types - if (ci->appType() == Card::NksApplication) { + if (ci->appName() == NetKeyCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end"; handle_netkey_card(ci, gpg_agent); return ci; - } else if (ci->appType() == Card::OpenPGPApplication) { + } else if (ci->appName() == OpenPGPCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; handle_openpgp_card(ci, gpg_agent); return ci; - } else if (ci->appType() == Card::PivApplication) { + } else if (ci->appName() == PIVCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end"; handle_piv_card(ci, gpg_agent); return ci; } else { - qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << verbatimType.c_str(); + qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << appName.c_str(); return ci; } return ci; } static std::vector > update_cardinfo(std::shared_ptr &gpgAgent) { // Multiple smartcard readers are only supported internally by gnupg // but not by scdaemon (Status gnupg 2.1.18) // We still pretend that there can be multiple cards inserted // at once but we don't handle it yet. const auto ci = get_card_status(0, gpgAgent); return std::vector >(1, ci); } } // namespace struct Transaction { QByteArray command; QPointer receiver; const char *slot; AssuanTransaction* assuanTransaction; }; static const Transaction updateTransaction = { "__update__", nullptr, nullptr, nullptr }; static const Transaction quitTransaction = { "__quit__", nullptr, nullptr, nullptr }; namespace { class ReaderStatusThread : public QThread { Q_OBJECT public: explicit ReaderStatusThread(QObject *parent = nullptr) : QThread(parent), m_gnupgHomePath(Kleo::gnupgHomeDirectory()), m_transactions(1, updateTransaction) // force initial scan { connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished); } std::vector > cardInfos() const { const QMutexLocker locker(&m_mutex); return m_cardInfos; } Card::Status cardStatus(unsigned int slot) const { const QMutexLocker locker(&m_mutex); if (slot < m_cardInfos.size()) { return m_cardInfos[slot]->status(); } else { return Card::NoCard; } } void addTransaction(const Transaction &t) { const QMutexLocker locker(&m_mutex); m_transactions.push_back(t); m_waitForTransactions.wakeOne(); } Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); void anyCardCanLearnKeysChanged(bool); void cardChanged(unsigned int); void oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: void ping() { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()"; addTransaction(updateTransaction); } void stop() { const QMutexLocker locker(&m_mutex); m_transactions.push_front(quitTransaction); m_waitForTransactions.wakeOne(); } private Q_SLOTS: void slotOneTransactionFinished(const GpgME::Error &err) { std::list ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); Q_FOREACH (const Transaction &t, ft) if (t.receiver && t.slot && *t.slot) { QMetaObject::invokeMethod(t.receiver, t.slot, Qt::DirectConnection, Q_ARG(GpgME::Error, err)); } } private: void run() override { while (true) { std::shared_ptr gpgAgent; QByteArray command; bool nullSlot = false; AssuanTransaction* assuanTransaction = nullptr; std::list item; std::vector > oldCards; Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: command = item.front().command; nullSlot = !item.front().slot; // we take ownership of the assuan transaction std::swap(assuanTransaction, item.front().assuanTransaction); oldCards = m_cardInfos; } qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot; // now, let's see what we got: if (nullSlot && command == quitTransaction.command) { return; // quit } if ((nullSlot && command == updateTransaction.command)) { std::vector > newCards = update_cardinfo(gpgAgent); newCards.resize(std::max(newCards.size(), oldCards.size())); oldCards.resize(std::max(newCards.size(), oldCards.size())); KDAB_SYNCHRONIZED(m_mutex) m_cardInfos = newCards; std::vector >::const_iterator nit = newCards.begin(), nend = newCards.end(), oit = oldCards.begin(), oend = oldCards.end(); unsigned int idx = 0; bool anyLC = false; std::string firstCardWithNullPin; bool anyError = false; while (nit != nend && oit != oend) { const auto optr = (*oit).get(); const auto nptr = (*nit).get(); if ((optr && !nptr) || (!optr && nptr) || (optr && nptr && *optr != *nptr)) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: slot" << idx << ": card Changed"; Q_EMIT cardChanged(idx); } if ((*nit)->canLearnKeys()) { anyLC = true; } if ((*nit)->hasNullPin() && firstCardWithNullPin.empty()) { firstCardWithNullPin = (*nit)->serialNumber(); } if ((*nit)->status() == Card::CardError) { anyError = true; } ++nit; ++oit; ++idx; } Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin); Q_EMIT anyCardCanLearnKeysChanged(anyLC); if (anyError) { gpgAgent.reset(); } } else { GpgME::Error err; if (assuanTransaction) { (void)gpgagent_transact(gpgAgent, command.constData(), std::unique_ptr(assuanTransaction), err); } else { (void)gpgagent_default_transact(gpgAgent, command.constData(), err); } KDAB_SYNCHRONIZED(m_mutex) // splice 'item' into m_finishedTransactions: m_finishedTransactions.splice(m_finishedTransactions.end(), item); Q_EMIT oneTransactionFinished(err); } } } private: mutable QMutex m_mutex; QWaitCondition m_waitForTransactions; const QString m_gnupgHomePath; // protected by m_mutex: std::vector > m_cardInfos; std::list m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq), q(qq), watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType("Kleo::SmartCard::Card::Status"); qRegisterMetaType("GpgME::Error"); watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged, q, &ReaderStatus::firstCardWithNullPinChanged); connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } ~Private() { 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(); } bool anyCardCanLearnKeysImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->canLearnKeys(); }); } private: FileSystemWatcher watcher; }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent), d(new Private(this)) { self = this; qRegisterMetaType("std::string"); } ReaderStatus::~ReaderStatus() { self = nullptr; } // slot void ReaderStatus::startMonitoring() { d->start(); } // static ReaderStatus *ReaderStatus::mutableInstance() { return self; } // static const ReaderStatus *ReaderStatus::instance() { return self; } Card::Status ReaderStatus::cardStatus(unsigned int slot) const { return d->cardStatus(slot); } std::string ReaderStatus::firstCardWithNullPin() const { return d->firstCardWithNullPinImpl(); } bool ReaderStatus::anyCardCanLearnKeys() const { return d->anyCardCanLearnKeysImpl(); } void ReaderStatus::startSimpleTransaction(const QByteArray &command, QObject *receiver, const char *slot) { const Transaction t = { command, receiver, slot, nullptr }; d->addTransaction(t); } void ReaderStatus::startTransaction(const QByteArray &command, QObject *receiver, const char *slot, std::unique_ptr transaction) { const Transaction t = { command, receiver, slot, transaction.release() }; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } std::vector > ReaderStatus::getCards() const { return d->cardInfos(); } #include "readerstatus.moc" diff --git a/src/view/smartcardwidget.cpp b/src/view/smartcardwidget.cpp index fe7b705a0..44b70b487 100644 --- a/src/view/smartcardwidget.cpp +++ b/src/view/smartcardwidget.cpp @@ -1,150 +1,150 @@ /* view/smartcardwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "smartcardwidget.h" #include "smartcard/readerstatus.h" #include "smartcard/openpgpcard.h" #include "smartcard/netkeycard.h" #include "smartcard/pivcard.h" #include "view/pgpcardwidget.h" #include "view/netkeywidget.h" #include "view/pivcardwidget.h" #include "kleopatra_debug.h" #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::SmartCard; namespace { class PlaceHolderWidget: public QWidget { Q_OBJECT public: explicit PlaceHolderWidget(QWidget *parent = nullptr) : QWidget(parent) { auto lay = new QVBoxLayout; lay->addStretch(-1); const QStringList supported = QStringList() << QStringLiteral("OpenPGP v2.0 - v3.3") << QStringLiteral("Gnuk") << QStringLiteral("NetKey v3") << QStringLiteral("PIV"); lay->addWidget(new QLabel(QStringLiteral("\t\t

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

"), this)); lay->addSpacing(10); lay->addWidget(new QLabel(QStringLiteral("\t\t") + i18n("Kleopatra currently supports the following card types:") + QStringLiteral("
  • ") + supported.join(QLatin1String("
  • ")) + QStringLiteral("
"), this)); lay->addSpacing(10); lay->addWidget(new QLabel(i18n("Refresh the view (F5) to update the smartcard status."), this)); lay->addStretch(-1); auto hLay = new QHBoxLayout(this); hLay->addStretch(-1); hLay->addLayout(lay); hLay->addStretch(-1); lay->addStretch(-1); } }; } // namespace class SmartCardWidget::Private { public: Private(SmartCardWidget *qq) : q(qq) { QPushButton *backBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("Back")); QHBoxLayout *backH = new QHBoxLayout; backH->addWidget(backBtn); backH->addWidget(new QLabel(QStringLiteral("

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

"))); backH->addStretch(-1); QVBoxLayout *vLay = new QVBoxLayout(q); connect(backBtn, &QPushButton::clicked, q, [this] () {Q_EMIT q->backRequested();}); vLay->addLayout(backH); mStack = new QStackedWidget; vLay->addWidget(mStack); mPGPCardWidget = new PGPCardWidget(q); mStack->addWidget(mPGPCardWidget); mNetKeyWidget = new NetKeyWidget(q); mStack->addWidget(mNetKeyWidget); mPIVCardWidget = new PIVCardWidget(q); mStack->addWidget(mPIVCardWidget); mPlaceHolderWidget = new PlaceHolderWidget(q); mStack->addWidget(mPlaceHolderWidget); mStack->setCurrentWidget(mPlaceHolderWidget); connect (ReaderStatus::instance(), &ReaderStatus::cardChanged, q, [this] (unsigned int /*slot*/) { const auto cards = ReaderStatus::instance()->getCards(); if (!cards.size()) { setCard(std::shared_ptr(new Card())); } else { // No support for multiple reader / cards currently setCard(cards[0]); } }); } void setCard(std::shared_ptr card) { - if (card->appType() == Card::OpenPGPApplication) { + if (card->appName() == SmartCard::OpenPGPCard::AppName) { mPGPCardWidget->setCard(static_cast (card.get())); mStack->setCurrentWidget(mPGPCardWidget); - } else if (card->appType() == Card::NksApplication) { + } else if (card->appName() == SmartCard::NetKeyCard::AppName) { mNetKeyWidget->setCard(static_cast (card.get())); mStack->setCurrentWidget(mNetKeyWidget); - } else if (card->appType() == Card::PivApplication) { + } else if (card->appName() == SmartCard::PIVCard::AppName) { mPIVCardWidget->setCard(static_cast (card.get())); mStack->setCurrentWidget(mPIVCardWidget); } else { mStack->setCurrentWidget(mPlaceHolderWidget); } } private: SmartCardWidget *const q; NetKeyWidget *mNetKeyWidget; PGPCardWidget *mPGPCardWidget; PIVCardWidget *mPIVCardWidget; PlaceHolderWidget *mPlaceHolderWidget; QStackedWidget *mStack; }; SmartCardWidget::SmartCardWidget(QWidget *parent): QWidget(parent), d(new Private(this)) { } void SmartCardWidget::reload() { ReaderStatus::mutableInstance()->updateStatus(); } #include "smartcardwidget.moc"