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"