diff --git a/src/commands/certificatetopivcardcommand.cpp b/src/commands/certificatetopivcardcommand.cpp index 66465263c..c9fb49455 100644 --- a/src/commands/certificatetopivcardcommand.cpp +++ b/src/commands/certificatetopivcardcommand.cpp @@ -1,250 +1,257 @@ /* commands/certificatetopivcardcommand.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 #include "certificatetopivcardcommand.h" #include "cardcommand_p.h" #include "commands/authenticatepivcardapplicationcommand.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include "utils/writecertassuantransaction.h" #include #include #include #include #include #include +#include +#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36 +# define GPG_ERROR_HAS_NO_AUTH +#endif + #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class CertificateToPIVCardCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::CertificateToPIVCardCommand; CertificateToPIVCardCommand *q_func() const { return static_cast(q); } public: explicit Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno); ~Private(); private: void start(); void startCertificateToPIVCard(); void authenticate(); void authenticationFinished(); void authenticationCanceled(); private: std::string cardSlot; Key certificate; bool hasBeenCanceled = false; }; CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func() { return static_cast(d.get()); } const CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() CertificateToPIVCardCommand::Private::Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno) : CardCommand::Private(qq, serialno, nullptr) , cardSlot(slot) { } CertificateToPIVCardCommand::Private::~Private() { } namespace { static Key getCertificateToWriteToPIVCard(const std::string &cardSlot, const std::shared_ptr &card) { if (!cardSlot.empty()) { const std::string cardKeygrip = card->keyGrip(cardSlot); const auto certificate = KeyCache::instance()->findSubkeyByKeyGrip(cardKeygrip).parent(); if (certificate.isNull() || certificate.protocol() != GpgME::CMS) { return Key(); } if ((cardSlot == PIVCard::pivAuthenticationKeyRef() && certificate.canSign()) || (cardSlot == PIVCard::cardAuthenticationKeyRef() && certificate.canSign()) || (cardSlot == PIVCard::digitalSignatureKeyRef() && certificate.canSign()) || (cardSlot == PIVCard::keyManagementKeyRef() && certificate.canEncrypt())) { return certificate; } } return Key(); } } void CertificateToPIVCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::start()"; 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; } certificate = getCertificateToWriteToPIVCard(cardSlot, pivCard); if (certificate.isNull()) { error(i18n("Sorry! No suitable certificate to write to this card slot was found.")); finished(); return; } const QString certificateInfo = i18nc("X.509 certificate DN (validity, created: date)", "%1 (%2, created: %3)", DN(certificate.userID(0).id()).prettyDN(), Formatting::complianceStringShort(certificate), Formatting::creationDateString(certificate)); const QString message = i18nc( "@info %1 name of card slot, %2 serial number of card", "

Please confirm that you want to write the following certificate to the %1 slot of card %2:

" "
%3
", PIVCard::keyDisplayName(cardSlot), QString::fromStdString(serialNumber()), certificateInfo); auto confirmButton = KStandardGuiItem::yes(); confirmButton.setText(i18nc("@action:button", "Write certificate")); confirmButton.setToolTip(QString()); const auto choice = KMessageBox::questionYesNo( parentWidgetOrView(), message, i18nc("@title:window", "Write certificate to card"), confirmButton, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::WindowModal); if (choice != KMessageBox::Yes) { finished(); return; } startCertificateToPIVCard(); } void CertificateToPIVCardCommand::Private::startCertificateToPIVCard() { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::startCertificateToPIVCard()"; auto ctx = Context::createForProtocol(GpgME::CMS); QGpgME::QByteArrayDataProvider dp; Data data(&dp); const Error err = ctx->exportPublicKeys(certificate.primaryFingerprint(), data); if (err) { error(i18nc("@info", "Exporting the certificate failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); finished(); return; } const QByteArray certificateData = dp.data(); 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; } const QByteArray command = QByteArrayLiteral("SCD WRITECERT ") + QByteArray::fromStdString(cardSlot); auto transaction = std::unique_ptr(new WriteCertAssuanTransaction(certificateData)); ReaderStatus::mutableInstance()->startTransaction(pivCard, command, q_func(), "certificateToPIVCardDone", std::move(transaction)); } void CertificateToPIVCardCommand::Private::authenticate() { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticate()"; auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView()); connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() { authenticationFinished(); }); connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() { authenticationCanceled(); }); cmd->start(); } void CertificateToPIVCardCommand::Private::authenticationFinished() { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationFinished()"; if (!hasBeenCanceled) { startCertificateToPIVCard(); } } void CertificateToPIVCardCommand::Private::authenticationCanceled() { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationCanceled()"; hasBeenCanceled = true; canceled(); } CertificateToPIVCardCommand::CertificateToPIVCardCommand(const std::string& cardSlot, const std::string &serialno) : CardCommand(new Private(this, cardSlot, serialno)) { } CertificateToPIVCardCommand::~CertificateToPIVCardCommand() { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::~CertificateToPIVCardCommand()"; } void CertificateToPIVCardCommand::certificateToPIVCardDone(const Error &err) { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::certificateToPIVCardDone():" << err.asString() << "(" << err.code() << ")"; if (err) { +#ifdef GPG_ERROR_HAS_NO_AUTH // 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; } +#endif d->error(i18nc("@info", "Writing the certificate to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { KMessageBox::information(d->parentWidgetOrView(), i18nc("@info", "Writing the certificate to the card succeeded."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } d->finished(); } void CertificateToPIVCardCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::doStart()"; d->start(); } void CertificateToPIVCardCommand::doCancel() { } #undef q_func #undef d_func diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp index 0ba849ff6..d0cf33da1 100644 --- a/src/commands/keytocardcommand.cpp +++ b/src/commands/keytocardcommand.cpp @@ -1,517 +1,524 @@ /* 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 "smartcard/utils.h" #include #include #include #include #include #include #include #include +#include +#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36 +# define GPG_ERROR_HAS_NO_AUTH +#endif + #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); 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_) : CardCommand::Private(qq, "", nullptr) , subkey(subkey_) { } 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() { } namespace { static std::shared_ptr getCardToTransferSubkeyTo(const Subkey &subkey, QWidget *parent) { const std::vector > suitableCards = KeyToCardCommand::getSuitableCards(subkey); if (suitableCards.empty()) { return std::shared_ptr(); } else if (suitableCards.size() == 1) { return suitableCards[0]; } QStringList options; for (const auto &card: suitableCards) { options.push_back(i18nc("smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber())); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Card"), i18n("Please select the card the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); if (!ok) { return std::shared_ptr(); } const int index = options.indexOf(choice); return suitableCards[index]; } } void KeyToCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()"; if (!subkey.isNull() && serialNumber().empty()) { const auto card = getCardToTransferSubkeyTo(subkey, parentWidgetOrView()); if (!card) { finished(); return; } setSerialNumber(card->serialNumber()); appName = card->appName(); } 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(), Qt::UTC); 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 getSigningCertificates() { std::vector signingCertificates = KeyCache::instance()->secretKeys(); const auto it = std::remove_if(signingCertificates.begin(), signingCertificates.end(), [](const Key &key) { return ! (key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canSign() && !key.subkey(0).canEncrypt() && key.subkey(0).isSecret() && !key.subkey(0).isCardKey()); }); signingCertificates.erase(it, signingCertificates.end()); return signingCertificates; } 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() && !key.subkey(0).isCardKey()); }); encryptionCertificates.erase(it, encryptionCertificates.end()); return encryptionCertificates; } } Subkey KeyToCardCommand::Private::getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &/*card*/) { if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) { return Subkey(); } const std::vector certificates = cardSlot == PIVCard::cardAuthenticationKeyRef() ? getSigningCertificates() : getEncryptionCertificates(); if (certificates.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(certificates); 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::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) { // key to card is only supported for the Card Authentication key and the Key Management key 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 &subkey) : CardCommand(new Private(this, subkey)) { } 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()"; } // static std::vector > KeyToCardCommand::getSuitableCards(const GpgME::Subkey &subkey) { std::vector > suitableCards; if (subkey.isNull() || subkey.parent().protocol() != GpgME::OpenPGP) { return suitableCards; } for (const auto &card: ReaderStatus::instance()->getCards()) { if (card->appName() == OpenPGPCard::AppName) { suitableCards.push_back(card); } } return suitableCards; } 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) { +#ifdef GPG_ERROR_HAS_NO_AUTH // 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; } +#endif 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/pivgeneratecardkeycommand.cpp b/src/commands/pivgeneratecardkeycommand.cpp index edbbce55d..56f68f4c1 100644 --- a/src/commands/pivgeneratecardkeycommand.cpp +++ b/src/commands/pivgeneratecardkeycommand.cpp @@ -1,247 +1,254 @@ /* commands/pivgeneratecardkeycommand.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 "pivgeneratecardkeycommand.h" #include "cardcommand_p.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include "commands/authenticatepivcardapplicationcommand.h" #include "dialogs/gencardkeydialog.h" #include #include +#include +#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36 +# define GPG_ERROR_HAS_NO_AUTH +#endif + #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class PIVGenerateCardKeyCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::PIVGenerateCardKeyCommand; PIVGenerateCardKeyCommand *q_func() const { return static_cast(q); } public: explicit Private(PIVGenerateCardKeyCommand *qq, const std::string &serialNumber, QWidget *p); ~Private(); void init(); private: void slotDialogAccepted(); void slotDialogRejected(); void slotResult(const Error &err); private: void authenticate(); void authenticationFinished(); void authenticationCanceled(); void generateKey(); void ensureDialogCreated(); private: std::string keyRef; bool overwriteExistingKey = false; std::string algorithm; QPointer dialog; bool hasBeenCanceled = false; }; PIVGenerateCardKeyCommand::Private *PIVGenerateCardKeyCommand::d_func() { return static_cast(d.get()); } const PIVGenerateCardKeyCommand::Private *PIVGenerateCardKeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() PIVGenerateCardKeyCommand::Private::Private(PIVGenerateCardKeyCommand *qq, const std::string &serialNumber, QWidget *p) : CardCommand::Private(qq, serialNumber, p) , dialog() { } PIVGenerateCardKeyCommand::Private::~Private() { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::Private::~Private()"; } PIVGenerateCardKeyCommand::PIVGenerateCardKeyCommand(const std::string &serialNumber, QWidget *p) : CardCommand(new Private(this, serialNumber, p)) { d->init(); } void PIVGenerateCardKeyCommand::Private::init() { } PIVGenerateCardKeyCommand::~PIVGenerateCardKeyCommand() { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::~PIVGenerateCardKeyCommand()"; } void PIVGenerateCardKeyCommand::setKeyRef(const std::string &keyRef) { d->keyRef = keyRef; } void PIVGenerateCardKeyCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::doStart()"; // check if key exists auto pivCard = ReaderStatus::instance()->getCard(d->serialNumber()); if (!pivCard) { d->error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(d->serialNumber()))); d->finished(); return; } auto existingKey = pivCard->keyGrip(d->keyRef); if (!existingKey.empty()) { const QString warningText = 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 ID:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + (d->keyRef == PIVCard::keyManagementKeyRef() ? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") : QString()); const auto choice = KMessageBox::warningContinueCancel(d->parentWidgetOrView(), warningText, i18nc("@title:window", "Overwrite existing key"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { d->finished(); return; } d->overwriteExistingKey = true; } d->ensureDialogCreated(); Q_ASSERT(d->dialog); d->dialog->show(); } void PIVGenerateCardKeyCommand::doCancel() { } void PIVGenerateCardKeyCommand::Private::authenticate() { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::authenticate()"; auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView()); connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() { authenticationFinished(); }); connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() { authenticationCanceled(); }); cmd->start(); } void PIVGenerateCardKeyCommand::Private::authenticationFinished() { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::authenticationFinished()"; if (!hasBeenCanceled) { generateKey(); } } void PIVGenerateCardKeyCommand::Private::authenticationCanceled() { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::authenticationCanceled()"; hasBeenCanceled = true; canceled(); } void PIVGenerateCardKeyCommand::Private::generateKey() { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::generateKey()"; auto pivCard = ReaderStatus::instance()->getCard(serialNumber()); if (!pivCard) { error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } QByteArrayList command; command << "SCD GENKEY"; if (overwriteExistingKey) { command << "--force"; } if (!algorithm.empty()) { command << "--algo=" + QByteArray::fromStdString(algorithm); } command << "--" << QByteArray::fromStdString(keyRef); ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, command.join(' '), q, "slotResult"); } void PIVGenerateCardKeyCommand::Private::slotResult(const GpgME::Error& err) { qCDebug(KLEOPATRA_LOG) << "PIVGenerateCardKeyCommand::slotResult():" << err.asString() << "(" << err.code() << ")"; if (err) { +#ifdef GPG_ERROR_HAS_NO_AUTH if (err.code() == GPG_ERR_NO_AUTH) { authenticate(); return; } +#endif error(i18nc("@info", "Generating key failed: %1", QString::fromLatin1(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { information(i18nc("@info", "Key successfully generated."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } finished(); } void PIVGenerateCardKeyCommand::Private::slotDialogAccepted() { algorithm = dialog->getKeyParams().algorithm; // assume that we are already authenticated to the card generateKey(); } void PIVGenerateCardKeyCommand::Private::slotDialogRejected() { finished(); } void PIVGenerateCardKeyCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new GenCardKeyDialog(GenCardKeyDialog::KeyAlgorithm, parentWidgetOrView()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setSupportedAlgorithms(PIVCard::supportedAlgorithms(keyRef), "rsa2048"); connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); } #undef d #undef q #include "moc_pivgeneratecardkeycommand.cpp"