File indexing completed on 2024-06-23 05:13:43

0001 /* commands/keytocardcommand.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
0005     SPDX-FileContributor: Intevation GmbH
0006     SPDX-FileCopyrightText: 2020,2022 g10 Code GmbH
0007     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include <config-kleopatra.h>
0013 
0014 #include "keytocardcommand.h"
0015 
0016 #include "cardcommand_p.h"
0017 
0018 #include "authenticatepivcardapplicationcommand.h"
0019 
0020 #include "smartcard/algorithminfo.h"
0021 #include "smartcard/openpgpcard.h"
0022 #include "smartcard/pivcard.h"
0023 #include "smartcard/readerstatus.h"
0024 #include "smartcard/utils.h"
0025 #include <utils/applicationstate.h>
0026 #include <utils/filedialog.h>
0027 
0028 #include <Libkleo/Algorithm>
0029 #include <Libkleo/Dn>
0030 #include <Libkleo/Formatting>
0031 #include <Libkleo/GnuPG>
0032 #include <Libkleo/KeyCache>
0033 #include <Libkleo/KeySelectionDialog>
0034 
0035 #include <KLocalizedString>
0036 
0037 #include <QDateTime>
0038 #include <QDir>
0039 #include <QInputDialog>
0040 #include <QSaveFile>
0041 #include <QStringList>
0042 
0043 #include <gpg-error.h>
0044 #if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
0045 #define GPG_ERROR_HAS_NO_AUTH
0046 #endif
0047 
0048 #include "kleopatra_debug.h"
0049 
0050 using namespace Kleo;
0051 using namespace Kleo::Commands;
0052 using namespace Kleo::SmartCard;
0053 using namespace GpgME;
0054 
0055 namespace
0056 {
0057 QString cardDisplayName(const std::shared_ptr<const Card> &card)
0058 {
0059     return i18nc("smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber());
0060 }
0061 }
0062 
0063 class KeyToCardCommand::Private : public CardCommand::Private
0064 {
0065     friend class ::Kleo::Commands::KeyToCardCommand;
0066     KeyToCardCommand *q_func() const
0067     {
0068         return static_cast<KeyToCardCommand *>(q);
0069     }
0070 
0071 public:
0072     explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey);
0073     explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName);
0074 
0075 private:
0076     enum Confirmation {
0077         AskForConfirmation,
0078         SkipConfirmation,
0079     };
0080 
0081     void start();
0082 
0083     void startKeyToOpenPGPCard();
0084 
0085     Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &card);
0086     void startKeyToPIVCard();
0087 
0088     void authenticate();
0089     void authenticationFinished();
0090     void authenticationCanceled();
0091 
0092     void keyToCardDone(const GpgME::Error &err);
0093     void keyToPIVCardDone(const GpgME::Error &err);
0094 
0095     void updateDone();
0096 
0097     void keyHasBeenCopiedToCard();
0098     void backupHasBeenCreated(const QString &backupFilename);
0099 
0100     QString backupKey();
0101     std::vector<QByteArray> readSecretKeyFile();
0102     bool writeSecretKeyBackup(const QString &filename, const std::vector<QByteArray> &keydata);
0103 
0104     void startDeleteSecretKeyLocally(Confirmation confirmation);
0105     void deleteSecretKeyLocallyFinished(const GpgME::Error &err);
0106 
0107 private:
0108     std::string appName;
0109     GpgME::Subkey subkey;
0110     std::string cardSlot;
0111     bool overwriteExistingAlreadyApproved = false;
0112     bool hasBeenCanceled = false;
0113     QMetaObject::Connection updateConnection;
0114 };
0115 
0116 KeyToCardCommand::Private *KeyToCardCommand::d_func()
0117 {
0118     return static_cast<Private *>(d.get());
0119 }
0120 const KeyToCardCommand::Private *KeyToCardCommand::d_func() const
0121 {
0122     return static_cast<const Private *>(d.get());
0123 }
0124 
0125 #define q q_func()
0126 #define d d_func()
0127 
0128 KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey_)
0129     : CardCommand::Private(qq, "", nullptr)
0130     , subkey(subkey_)
0131 {
0132 }
0133 
0134 KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName_)
0135     : CardCommand::Private(qq, serialNumber, nullptr)
0136     , appName(appName_)
0137     , cardSlot(slot)
0138 {
0139 }
0140 
0141 namespace
0142 {
0143 static std::shared_ptr<Card> getCardToTransferSubkeyTo(const Subkey &subkey, QWidget *parent)
0144 {
0145     const std::vector<std::shared_ptr<Card>> suitableCards = KeyToCardCommand::getSuitableCards(subkey);
0146     if (suitableCards.empty()) {
0147         return std::shared_ptr<Card>();
0148     } else if (suitableCards.size() == 1) {
0149         return suitableCards[0];
0150     }
0151 
0152     QStringList options;
0153     for (const auto &card : suitableCards) {
0154         options.push_back(cardDisplayName(card));
0155     }
0156 
0157     bool ok;
0158     const QString choice = QInputDialog::getItem(parent,
0159                                                  i18n("Select Card"),
0160                                                  i18n("Please select the card the key should be written to:"),
0161                                                  options,
0162                                                  /* current= */ 0,
0163                                                  /* editable= */ false,
0164                                                  &ok);
0165     if (!ok) {
0166         return std::shared_ptr<Card>();
0167     }
0168     const int index = options.indexOf(choice);
0169     return suitableCards[index];
0170 }
0171 }
0172 
0173 void KeyToCardCommand::Private::start()
0174 {
0175     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()";
0176 
0177     if (!subkey.isNull() && serialNumber().empty()) {
0178         const auto card = getCardToTransferSubkeyTo(subkey, parentWidgetOrView());
0179         if (!card) {
0180             finished();
0181             return;
0182         }
0183         setSerialNumber(card->serialNumber());
0184         appName = card->appName();
0185     }
0186 
0187     const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
0188     if (!card) {
0189         error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
0190         finished();
0191         return;
0192     }
0193 
0194     if (card->appName() == SmartCard::OpenPGPCard::AppName) {
0195         startKeyToOpenPGPCard();
0196     } else if (card->appName() == SmartCard::PIVCard::AppName) {
0197         startKeyToPIVCard();
0198     } else {
0199         error(xi18nc("@info", "Sorry! Writing keys to the card <emphasis>%1</emphasis> is not supported.", cardDisplayName(card)));
0200         finished();
0201         return;
0202     }
0203 }
0204 
0205 namespace
0206 {
0207 static std::string getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent)
0208 {
0209     // Check if we need to ask the user for the slot
0210     if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) {
0211         // Signing only
0212         return OpenPGPCard::pgpSigKeyRef();
0213     }
0214     if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) {
0215         // Encrypt only
0216         return OpenPGPCard::pgpEncKeyRef();
0217     }
0218     if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) {
0219         // Auth only
0220         return OpenPGPCard::pgpAuthKeyRef();
0221     }
0222     // Multiple uses, ask user.
0223     QStringList options;
0224     std::vector<std::string> cardSlots;
0225 
0226     if (subKey.canSign() || subKey.canCertify()) {
0227         options.push_back(i18nc("@item:inlistbox", "Signature"));
0228         cardSlots.push_back(OpenPGPCard::pgpSigKeyRef());
0229     }
0230     if (subKey.canEncrypt()) {
0231         options.push_back(i18nc("@item:inlistbox", "Encryption"));
0232         cardSlots.push_back(OpenPGPCard::pgpEncKeyRef());
0233     }
0234     if (subKey.canAuthenticate()) {
0235         options.push_back(i18nc("@item:inlistbox", "Authentication"));
0236         cardSlots.push_back(OpenPGPCard::pgpAuthKeyRef());
0237     }
0238 
0239     bool ok;
0240     const QString choice = QInputDialog::getItem(parent,
0241                                                  i18n("Select Card Slot"),
0242                                                  i18n("Please select the card slot the key should be written to:"),
0243                                                  options,
0244                                                  /* current= */ 0,
0245                                                  /* editable= */ false,
0246                                                  &ok);
0247     const int choiceIndex = options.indexOf(choice);
0248     if (ok && choiceIndex >= 0) {
0249         return cardSlots[choiceIndex];
0250     } else {
0251         return {};
0252     }
0253 }
0254 }
0255 
0256 void KeyToCardCommand::Private::startKeyToOpenPGPCard()
0257 {
0258     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()";
0259 
0260     const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard<OpenPGPCard>(serialNumber());
0261     if (!pgpCard) {
0262         error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber())));
0263         finished();
0264         return;
0265     }
0266 
0267     if (subkey.isNull()) {
0268         finished();
0269         return;
0270     }
0271     if (subkey.parent().protocol() != GpgME::OpenPGP) {
0272         error(i18n("Sorry! This key cannot be transferred to an OpenPGP card."));
0273         finished();
0274         return;
0275     }
0276 
0277     cardSlot = getOpenPGPCardSlotForKey(subkey, parentWidgetOrView());
0278     if (cardSlot.empty()) {
0279         finished();
0280         return;
0281     }
0282 
0283     // Check if we need to do the overwrite warning.
0284     const std::string existingKey = pgpCard->keyFingerprint(cardSlot);
0285     if (!existingKey.empty()) {
0286         const auto encKeyWarning = (cardSlot == OpenPGPCard::pgpEncKeyRef())
0287             ? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.")
0288             : QString{};
0289         const QString message = i18nc("@info",
0290                                       "<p>The card <em>%1</em> already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
0291                                       "<p>If there is no backup the existing key will be irrecoverably lost.</p>",
0292                                       cardDisplayName(pgpCard))
0293             + i18n("The existing key has the fingerprint:") + QStringLiteral("<pre>%1</pre>").arg(Formatting::prettyID(existingKey.c_str())) + encKeyWarning;
0294         const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(),
0295                                                                message,
0296                                                                i18nc("@title:window", "Overwrite existing key"),
0297                                                                KGuiItem{i18nc("@action:button", "Overwrite Existing Key")},
0298                                                                KStandardGuiItem::cancel(),
0299                                                                QString(),
0300                                                                KMessageBox::Notify | KMessageBox::Dangerous);
0301         if (choice != KMessageBox::Continue) {
0302             finished();
0303             return;
0304         }
0305     }
0306 
0307     // Now do the deed
0308     const auto time = QDateTime::fromSecsSinceEpoch(quint32(subkey.creationTime()), Qt::UTC);
0309     const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss"));
0310     const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3 %4")
0311                             .arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()), QString::fromStdString(cardSlot), timestamp);
0312     ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
0313         keyToCardDone(err);
0314     });
0315 }
0316 
0317 namespace
0318 {
0319 static std::vector<Key> getSigningCertificates()
0320 {
0321     std::vector<Key> signingCertificates = KeyCache::instance()->secretKeys();
0322     const auto it = std::remove_if(signingCertificates.begin(), signingCertificates.end(), [](const Key &key) {
0323         return !(key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canSign() && !key.subkey(0).canEncrypt() && key.subkey(0).isSecret()
0324                  && !key.subkey(0).isCardKey());
0325     });
0326     signingCertificates.erase(it, signingCertificates.end());
0327     return signingCertificates;
0328 }
0329 
0330 static std::vector<Key> getEncryptionCertificates()
0331 {
0332     std::vector<Key> encryptionCertificates = KeyCache::instance()->secretKeys();
0333     const auto it = std::remove_if(encryptionCertificates.begin(), encryptionCertificates.end(), [](const Key &key) {
0334         return !(key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canEncrypt() && key.subkey(0).isSecret()
0335                  && !key.subkey(0).isCardKey());
0336     });
0337     encryptionCertificates.erase(it, encryptionCertificates.end());
0338     return encryptionCertificates;
0339 }
0340 }
0341 
0342 Subkey KeyToCardCommand::Private::getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> & /*card*/)
0343 {
0344     if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
0345         return Subkey();
0346     }
0347 
0348     const std::vector<Key> certificates = cardSlot == PIVCard::cardAuthenticationKeyRef() ? getSigningCertificates() : getEncryptionCertificates();
0349     if (certificates.empty()) {
0350         error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
0351         return Subkey();
0352     }
0353 
0354     auto dialog = new KeySelectionDialog(parentWidgetOrView());
0355     dialog->setWindowTitle(i18nc("@title:window", "Select Certificate"));
0356     dialog->setText(i18n("Please select the certificate whose key pair you want to write to the card:"));
0357     dialog->setKeys(certificates);
0358 
0359     if (dialog->exec() == QDialog::Rejected) {
0360         return Subkey();
0361     }
0362 
0363     return dialog->selectedKey().subkey(0);
0364 }
0365 
0366 void KeyToCardCommand::Private::startKeyToPIVCard()
0367 {
0368     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()";
0369 
0370     const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
0371     if (!pivCard) {
0372         error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
0373         finished();
0374         return;
0375     }
0376 
0377     if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
0378         // key to card is only supported for the Card Authentication key and the Key Management key
0379         finished();
0380         return;
0381     }
0382 
0383     if (subkey.isNull()) {
0384         subkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard);
0385     }
0386     if (subkey.isNull()) {
0387         finished();
0388         return;
0389     }
0390     if (subkey.parent().protocol() != GpgME::CMS) {
0391         error(i18n("Sorry! This key cannot be transferred to a PIV card."));
0392         finished();
0393         return;
0394     }
0395     if (!subkey.canEncrypt() && !subkey.canSign()) {
0396         error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card."));
0397         finished();
0398         return;
0399     }
0400 
0401     // Check if we need to do the overwrite warning.
0402     if (!overwriteExistingAlreadyApproved) {
0403         const std::string existingKey = pivCard->keyInfo(cardSlot).grip;
0404         if (!existingKey.empty() && (existingKey != subkey.keyGrip())) {
0405             const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef())
0406                 ? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.")
0407                 : QString();
0408             const QString message = i18nc("@info",
0409                                           "<p>The card <em>%1</em> already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
0410                                           "<p>If there is no backup the existing key will be irrecoverably lost.</p>",
0411                                           cardDisplayName(pivCard))
0412                 + i18n("The existing key has the key grip:") + QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(existingKey)) + decryptionWarning;
0413             const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(),
0414                                                                    message,
0415                                                                    i18nc("@title:window", "Overwrite existing key"),
0416                                                                    KGuiItem{i18nc("@action:button", "Overwrite Existing Key")},
0417                                                                    KStandardGuiItem::cancel(),
0418                                                                    QString(),
0419                                                                    KMessageBox::Notify | KMessageBox::Dangerous);
0420             if (choice != KMessageBox::Continue) {
0421                 finished();
0422                 return;
0423             }
0424             overwriteExistingAlreadyApproved = true;
0425         }
0426     }
0427 
0428     const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3")
0429                             .arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()))
0430                             .arg(QString::fromStdString(cardSlot));
0431     ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
0432         keyToPIVCardDone(err);
0433     });
0434 }
0435 
0436 void KeyToCardCommand::Private::authenticate()
0437 {
0438     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()";
0439 
0440     auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
0441     cmd->setAutoResetCardToOpenPGP(false);
0442     connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() {
0443         authenticationFinished();
0444     });
0445     connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() {
0446         authenticationCanceled();
0447     });
0448     cmd->start();
0449 }
0450 
0451 void KeyToCardCommand::Private::authenticationFinished()
0452 {
0453     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()";
0454     if (!hasBeenCanceled) {
0455         startKeyToPIVCard();
0456     }
0457 }
0458 
0459 void KeyToCardCommand::Private::authenticationCanceled()
0460 {
0461     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()";
0462     hasBeenCanceled = true;
0463     canceled();
0464 }
0465 
0466 void KeyToCardCommand::Private::updateDone()
0467 {
0468     disconnect(updateConnection);
0469     const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
0470     if (!card) {
0471         error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
0472         finished();
0473         return;
0474     }
0475 
0476     const std::string keyGripOnCard = card->keyInfo(cardSlot).grip;
0477     if (keyGripOnCard != subkey.keyGrip()) {
0478         qCWarning(KLEOPATRA_LOG) << q << __func__ << "KEYTOCARD succeeded, but key on card doesn't match copied key";
0479         error(i18nc("@info", "Copying the key to the card failed."));
0480         finished();
0481         return;
0482     }
0483     keyHasBeenCopiedToCard();
0484 }
0485 
0486 void KeyToCardCommand::Private::keyHasBeenCopiedToCard()
0487 {
0488     const auto answer = KMessageBox::questionTwoActionsCancel(parentWidgetOrView(),
0489                                                               xi18nc("@info",
0490                                                                      "<para>The key has been copied to the card.</para>"
0491                                                                      "<para>You can now delete the copy of the key stored on this computer. "
0492                                                                      "Optionally, you can first create a backup of the key.</para>"),
0493                                                               i18nc("@title:window", "Success"),
0494                                                               KGuiItem{i18nc("@action:button", "Create backup")},
0495                                                               KGuiItem{i18nc("@action:button", "Delete copy on disk")},
0496                                                               KGuiItem{i18nc("@action:button", "Keep copy on disk")});
0497     if (answer == KMessageBox::ButtonCode::PrimaryAction) {
0498         const QString backupFilename = backupKey();
0499         if (backupFilename.isEmpty()) {
0500             // user canceled the backup or there was an error; repeat above question
0501             QMetaObject::invokeMethod(q, [this]() {
0502                 keyHasBeenCopiedToCard();
0503             });
0504         }
0505         backupHasBeenCreated(backupFilename);
0506     } else if (answer == KMessageBox::ButtonCode::SecondaryAction) {
0507         startDeleteSecretKeyLocally(AskForConfirmation);
0508     } else {
0509         finished();
0510     }
0511 }
0512 
0513 void KeyToCardCommand::Private::backupHasBeenCreated(const QString &backupFilename)
0514 {
0515     const auto answer =
0516         KMessageBox::questionTwoActions(parentWidgetOrView(),
0517                                         xi18nc("@info",
0518                                                "<para>The key has been copied to the card and a backup has been written to <filename>%1</filename>.</para>"
0519                                                "<para>Do you want to delete the copy of the key stored on this computer?</para>",
0520                                                backupFilename),
0521                                         i18nc("@title:window", "Success"),
0522                                         KGuiItem{i18nc("@action:button", "Delete copy on disk")},
0523                                         KGuiItem{i18nc("@action:button", "Keep copy on disk")});
0524     if (answer == KMessageBox::ButtonCode::PrimaryAction) {
0525         // the user has created a backup; don't ask again for confirmation before deleting the copy on disk
0526         startDeleteSecretKeyLocally(SkipConfirmation);
0527     } else {
0528         finished();
0529     }
0530 }
0531 
0532 namespace
0533 {
0534 QString gnupgPrivateKeyBackupExtension()
0535 {
0536     return QStringLiteral(".gpgsk");
0537 }
0538 
0539 QString proposeFilename(const Subkey &subkey)
0540 {
0541     QString filename;
0542 
0543     const auto key = subkey.parent();
0544     auto name = Formatting::prettyName(key);
0545     if (name.isEmpty()) {
0546         name = Formatting::prettyEMail(key);
0547     }
0548     const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID());
0549     const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData());
0550     const auto usage = Formatting::usageString(subkey).replace(QLatin1StringView{", "}, QLatin1String{"_"});
0551     /* Not translated so it's better to use in tutorials etc. */
0552     filename = ((shortKeyID == shortSubkeyID) //
0553                     ? QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3"}.arg(name, shortKeyID, usage)
0554                     : QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3_%4"}.arg(name, shortKeyID, shortSubkeyID, usage));
0555     filename.replace(u'/', u'_');
0556 
0557     return QDir{ApplicationState::lastUsedExportDirectory()}.filePath(filename + gnupgPrivateKeyBackupExtension());
0558 }
0559 
0560 QString requestPrivateKeyBackupFilename(const QString &proposedFilename, QWidget *parent)
0561 {
0562     auto filename = FileDialog::getSaveFileNameEx(parent,
0563                                                   i18nc("@title:window", "Backup Secret Key"),
0564                                                   QStringLiteral("imp"),
0565                                                   proposedFilename,
0566                                                   i18nc("description of filename filter", "Secret Key Backup Files") + QLatin1StringView{" (*.gpgsk)"});
0567 
0568     if (!filename.isEmpty()) {
0569         const QFileInfo fi{filename};
0570         if (fi.suffix().isEmpty()) {
0571             filename += gnupgPrivateKeyBackupExtension();
0572         }
0573         ApplicationState::setLastUsedExportDirectory(filename);
0574     }
0575 
0576     return filename;
0577 }
0578 }
0579 
0580 QString KeyToCardCommand::Private::backupKey()
0581 {
0582     static const QByteArray backupInfoName = "Backup-info:";
0583 
0584     auto keydata = readSecretKeyFile();
0585     if (keydata.empty()) {
0586         return {};
0587     }
0588     const auto filename = requestPrivateKeyBackupFilename(proposeFilename(subkey), parentWidgetOrView());
0589     if (filename.isEmpty()) {
0590         return {};
0591     }
0592 
0593     // remove old backup info
0594     Kleo::erase_if(keydata, [](const auto &line) {
0595         return line.startsWith(backupInfoName);
0596     });
0597     // prepend new backup info
0598     const QByteArrayList backupInfo = {
0599         backupInfoName,
0600         subkey.keyGrip(),
0601         QDateTime::currentDateTimeUtc().toString(Qt::ISODate).toUtf8(),
0602         "Kleopatra",
0603         Formatting::prettyNameAndEMail(subkey.parent()).toUtf8(),
0604     };
0605     keydata.insert(keydata.begin(), backupInfo.join(' ') + '\n');
0606 
0607     if (writeSecretKeyBackup(filename, keydata)) {
0608         return filename;
0609     } else {
0610         return {};
0611     }
0612 }
0613 
0614 std::vector<QByteArray> KeyToCardCommand::Private::readSecretKeyFile()
0615 {
0616     const auto filename = QString::fromLatin1(subkey.keyGrip()) + QLatin1StringView{".key"};
0617     const auto path = QDir{Kleo::gnupgPrivateKeysDirectory()}.filePath(filename);
0618 
0619     QFile file{path};
0620     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0621         error(xi18n("Cannot open the private key file <filename>%1</filename> for reading.", path));
0622         return {};
0623     }
0624 
0625     std::vector<QByteArray> lines;
0626     while (!file.atEnd()) {
0627         lines.push_back(file.readLine());
0628     }
0629     if (lines.empty()) {
0630         error(xi18n("The private key file <filename>%1</filename> is empty.", path));
0631     }
0632     return lines;
0633 }
0634 
0635 bool KeyToCardCommand::Private::writeSecretKeyBackup(const QString &filename, const std::vector<QByteArray> &keydata)
0636 {
0637     QSaveFile file{filename};
0638     // open the file in binary format because we want to write Unix line endings
0639     if (!file.open(QIODevice::WriteOnly)) {
0640         error(xi18n("Cannot open the file <filename>%1</filename> for writing.", filename));
0641         return false;
0642     }
0643     for (const auto &line : keydata) {
0644         file.write(line);
0645     }
0646     if (!file.commit()) {
0647         error(xi18n("Writing the backup of the secret key to <filename>%1</filename> failed.", filename));
0648         return false;
0649     };
0650     return true;
0651 }
0652 
0653 void KeyToCardCommand::Private::startDeleteSecretKeyLocally(Confirmation confirmation)
0654 {
0655     const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
0656     if (!card) {
0657         error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
0658         finished();
0659         return;
0660     }
0661 
0662     if (confirmation == AskForConfirmation) {
0663         const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(),
0664                                                             xi18nc("@info", "Do you really want to delete the copy of the key stored on this computer?"),
0665                                                             i18nc("@title:window", "Confirm Deletion"),
0666                                                             KStandardGuiItem::del(),
0667                                                             KStandardGuiItem::cancel(),
0668                                                             {},
0669                                                             KMessageBox::Notify | KMessageBox::Dangerous);
0670         if (answer != KMessageBox::ButtonCode::PrimaryAction) {
0671             finished();
0672             return;
0673         }
0674     }
0675 
0676     const auto cmd = QByteArray{"DELETE_KEY --force "} + subkey.keyGrip();
0677     ReaderStatus::mutableInstance()->startSimpleTransaction(card, cmd, q, [this](const GpgME::Error &err) {
0678         deleteSecretKeyLocallyFinished(err);
0679     });
0680 }
0681 
0682 void KeyToCardCommand::Private::deleteSecretKeyLocallyFinished(const GpgME::Error &err)
0683 {
0684     if (err) {
0685         error(xi18nc("@info",
0686                      "<para>Failed to delete the copy of the key stored on this computer:</para><para><message>%1</message></para>",
0687                      Formatting::errorAsString(err)));
0688     }
0689     ReaderStatus::mutableInstance()->updateStatus();
0690     success(i18nc("@info", "Successfully deleted the copy of the key stored on this computer."));
0691     finished();
0692 }
0693 
0694 KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &subkey)
0695     : CardCommand(new Private(this, subkey))
0696 {
0697 }
0698 
0699 KeyToCardCommand::KeyToCardCommand(const std::string &cardSlot, const std::string &serialNumber, const std::string &appName)
0700     : CardCommand(new Private(this, cardSlot, serialNumber, appName))
0701 {
0702 }
0703 
0704 KeyToCardCommand::~KeyToCardCommand()
0705 {
0706     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()";
0707 }
0708 
0709 namespace
0710 {
0711 bool cardSupportsKeyAlgorithm(const std::shared_ptr<const Card> &card, const std::string &keyAlgo)
0712 {
0713     if (card->appName() == OpenPGPCard::AppName) {
0714         const auto pgpCard = static_cast<const OpenPGPCard *>(card.get());
0715         const auto cardAlgos = pgpCard->supportedAlgorithms();
0716         return Kleo::any_of(cardAlgos, [keyAlgo](const auto &algo) {
0717             return (keyAlgo == algo.id) //
0718                 || (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpEncKeyRef()))
0719                 || (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpSigKeyRef()));
0720         });
0721     }
0722     return false;
0723 }
0724 }
0725 
0726 // static
0727 std::vector<std::shared_ptr<Card>> KeyToCardCommand::getSuitableCards(const GpgME::Subkey &subkey)
0728 {
0729     std::vector<std::shared_ptr<Card>> suitableCards;
0730     if (subkey.isNull() || subkey.parent().protocol() != GpgME::OpenPGP) {
0731         return suitableCards;
0732     }
0733     const auto keyAlgo = subkey.algoName();
0734     Kleo::copy_if(ReaderStatus::instance()->getCards(), std::back_inserter(suitableCards), [keyAlgo](const auto &card) {
0735         return cardSupportsKeyAlgorithm(card, keyAlgo);
0736     });
0737     return suitableCards;
0738 }
0739 
0740 void KeyToCardCommand::Private::keyToCardDone(const GpgME::Error &err)
0741 {
0742     if (!err && !err.isCanceled()) {
0743         updateConnection = connect(ReaderStatus::instance(), &ReaderStatus::updateFinished, q, [this]() {
0744             updateDone();
0745         });
0746         ReaderStatus::mutableInstance()->updateCard(serialNumber(), appName);
0747         return;
0748     }
0749     if (err) {
0750         error(xi18nc("@info", "<para>Copying the key to the card failed:</para><para><message>%1</message></para>", Formatting::errorAsString(err)));
0751     }
0752     finished();
0753 }
0754 
0755 void KeyToCardCommand::Private::keyToPIVCardDone(const GpgME::Error &err)
0756 {
0757     qCDebug(KLEOPATRA_LOG) << q << __func__ << Formatting::errorAsString(err) << "(" << err.code() << ")";
0758 #ifdef GPG_ERROR_HAS_NO_AUTH
0759     // gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
0760     if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
0761         authenticate();
0762         return;
0763     }
0764 #endif
0765     keyToCardDone(err);
0766 }
0767 
0768 void KeyToCardCommand::doStart()
0769 {
0770     qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()";
0771 
0772     d->start();
0773 }
0774 
0775 void KeyToCardCommand::doCancel()
0776 {
0777 }
0778 
0779 #undef q_func
0780 #undef d_func
0781 
0782 #include "moc_keytocardcommand.cpp"