diff --git a/src/commands/changepincommand.cpp b/src/commands/changepincommand.cpp index fec5ac603..7c440a4cb 100644 --- a/src/commands/changepincommand.cpp +++ b/src/commands/changepincommand.cpp @@ -1,185 +1,187 @@ /* commands/changepincommand.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 "changepincommand.h" #include "cardcommand_p.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class ChangePinCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::ChangePinCommand; ChangePinCommand *q_func() const { return static_cast(q); } public: - explicit Private(ChangePinCommand *qq, const std::string &serialNumber, QWidget *p); + explicit Private(ChangePinCommand *qq, const std::string &serialNumber, const std::string &appName, QWidget *p); ~Private(); void init(); private: void slotResult(const Error &err); private: void changePin(); private: + std::string appName; std::string keyRef; }; ChangePinCommand::Private *ChangePinCommand::d_func() { return static_cast(d.get()); } const ChangePinCommand::Private *ChangePinCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() -ChangePinCommand::Private::Private(ChangePinCommand *qq, const std::string &serialNumber, QWidget *p) +ChangePinCommand::Private::Private(ChangePinCommand *qq, const std::string &serialNumber, const std::string &appName_, QWidget *p) : CardCommand::Private(qq, serialNumber, p) + , appName(appName_) { } ChangePinCommand::Private::~Private() { qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::Private::~Private()"; } -ChangePinCommand::ChangePinCommand(const std::string &serialNumber, QWidget *p) - : CardCommand(new Private(this, serialNumber, p)) +ChangePinCommand::ChangePinCommand(const std::string &serialNumber, const std::string &appName, QWidget *p) + : CardCommand(new Private(this, serialNumber, appName, p)) { d->init(); } void ChangePinCommand::Private::init() { } ChangePinCommand::~ChangePinCommand() { qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::~ChangePinCommand()"; } void ChangePinCommand::setKeyRef(const std::string &keyRef) { d->keyRef = keyRef; } void ChangePinCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::doStart()"; d->changePin(); } void ChangePinCommand::doCancel() { } void ChangePinCommand::Private::changePin() { qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::changePin()"; - const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); + const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } QByteArrayList command; command << "SCD PASSWD"; if (card->appName() == OpenPGPCard::AppName && keyRef == OpenPGPCard::resetCodeKeyRef()) { // special handling for setting/changing the Reset Code of OpenPGP v2 cards const auto pgpCard = std::dynamic_pointer_cast(card); if (!pgpCard) { error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } const std::string firstTwoVersionChars = pgpCard->cardVersion().substr(0, 2); const bool isVersion2 = !(firstTwoVersionChars == "1." || firstTwoVersionChars == "0."); if (isVersion2) { command << "--reset"; } } command << QByteArray::fromStdString(keyRef); ReaderStatus::mutableInstance()->startSimpleTransaction(card, command.join(' '), q, "slotResult"); } namespace { static QString errorMessage(const std::string &keyRef, const QString &errorText) { // see cmd_passwd() in gpg-card.c if (keyRef == PIVCard::pukKeyRef()) { return i18nc("@info", "Changing the PUK failed: %1", errorText); } if (keyRef == OpenPGPCard::adminPinKeyRef()) { return i18nc("@info", "Changing the Admin PIN failed: %1", errorText); } if (keyRef == OpenPGPCard::resetCodeKeyRef()) { return i18nc("@info", "Changing the Reset Code failed: %1", errorText); } return i18nc("@info", "Changing the PIN failed: %1", errorText); } static QString successMessage(const std::string &keyRef) { // see cmd_passwd() in gpg-card.c if (keyRef == PIVCard::pukKeyRef()) { return i18nc("@info", "PUK successfully changed."); } if (keyRef == OpenPGPCard::adminPinKeyRef()) { return i18nc("@info", "Admin PIN changed successfully."); } if (keyRef == OpenPGPCard::resetCodeKeyRef()) { return i18nc("@info", "Reset Code changed successfully."); } return i18nc("@info", "PIN changed successfully."); } } void ChangePinCommand::Private::slotResult(const GpgME::Error& err) { qCDebug(KLEOPATRA_LOG) << "ChangePinCommand::slotResult():" << err.asString() << "(" << err.code() << ")"; if (err) { error(errorMessage(keyRef, QString::fromLatin1(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { information(successMessage(keyRef), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } finished(); } #undef d #undef q #include "moc_changepincommand.cpp" diff --git a/src/commands/changepincommand.h b/src/commands/changepincommand.h index 164c56a9e..6a46e38dd 100644 --- a/src/commands/changepincommand.h +++ b/src/commands/changepincommand.h @@ -1,48 +1,48 @@ /* commands/changepincommand.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 __KLEOPATRA_COMMMANDS_CHANGEPINCOMMAND_H__ #define __KLEOPATRA_COMMMANDS_CHANGEPINCOMMAND_H__ #include "cardcommand.h" namespace GpgME { class Error; } namespace Kleo { namespace Commands { class ChangePinCommand : public CardCommand { Q_OBJECT public: - explicit ChangePinCommand(const std::string &serialNumber, QWidget *parent); + explicit ChangePinCommand(const std::string &serialNumber, const std::string &appName, QWidget *parent); ~ChangePinCommand() override; void setKeyRef(const std::string &keyRef); private: void doStart() override; void doCancel() override; private: class Private; inline Private *d_func(); inline const Private *d_func() const; Q_PRIVATE_SLOT(d_func(), void slotResult(GpgME::Error)) }; } // namespace Commands } // namespace Kleo #endif // __KLEOPATRA_COMMMANDS_CHANGEPINCOMMAND_H__ diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp index 61f457025..a28c1270b 100644 --- a/src/commands/keytocardcommand.cpp +++ b/src/commands/keytocardcommand.cpp @@ -1,452 +1,457 @@ /* 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); + explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey, const std::string &serialNumber, const std::string &appName); + explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName); ~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: + std::string appName; 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_) + const std::string &serialNumber, + const std::string &appName_ + ) + : CardCommand::Private(qq, serialNumber, nullptr) + , appName(appName_) + , subkey(subkey_) { } -KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialno) - : CardCommand::Private(qq, serialno, nullptr) +KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName_) + : CardCommand::Private(qq, serialNumber, nullptr) + , appName(appName_) , cardSlot(slot) { } KeyToCardCommand::Private::~Private() { } void KeyToCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()"; - const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); + const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (card->appName() == SmartCard::OpenPGPCard::AppName) { startKeyToOpenPGPCard(); } else if (card->appName() == SmartCard::PIVCard::AppName) { startKeyToPIVCard(); } 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(pgpCard, 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(pivCard, 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 GpgME::Subkey &key, const std::string &serialNumber, const std::string &appName) + : CardCommand(new Private(this, key, serialNumber, appName)) { } -KeyToCardCommand::KeyToCardCommand(const std::string& cardSlot, const std::string &serialno) - : CardCommand(new Private(this, cardSlot, serialno)) +KeyToCardCommand::KeyToCardCommand(const std::string& cardSlot, const std::string &serialNumber, const std::string &appName) + : CardCommand(new Private(this, cardSlot, serialNumber, appName)) { } 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(card, 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/commands/keytocardcommand.h b/src/commands/keytocardcommand.h index e1898634f..7ba6f90a6 100644 --- a/src/commands/keytocardcommand.h +++ b/src/commands/keytocardcommand.h @@ -1,52 +1,52 @@ /* commands/keytocardcommand.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-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ #define __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ #include #include namespace Kleo { namespace Commands { class KeyToCardCommand : public CardCommand { Q_OBJECT public: - KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialno); - KeyToCardCommand(const std::string& cardSlot, const std::string &serialno); + KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialNumber, const std::string &appName); + KeyToCardCommand(const std::string &cardSlot, const std::string &serialNumber, const std::string &appName); ~KeyToCardCommand() override; static bool supported(); public Q_SLOTS: void keyToOpenPGPCardDone(const GpgME::Error &err); void keyToPIVCardDone(const GpgME::Error &err); void deleteDone(const GpgME::Error &err); private: void doStart() override; void doCancel() override; private: class Private; inline Private *d_func(); inline const Private *d_func() const; }; } } #endif /* __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ */ diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index 9841f03d4..02ce3a11c 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,263 +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]->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()); + auto cmd = new Kleo::Commands::KeyToCardCommand(subkey, card->serialNumber(), card->appName()); 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/readerstatus.cpp b/src/smartcard/readerstatus.cpp index 3b73b5d6b..31cbd8dc1 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,937 +1,949 @@ /* -*- 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, ""); Q_DECLARE_METATYPE(GpgME::Error) namespace { static QDebug operator<<(QDebug s, const std::string &string) { return s << QString::fromStdString(string); } 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 << ')'; } struct CardApp { std::string serialNumber; std::string appName; }; static void logUnexpectedStatusLine(const std::pair &line, const std::string &prefix = std::string(), const std::string &command = std::string()) { qCWarning(KLEOPATRA_LOG) << (!prefix.empty() ? QString::fromStdString(prefix + ": ") : QString()) << "Unexpected status line" << (!command.empty() ? QString::fromStdString(" on " + command + ":") : QLatin1String(":")) << QString::fromStdString(line.first) << QString::fromStdString(line.second); } static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } 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 std::vector getCardsAndApps(std::shared_ptr &gpgAgent, Error &err) { std::vector result; const std::string command = "SCD GETINFO all_active_apps"; const auto statusLines = gpgagent_statuslines(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); // 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); } } return result; } static std::string switchCard(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { const std::string command = "SCD SWITCHCARD " + serialNumber; const auto statusLines = gpgagent_statuslines(gpgAgent, command.c_str(), err); if (err) { return std::string(); } if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second == serialNumber) { return serialNumber; } qCWarning(KLEOPATRA_LOG) << "switchCard():" << command << "returned" << statusLines << "(expected:" << "SERIALNO " + serialNumber << ")"; return std::string(); } static std::string switchApp(std::shared_ptr &gpgAgent, const std::string &serialNumber, const std::string &appName, Error &err) { const std::string command = "SCD SWITCHAPP " + appName; const auto statusLines = gpgagent_statuslines(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 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 pgpCard = new OpenPGPCard(*ci); pgpCard->setManufacturer(get_manufacturer(gpg_agent, err)); if (err.code()) { // fallback, e.g. if gpg does not yet support querying for the MANUFACTURER attribute pgpCard->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; } pgpCard->setKeyPairInfo(info); ci.reset(pgpCard); } static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &gpg_agent) { Error err; const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef; const auto keyPairInfoLines = 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 { logUnexpectedStatusLine(pair, "readKeyPairInfoFromPIVCard()", command); } } } 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(*ci); 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(*ci); 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(const std::string &serialNumber, const std::string &appName, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr(new Card()); // 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); } // 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); // 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); return ci; } else if (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 (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:" << appName; return ci; } return ci; } static std::vector > update_cardinfo(std::shared_ptr &gpgAgent) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo()"; // ensure that a card is present and that all cards are properly set up { Error err; const std::string serialno = gpgagent_status(gpgAgent, "SCD SERIALNO --all", err); if (err) { auto ci = std::shared_ptr(new Card()); if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return std::vector >(1, ci); } } Error err; const std::vector cardApps = getCardsAndApps(gpgAgent, err); if (err) { auto ci = std::shared_ptr(new Card()); if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return std::vector >(1, ci); } std::vector > cards; for (const auto &cardApp: cardApps) { const auto card = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent); cards.push_back(card); } return cards; } } // namespace struct Transaction { CardApp cardApp; QByteArray command; QPointer receiver; const char *slot; AssuanTransaction* assuanTransaction; }; static const Transaction updateTransaction = { { "__all__", "__all__" }, "__update__", nullptr, nullptr, nullptr }; static const Transaction quitTransaction = { { "__all__", "__all__" }, "__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; CardApp cardApp; 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: 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)) { std::vector > newCards = update_cardinfo(gpgAgent); 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 Card *optr = oit != oend ? (*oit).get() : nullptr; const Card *nptr = nit != nend ? (*nit).get() : nullptr; if ((optr && !nptr) || (!optr && nptr) || (optr && nptr && *optr != *nptr)) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: slot" << idx << ": card Changed"; Q_EMIT cardChanged(idx); } if (nptr) { if (nptr->canLearnKeys()) { anyLC = true; } if (nptr->hasNullPin() && firstCardWithNullPin.empty()) { firstCardWithNullPin = (*nit)->serialNumber(); } if (nptr->status() == Card::CardError) { anyError = true; } } if (nit != nend) { ++nit; } if (oit != oend) { ++oit; } ++idx; } Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin); Q_EMIT anyCardCanLearnKeysChanged(anyLC); if (anyError) { gpgAgent.reset(); } } else { GpgME::Error err; switchCard(gpgAgent, cardApp.serialNumber, err); if (!err) { switchApp(gpgAgent, cardApp.serialNumber, cardApp.appName, err); } if (!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 std::shared_ptr &card, const QByteArray &command, QObject *receiver, const char *slot) { const CardApp cardApp = { card->serialNumber(), card->appName() }; const Transaction t = { cardApp, command, receiver, slot, nullptr }; d->addTransaction(t); } void ReaderStatus::startTransaction(const std::shared_ptr &card, const QByteArray &command, QObject *receiver, const char *slot, std::unique_ptr transaction) { const CardApp cardApp = { card->serialNumber(), card->appName() }; const Transaction t = { cardApp, command, receiver, slot, transaction.release() }; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } std::vector > ReaderStatus::getCards() const { return d->cardInfos(); } +std::shared_ptr ReaderStatus::getCard(const std::string &serialNumber, const std::string &appName) const +{ + for (const auto &card: d->cardInfos()) { + if (card->serialNumber() == serialNumber && card->appName() == appName) { + qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << serialNumber << "and app" << appName; + return card; + } + } + qCWarning(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << serialNumber << "and app" << appName; + return std::shared_ptr(); +} + #include "readerstatus.moc" diff --git a/src/smartcard/readerstatus.h b/src/smartcard/readerstatus.h index 55c6cff3b..b15ce3361 100644 --- a/src/smartcard/readerstatus.h +++ b/src/smartcard/readerstatus.h @@ -1,85 +1,80 @@ /* -*- 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 */ #ifndef __KLEOPATRA__SMARTCARD__READERSTATUS_H___ #define __KLEOPATRA__SMARTCARD__READERSTATUS_H___ #include #include #include "card.h" #include #include #include "kleopatra_debug.h" namespace GpgME { class AssuanTransaction; } namespace Kleo { namespace SmartCard { class ReaderStatus : public QObject { Q_OBJECT public: explicit ReaderStatus(QObject *parent = nullptr); ~ReaderStatus(); static const ReaderStatus *instance(); static ReaderStatus *mutableInstance(); void startSimpleTransaction(const std::shared_ptr &card, const QByteArray &cmd, QObject *receiver, const char *slot); void startTransaction(const std::shared_ptr &card, const QByteArray &cmd, QObject *receiver, const char *slot, std::unique_ptr transaction); Card::Status cardStatus(unsigned int slot) const; std::string firstCardWithNullPin() const; bool anyCardCanLearnKeys() const; std::vector > getCards() const; + std::shared_ptr getCard(const std::string &serialNumber, const std::string &appName) const; + template std::shared_ptr getCard(const std::string &serialNumber) const { - for (const auto &card: getCards()) { - if (card->serialNumber() == serialNumber) { - qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << QString::fromStdString(serialNumber); - return std::dynamic_pointer_cast(card); - } - } - qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << QString::fromStdString(serialNumber); - return std::shared_ptr(); + return std::dynamic_pointer_cast(getCard(serialNumber, T::AppName)); } public Q_SLOTS: void updateStatus(); void startMonitoring(); Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); void anyCardCanLearnKeysChanged(bool); void cardChanged(unsigned int slot); private: class Private; std::shared_ptr d; }; } // namespace SmartCard } // namespace Kleo Q_DECLARE_METATYPE(Kleo::SmartCard::Card::Status) #endif /* __KLEOPATRA__SMARTCARD__READERSTATUS_H___ */ diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp index 57a174b94..2b8786749 100644 --- a/src/view/pgpcardwidget.cpp +++ b/src/view/pgpcardwidget.cpp @@ -1,483 +1,483 @@ /* view/pgpcardwiget.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 "pgpcardwidget.h" #include "kleopatra_debug.h" #include "commands/changepincommand.h" #include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "dialogs/gencardkeydialog.h" #include "utils/gnupg-helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; namespace { class GenKeyThread: public QThread { Q_OBJECT public: explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const std::string &serial): mSerial(serial), mParams(params) { } GpgME::Error error() { return mErr; } std::string bkpFile() { return mBkpFile; } protected: void run() override { GpgME::GpgGenCardKeyInteractor *ei = new GpgME::GpgGenCardKeyInteractor(mSerial); ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA); ei->setKeySize(QByteArray::fromStdString(mParams.algorithm).toInt()); ei->setNameUtf8(mParams.name.toStdString()); ei->setEmailUtf8(mParams.email.toStdString()); ei->setDoBackup(mParams.backup); const auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(GpgME::OpenPGP)); QGpgME::QByteArrayDataProvider dp; GpgME::Data data(&dp); mErr = ctx->cardEdit(GpgME::Key(), std::unique_ptr (ei), data); mBkpFile = ei->backupFileName(); } private: GpgME::Error mErr; std::string mSerial; GenCardKeyDialog::KeyParams mParams; std::string mBkpFile; }; } // Namespace PGPCardWidget::PGPCardWidget(QWidget *parent): QWidget(parent), mSerialNumber(new QLabel(this)), mCardHolderLabel(new QLabel(this)), mVersionLabel(new QLabel(this)), mSigningKey(new QLabel(this)), mEncryptionKey(new QLabel(this)), mAuthKey(new QLabel(this)), mUrlLabel(new QLabel(this)), mCardIsEmpty(false) { auto grid = new QGridLayout; int row = 0; // Set up the scroll are auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); auto areaWidget = new QWidget; auto areaVLay = new QVBoxLayout(areaWidget); areaVLay->addLayout(grid); areaVLay->addStretch(1); area->setWidget(areaWidget); auto myLayout = new QVBoxLayout(this); myLayout->addWidget(area); // Version and Serialnumber grid->addWidget(mVersionLabel, row++, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Serial number:")), row, 0); grid->addWidget(mSerialNumber, row++, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); // Cardholder Row grid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0); grid->addWidget(mCardHolderLabel, row, 1); mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); auto nameButtton = new QPushButton; nameButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); nameButtton->setToolTip(i18n("Change")); grid->addWidget(nameButtton, row++, 2); connect(nameButtton, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); // URL Row grid->addWidget(new QLabel(i18nc("The URL under which a public key that " "corresponds to a smartcard can be downloaded", "Pubkey URL:")), row, 0); grid->addWidget(mUrlLabel, row, 1); mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); auto urlButtton = new QPushButton; urlButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); urlButtton->setToolTip(i18n("Change")); grid->addWidget(urlButtton, row++, 2); connect(urlButtton, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); // The keys auto line1 = new QFrame(); line1->setFrameShape(QFrame::HLine); grid->addWidget(line1, row++, 0, 1, 4); grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:"))), row++, 0); grid->addWidget(new QLabel(i18n("Signature:")), row, 0); grid->addWidget(mSigningKey, row++, 1); mSigningKey->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Encryption:")), row, 0); grid->addWidget(mEncryptionKey, row++, 1); mEncryptionKey->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Authentication:")), row, 0); grid->addWidget(mAuthKey, row++, 1); mAuthKey->setTextInteractionFlags(Qt::TextBrowserInteraction); auto line2 = new QFrame(); line2->setFrameShape(QFrame::HLine); grid->addWidget(line2, row++, 0, 1, 4); grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), row++, 0); auto actionLayout = new QHBoxLayout; auto generateButton = new QPushButton(i18n("Generate new Keys")); generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card.")); actionLayout->addWidget(generateButton); connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested); auto pinButtton = new QPushButton(i18n("Change PIN")); pinButtton->setToolTip(i18n("Change the PIN required to unblock the smartcard.")); actionLayout->addWidget(pinButtton); connect(pinButtton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::pinKeyRef()); }); auto pukButton = new QPushButton(i18n("Change Admin PIN")); pukButton->setToolTip(i18n("Change the PIN required to unlock the smartcard.")); actionLayout->addWidget(pukButton); connect(pukButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::adminPinKeyRef()); }); auto resetCodeButton = new QPushButton(i18n("Change Reset Code")); pukButton->setToolTip(i18n("Change the PIN required to reset the smartcard to an empty state.")); actionLayout->addWidget(resetCodeButton); connect(resetCodeButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef()); }); actionLayout->addStretch(-1); grid->addLayout(actionLayout, row++, 0, 1, 4); grid->setColumnStretch(4, -1); } void PGPCardWidget::setCard(const OpenPGPCard *card) { const QString version = QString::fromStdString(card->cardVersion()); mIs21 = versionIsAtLeast("2.1", card->cardVersion().c_str()); const QString manufacturer = QString::fromStdString(card->manufacturer()); const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown"); mVersionLabel->setText(manufacturerIsUnknown ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version) : i18nc("First placeholder is manufacturer, second placeholder is a version number", "%1 OpenPGP v%2 card", manufacturer, version)); const QString sn = QString::fromStdString(card->serialNumber()).mid(16, 12); mSerialNumber->setText(sn); mRealSerial = card->serialNumber(); const auto holder = QString::fromStdString(card->cardHolder()); const auto url = QString::fromStdString(card->pubkeyUrl()); mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); mUrl = url; mUrlLabel->setText(url.isEmpty() ? i18n("not set") : QStringLiteral("%1").arg(url.toHtmlEscaped())); mUrlLabel->setOpenExternalLinks(true); updateKey(mSigningKey, card->sigFpr()); updateKey(mEncryptionKey, card->encFpr()); updateKey(mAuthKey, card->authFpr()); mCardIsEmpty = card->authFpr().empty() && card->sigFpr().empty() && card->encFpr().empty(); } void PGPCardWidget::doChangePin(const std::string &keyRef) { - auto cmd = new ChangePinCommand(mRealSerial, this); + auto cmd = new ChangePinCommand(mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->start(); } void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) { const auto params = dlg->getKeyParams(); auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setMaximum(0); progress->setMinimum(0); progress->setModal(true); progress->setCancelButton(nullptr); progress->setWindowTitle(i18nc("@title:window", "Generating Keys")); progress->setLabel(new QLabel(i18n("This may take several minutes..."))); GenKeyThread *workerThread = new GenKeyThread(params, mRealSerial); connect(workerThread, &QThread::finished, this, [this, workerThread, progress] { progress->accept(); progress->deleteLater(); genKeyDone(workerThread->error(), workerThread->bkpFile()); delete workerThread; }); workerThread->start(); progress->exec(); } void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup) { if (err) { KMessageBox::error(this, i18nc("@info", "Failed to generate new key: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); return; } if (err.isCanceled()) { return; } if (!backup.empty()) { const auto bkpFile = QString::fromStdString(backup); QFileInfo fi(bkpFile); const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), fi.fileName(), QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key"))); if (!target.isEmpty() && !QFile::copy(bkpFile, target)) { KMessageBox::error(this, i18nc("@info", "Failed to move backup. The backup key is still stored under: %1", bkpFile), i18nc("@title", "Error")); } else if (!target.isEmpty()) { QFile::remove(bkpFile); } } KMessageBox::information(this, i18nc("@info", "Successfully generated a new key for this card."), i18nc("@title", "Success")); } void PGPCardWidget::genkeyRequested() { if (!mCardIsEmpty) { auto ret = KMessageBox::warningContinueCancel(this, i18n("The existing keys on this card will be deleted " "and replaced by new keys.") + QStringLiteral("

") + i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."), i18n("Secret Key Deletion"), KStandardGuiItem::guiItem(KStandardGuiItem::Delete), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Continue) { return; } } GenCardKeyDialog *dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this); std::vector> algos = { { "1024", QStringLiteral("RSA 1024") }, { "2048", QStringLiteral("RSA 2048") }, { "3072", QStringLiteral("RSA 3072") } }; // There is probably a better way to check for capabilities if (mIs21) { algos.push_back({"4096", QStringLiteral("RSA 4096")}); } dlg->setSupportedAlgorithms(algos, "2048"); connect(dlg, &QDialog::accepted, this, [this, dlg] () { doGenKey(dlg); dlg->deleteLater(); }); dlg->setModal(true); dlg->show(); } void PGPCardWidget::changeNameRequested() { QString text = mCardHolderLabel->text(); while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change cardholder"), i18n("New name:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.contains(QLatin1Char('<'))) { KMessageBox::error(this, i18nc("@info", "The \"<\" character may not be used."), i18nc("@title", "Error")); continue; } if (text.contains(QLatin1String(" "))) { KMessageBox::error(this, i18nc("@info", "Double spaces are not allowed"), i18nc("@title", "Error")); continue; } if (text.size() > 38) { KMessageBox::error(this, i18nc("@info", "The size of the name may not exceed 38 characters."), i18nc("@title", "Error")); } break; } auto parts = text.split(QLatin1Char(' ')); const auto lastName = parts.takeLast(); const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, "changeNameResult"); } void PGPCardWidget::changeNameResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "Name change failed: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "Name successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::changeUrlRequested() { QString text = mUrl; while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"), i18n("New pubkey URL:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.size() > 254) { KMessageBox::error(this, i18nc("@info", "The size of the URL may not exceed 254 characters."), i18nc("@title", "Error")); } break; } const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, "changeUrlResult"); } void PGPCardWidget::changeUrlResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "URL change failed: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "URL successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::updateKey(QLabel *label, const std::string &fpr) { label->setText(QString::fromStdString(fpr)); if (fpr.empty()) { label->setText(i18n("Slot empty")); return; } std::vector vec; std::string keyid = fpr; keyid.erase(0, keyid.size() - 16); vec.push_back(keyid); const auto subkeys = KeyCache::instance()->findSubkeysByKeyID(vec); if (subkeys.empty() || subkeys[0].isNull()) { label->setToolTip(i18n("Public key not found.")); return; } QStringList toolTips; toolTips.reserve(subkeys.size()); for (const auto &sub: subkeys) { // Yep you can have one subkey associated with multiple // primary keys. toolTips << Formatting::toolTip(sub.parent(), Formatting::Validity | Formatting::StorageLocation | Formatting::ExpiryDates | Formatting::UserIDs | Formatting::Fingerprint); } label->setToolTip(toolTips.join(QLatin1String("
"))); return; } #include "pgpcardwidget.moc" diff --git a/src/view/pivcardwidget.cpp b/src/view/pivcardwidget.cpp index 397c68dfc..854c5bdcd 100644 --- a/src/view/pivcardwidget.cpp +++ b/src/view/pivcardwidget.cpp @@ -1,363 +1,363 @@ /* view/pivcardwiget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "pivcardwidget.h" #include "tooltippreferences.h" #include "commands/certificatetopivcardcommand.h" #include "commands/changepincommand.h" #include "commands/importcertificatefrompivcardcommand.h" #include "commands/keytocardcommand.h" #include "commands/pivgeneratecardkeycommand.h" #include "commands/setpivcardapplicationadministrationkeycommand.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; namespace { static QString formatVersion(int value) { if (value < 0) { return QLatin1String("n/a"); } const unsigned int a = ((value >> 24) & 0xff); const unsigned int b = ((value >> 16) & 0xff); const unsigned int c = ((value >> 8) & 0xff); const unsigned int d = ((value ) & 0xff); if (a) { return QStringLiteral("%1.%2.%3.%4").arg(QString::number(a), QString::number(b), QString::number(c), QString::number(d)); } else if (b) { return QStringLiteral("%1.%2.%3").arg(QString::number(b), QString::number(c), QString::number(d)); } else if (c) { return QStringLiteral("%1.%2").arg(QString::number(c), QString::number(d)); } return QString::number(d); } static void layoutKeyWidgets(QGridLayout *grid, const QString &keyName, const PIVCardWidget::KeyWidgets &keyWidgets) { int row = grid->rowCount(); grid->addWidget(new QLabel(keyName), row, 0); grid->addWidget(keyWidgets.certificateInfo, row, 1, 1, 2); grid->addWidget(keyWidgets.generateButton, row, 3); if (keyWidgets.writeKeyButton) { grid->addWidget(keyWidgets.writeKeyButton, row, 4); } row++; grid->addWidget(keyWidgets.keyGrip, row, 1); grid->addWidget(keyWidgets.keyAlgorithm, row, 2); grid->addWidget(keyWidgets.writeCertificateButton, row, 3); grid->addWidget(keyWidgets.importCertificateButton, row, 4); } static int toolTipOptions() { using namespace Kleo::Formatting; static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage; static const int ownerFlags = Subject | UserIDs | OwnerTrust; static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint; const TooltipPreferences prefs; int flags = KeyID; flags |= prefs.showValidity() ? validityFlags : 0; flags |= prefs.showOwnerInformation() ? ownerFlags : 0; flags |= prefs.showCertificateDetails() ? detailsFlags : 0; return flags; } } PIVCardWidget::PIVCardWidget(QWidget *parent): QWidget(parent), mSerialNumber(new QLabel(this)), mVersionLabel(new QLabel(this)) { // Set up the scroll area auto myLayout = new QVBoxLayout(this); auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); myLayout->addWidget(area); auto areaWidget = new QWidget; area->setWidget(areaWidget); auto areaVLay = new QVBoxLayout(areaWidget); auto grid = new QGridLayout; areaVLay->addLayout(grid); areaVLay->addStretch(1); const int columnCount = 5; int row = 0; // Version and Serialnumber grid->addWidget(mVersionLabel, row++, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); grid->addWidget(new QLabel(i18n("Serial number:")), row, 0); grid->addWidget(mSerialNumber, row++, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); { auto line = new QFrame(); line->setFrameShape(QFrame::HLine); grid->addWidget(line, row++, 0, 1, columnCount); } // The keys grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:"))), row++, 0); { KeyWidgets keyWidgets = createKeyWidgets(PIVCard::pivAuthenticationKeyRef()); layoutKeyWidgets(grid, i18n("PIV authentication:"), keyWidgets); } { KeyWidgets keyWidgets = createKeyWidgets(PIVCard::cardAuthenticationKeyRef()); layoutKeyWidgets(grid, i18n("Card authentication:"), keyWidgets); } { KeyWidgets keyWidgets = createKeyWidgets(PIVCard::digitalSignatureKeyRef()); layoutKeyWidgets(grid, i18n("Digital signature:"), keyWidgets); } { KeyWidgets keyWidgets = createKeyWidgets(PIVCard::keyManagementKeyRef()); layoutKeyWidgets(grid, i18n("Key management:"), keyWidgets); } row = grid->rowCount(); { auto line = new QFrame(); line->setFrameShape(QFrame::HLine); grid->addWidget(line, row++, 0, 1, columnCount); } auto actionLayout = new QHBoxLayout; { auto button = new QPushButton(i18nc("@action:button", "Change PIN")); button->setToolTip(i18nc("@info:tooltip", "Change the PIV Card Application PIN that activates the PIV Card " "and enables private key operations using the stored keys.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this] () { changePin(PIVCard::pinKeyRef()); }); } { auto button = new QPushButton(i18nc("@action:button", "Change PUK")); button->setToolTip(i18nc("@info:tooltip", "Change the PIN Unblocking Key that enables a reset of the PIN.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this] () { changePin(PIVCard::pukKeyRef()); }); } { auto button = new QPushButton(i18nc("@action:button", "Change Admin Key")); button->setToolTip(i18nc("@info:tooltip", "Change the PIV Card Application Administration Key that is used by the " "PIV Card Application to authenticate the PIV Card Application Administrator and by the " "administrator (resp. Kleopatra) to authenticate the PIV Card Application.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this] () { setAdminKey(); }); } actionLayout->addStretch(-1); grid->addLayout(actionLayout, row++, 0, 1, columnCount); grid->setColumnStretch(4, -1); } PIVCardWidget::KeyWidgets PIVCardWidget::createKeyWidgets(const std::string &keyRef) { KeyWidgets keyWidgets; keyWidgets.keyGrip = new QLabel(this); keyWidgets.keyGrip->setTextInteractionFlags(Qt::TextBrowserInteraction); keyWidgets.keyAlgorithm = new QLabel(this); keyWidgets.keyAlgorithm->setTextInteractionFlags(Qt::TextSelectableByMouse); keyWidgets.certificateInfo = new QLabel(this); keyWidgets.certificateInfo->setTextInteractionFlags(Qt::TextBrowserInteraction); keyWidgets.generateButton = new QPushButton(i18nc("@action:button", "Generate"), this); keyWidgets.generateButton->setEnabled(false); connect(keyWidgets.generateButton, &QPushButton::clicked, this, [this, keyRef] () { generateKey(keyRef); }); keyWidgets.writeCertificateButton = new QPushButton(i18nc("@action:button", "Write Certificate")); keyWidgets.writeCertificateButton->setToolTip(i18nc("@info:tooltip", "Write the certificate corresponding to this key to the card")); keyWidgets.writeCertificateButton->setEnabled(false); connect(keyWidgets.writeCertificateButton, &QPushButton::clicked, this, [this, keyRef] () { writeCertificateToCard(keyRef); }); keyWidgets.importCertificateButton = new QPushButton(i18nc("@action:button", "Import Certificate")); keyWidgets.importCertificateButton->setToolTip(i18nc("@info:tooltip", "Import the certificate stored on the card")); keyWidgets.importCertificateButton->setEnabled(false); connect(keyWidgets.importCertificateButton, &QPushButton::clicked, this, [this, keyRef] () { importCertificateFromCard(keyRef); }); if (keyRef == PIVCard::keyManagementKeyRef()) { keyWidgets.writeKeyButton = new QPushButton(i18nc("@action:button", "Write Key")); keyWidgets.writeKeyButton->setToolTip(i18nc("@info:tooltip", "Write the key pair of a certificate to the card")); keyWidgets.writeKeyButton->setEnabled(true); connect(keyWidgets.writeKeyButton, &QPushButton::clicked, this, [this, keyRef] () { writeKeyToCard(keyRef); }); } mKeyWidgets.insert(keyRef, keyWidgets); return keyWidgets; } PIVCardWidget::~PIVCardWidget() { } void PIVCardWidget::setCard(const PIVCard *card) { mCardSerialNumber = card->serialNumber(); mVersionLabel->setText(i18nc("%1 version number", "PIV v%1 card", formatVersion(card->appVersion()))); if (card->displaySerialNumber() != card->serialNumber()) { mSerialNumber->setText(QStringLiteral("%1 (%2)").arg(QString::fromStdString(card->displaySerialNumber()), QString::fromStdString(card->serialNumber()))); } else { mSerialNumber->setText(QString::fromStdString(card->serialNumber())); } updateKeyWidgets(PIVCard::pivAuthenticationKeyRef(), card); updateKeyWidgets(PIVCard::cardAuthenticationKeyRef(), card); updateKeyWidgets(PIVCard::digitalSignatureKeyRef(), card); updateKeyWidgets(PIVCard::keyManagementKeyRef(), card); } void PIVCardWidget::updateKeyWidgets(const std::string &keyRef, const PIVCard *card) { KeyWidgets widgets = mKeyWidgets.value(keyRef); const std::string grip = card ? card->keyGrip(keyRef) : widgets.keyGrip->text().toStdString(); if (grip.empty()) { widgets.certificateInfo->setText(i18nc("@info", "slot empty")); widgets.certificateInfo->setToolTip(QString()); widgets.keyGrip->setText(QString()); widgets.keyAlgorithm->setText(QString()); widgets.generateButton->setText(i18nc("@action:button", "Generate")); widgets.generateButton->setToolTip( i18nc("@info:tooltip %1 display name of a key", "Generate %1", PIVCard::keyDisplayName(keyRef))); widgets.writeCertificateButton->setEnabled(false); widgets.importCertificateButton->setEnabled(false); } else { const Key certificate = KeyCache::instance()->findSubkeyByKeyGrip(grip).parent(); if (!certificate.isNull() && certificate.protocol() == GpgME::CMS) { widgets.certificateInfo->setText( i18nc("X.509 certificate DN (validity, created: date)", "%1 (%2, created: %3)", DN(certificate.userID(0).id()).prettyDN(), Formatting::complianceStringShort(certificate), Formatting::creationDateString(certificate))); widgets.certificateInfo->setToolTip(Formatting::toolTip(certificate, toolTipOptions())); widgets.writeCertificateButton->setEnabled(true); } else { widgets.certificateInfo->setText(i18nc("@info", "no matching certificate")); widgets.certificateInfo->setToolTip(QString()); widgets.writeCertificateButton->setEnabled(false); } if (card) { // update information if called with card widgets.keyGrip->setText(QString::fromStdString(grip)); const std::string algo = card->keyAlgorithm(keyRef); widgets.keyAlgorithm->setText(algo.empty() ? i18nc("@info unknown key algorithm", "unknown") : QString::fromStdString(algo)); widgets.importCertificateButton->setEnabled(!card->certificateData(keyRef).empty()); } widgets.generateButton->setText(i18nc("@action:button", "Replace")); widgets.generateButton->setToolTip( i18nc("@info:tooltip %1 display name of a key", "Replace %1 with new key", PIVCard::keyDisplayName(keyRef))); } widgets.generateButton->setEnabled(true); } void PIVCardWidget::generateKey(const std::string &keyref) { auto cmd = new PIVGenerateCardKeyCommand(mCardSerialNumber, this); this->setEnabled(false); connect(cmd, &PIVGenerateCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyref); cmd->start(); } void PIVCardWidget::writeCertificateToCard(const std::string &keyref) { auto cmd = new CertificateToPIVCardCommand(keyref, mCardSerialNumber); this->setEnabled(false); connect(cmd, &CertificateToPIVCardCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::importCertificateFromCard(const std::string &keyref) { auto cmd = new ImportCertificateFromPIVCardCommand(keyref, mCardSerialNumber); this->setEnabled(false); connect(cmd, &ImportCertificateFromPIVCardCommand::finished, this, [this, keyref] () { this->updateKeyWidgets(keyref, nullptr); this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::writeKeyToCard(const std::string &keyref) { - auto cmd = new KeyToCardCommand(keyref, mCardSerialNumber); + auto cmd = new KeyToCardCommand(keyref, mCardSerialNumber, PIVCard::AppName); this->setEnabled(false); connect(cmd, &KeyToCardCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::changePin(const std::string &keyRef) { - auto cmd = new ChangePinCommand(mCardSerialNumber, this); + auto cmd = new ChangePinCommand(mCardSerialNumber, PIVCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->start(); } void PIVCardWidget::setAdminKey() { auto cmd = new SetPIVCardApplicationAdministrationKeyCommand(mCardSerialNumber, this); this->setEnabled(false); connect(cmd, &SetPIVCardApplicationAdministrationKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); }