Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/certificatetocardcommand.cpp b/src/commands/certificatetocardcommand.cpp
index 5615abe94..c78593059 100644
--- a/src/commands/certificatetocardcommand.cpp
+++ b/src/commands/certificatetocardcommand.cpp
@@ -1,463 +1,456 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <config-kleopatra.h>
#include "certificatetocardcommand.h"
#include "cardcommand_p.h"
#include "commands/exportpaperkeycommand.h"
#include "dialogs/copytosmartcarddialog.h"
#include "exportsecretkeycommand.h"
#include "smartcard/algorithminfo.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/readerstatus.h"
#include "smartcard/utils.h"
#include "utils/applicationstate.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/KeySelectionDialog>
#include <QGpgME/ExportJob>
#include <gpgme.h>
#include <KFileUtils>
#include <KLocalizedString>
#include <QDateTime>
#include <QDir>
#include <QStandardPaths>
#include <QStringList>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
namespace
{
struct GripAndSlot {
std::string keygrip;
std::string slot;
};
}
namespace
{
bool cardSupportsKeyAlgorithm(const std::shared_ptr<const Card> &card, const std::string &keyAlgo)
{
if (card->appName() == OpenPGPCard::AppName) {
const auto pgpCard = static_cast<const OpenPGPCard *>(card.get());
const auto cardAlgos = pgpCard->supportedAlgorithms();
return std::ranges::any_of(cardAlgos, [keyAlgo](const auto &algo) {
return (keyAlgo == algo.id) //
|| (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpEncKeyRef()))
|| (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpSigKeyRef()));
});
}
return false;
}
QString cardDisplayName(const std::shared_ptr<const Card> &card)
{
return i18nc("smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber());
}
}
class CertificateToCardCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::CertificateToCardCommand;
CertificateToCardCommand *q_func() const
{
return static_cast<CertificateToCardCommand *>(q);
}
public:
explicit Private(CertificateToCardCommand *qq);
private:
enum Confirmation {
AskForConfirmation,
SkipConfirmation,
};
void start();
void startKeyToOpenPGPCard();
void keyToCardDone(const GpgME::Error &err);
void updateDone();
void keyHasBeenCopiedToCard();
void startDeleteSecretKeyLocally(Confirmation confirmation);
void deleteSecretKeyLocallyFinished(const GpgME::Error &err);
void copyNextSubkey();
void deleteNextSubkey();
- void startCertificateToCard();
-
private:
std::vector<GripAndSlot> gripsAndSlots;
std::string appName;
std::vector<GpgME::Subkey> subkeys;
std::vector<GpgME::Subkey> remainingSubkeys;
std::string cardSlot;
QMetaObject::Connection updateConnection;
bool removeSecretKey = false;
QString exportPath;
};
CertificateToCardCommand::Private *CertificateToCardCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CertificateToCardCommand::Private *CertificateToCardCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define q q_func()
#define d d_func()
CertificateToCardCommand::Private::Private(CertificateToCardCommand *qq)
: CardCommand::Private(qq, "", nullptr)
{
}
namespace
{
static std::shared_ptr<Card> getEmptyCard(const Key &key)
{
if (key.isNull() || key.protocol() != GpgME::OpenPGP) {
return std::shared_ptr<Card>();
}
for (const auto &card : ReaderStatus::instance()->getCards()) {
const auto &subkeys = key.subkeys();
if (std::all_of(subkeys.begin(), subkeys.end(), [card](const auto &subkey) {
const auto keyAlgo = subkey.algoName();
return cardSupportsKeyAlgorithm(card, keyAlgo);
})) {
if (!card->signingKeyRef().empty() && card->keyFingerprint(card->signingKeyRef()).empty() && !card->encryptionKeyRef().empty()
&& card->keyFingerprint(card->encryptionKeyRef()).empty() && !card->authenticationKeyRef().empty()
&& card->keyFingerprint(card->authenticationKeyRef()).empty() && card->appName() == OpenPGPCard::AppName) {
return card;
}
}
}
return std::shared_ptr<Card>();
}
}
void CertificateToCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToCardCommand::Private::start()";
const auto card = getEmptyCard(key());
if (!card) {
- error(i18nc("@info", "No empty card was found."));
+ error(i18nc("@info", "No empty smart card was found."));
finished();
return;
}
Dialogs::CopyToSmartcardDialog dialog(parentWidgetOrView());
dialog.setKey(key());
dialog.setCardDisplayName(cardDisplayName(card));
dialog.exec();
if (dialog.result() == QDialog::Rejected) {
finished();
return;
}
setSerialNumber(card->serialNumber());
appName = card->appName();
auto choice = dialog.backupChoice();
if (choice != Dialogs::CopyToSmartcardDialog::KeepKey) {
removeSecretKey = true;
}
if (choice == Dialogs::CopyToSmartcardDialog::FileBackup) {
auto command = new ExportSecretKeyCommand(key());
command->setInteractive(false);
auto name = Formatting::prettyName(key());
if (name.isEmpty()) {
name = Formatting::prettyEMail(key());
}
auto filename = QStringLiteral("%1_%2_secret.asc").arg(name, Formatting::prettyKeyID(key().shortKeyID()));
- const auto dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
- if (QFileInfo::exists(QStringLiteral("%1/%2").arg(dir, filename))) {
- filename = KFileUtils::suggestName(QUrl::fromLocalFile(dir), filename);
+ const auto dir = QDir{QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)};
+ if (dir.exists(filename)) {
+ filename = KFileUtils::suggestName(QUrl::fromLocalFile(dir.path()), filename);
}
- exportPath = QStringLiteral("%1/%2").arg(dir, filename);
+ exportPath = dir.absoluteFilePath(filename);
command->setFileName(exportPath);
command->start();
connect(command, &Command::finished, q, [this, command]() {
if (!command->success()) {
finished();
return;
}
startKeyToOpenPGPCard();
});
} else if (choice == Dialogs::CopyToSmartcardDialog::PrintBackup) {
auto exportPaperKey = new ExportPaperKeyCommand(key());
exportPaperKey->start();
connect(exportPaperKey, &ExportPaperKeyCommand::finished, q, [this, exportPaperKey]() {
if (!exportPaperKey->success()) {
return;
}
startKeyToOpenPGPCard();
});
} else {
startKeyToOpenPGPCard();
}
}
namespace
{
static std::string getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey)
{
- // Check if we need to ask the user for the slot
if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) {
// Signing only
return OpenPGPCard::pgpSigKeyRef();
}
if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) {
// Encrypt only
return OpenPGPCard::pgpEncKeyRef();
}
if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) {
// Auth only
return OpenPGPCard::pgpAuthKeyRef();
}
return {};
}
}
void CertificateToCardCommand::Private::startKeyToOpenPGPCard()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToCardCommand::Private::startKeyToOpenPGPCard()";
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 ((subkeys.empty() || subkeys[0].isNull()) && key().isNull()) {
finished();
return;
}
if ((!subkeys.empty() && !subkeys[0].isNull() && subkeys[0].parent().protocol() != GpgME::OpenPGP)
|| (!key().isNull() && key().protocol() != GpgME::OpenPGP)) {
error(i18n("Sorry! This key cannot be transferred to an OpenPGP card."));
finished();
return;
}
if (!key().isNull()) {
subkeys = key().subkeys();
}
remainingSubkeys = subkeys;
copyNextSubkey();
}
void CertificateToCardCommand::Private::updateDone()
{
disconnect(updateConnection);
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;
}
for (const auto &gripAndSlot : gripsAndSlots) {
const std::string keyGripOnCard = card->keyInfo(gripAndSlot.slot).grip;
if (keyGripOnCard != gripAndSlot.keygrip) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "KEYTOCARD succeeded, but key on card doesn't match copied key";
error(i18nc("@info", "Copying the key to the card failed."));
finished();
return;
}
}
keyHasBeenCopiedToCard();
}
void CertificateToCardCommand::Private::keyHasBeenCopiedToCard()
{
if (removeSecretKey) {
startDeleteSecretKeyLocally(AskForConfirmation);
}
if (exportPath.isEmpty()) {
- information(xi18nc("@info", "<para>The key was copied to the smartcard.</para>"));
+ information(xi18nc("@info", "<para>The key was copied to the smart card.</para>"));
} else {
information(
- xi18nc("@info", "<para>The key was copied to the smartcard.</para><para>A backup was exported to <filename>%1</filename></para>", exportPath));
+ xi18nc("@info", "<para>The key was copied to the smart card.</para><para>A backup was exported to <filename>%1</filename></para>", exportPath));
}
}
void CertificateToCardCommand::Private::startDeleteSecretKeyLocally(Confirmation confirmation)
{
if (confirmation == AskForConfirmation) {
const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(),
- xi18nc("@info", "Do you really want to delete the copy of the key stored on this computer?"),
+ xi18nc("@info", "Do you really want to delete the copy of the secret key stored on this computer?"),
i18nc("@title:window", "Confirm Deletion"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
if (answer != KMessageBox::ButtonCode::PrimaryAction) {
finished();
return;
}
}
remainingSubkeys = subkeys;
deleteNextSubkey();
}
void CertificateToCardCommand::Private::deleteSecretKeyLocallyFinished(const GpgME::Error &err)
{
if (err) {
error(xi18nc("@info",
- "<para>Failed to delete the copy of the key stored on this computer:</para><para><message>%1</message></para>",
+ "<para>Failed to delete the copy of the secret key stored on this computer:</para><para><message>%1</message></para>",
Formatting::errorAsString(err)));
}
ReaderStatus::mutableInstance()->updateStatus();
finished();
}
CertificateToCardCommand::CertificateToCardCommand(QAbstractItemView *view, KeyListController *controller)
: CardCommand(new Private(this), view)
{
Q_UNUSED(controller);
}
CertificateToCardCommand::~CertificateToCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToCardCommand::~CertificateToCardCommand()";
}
void CertificateToCardCommand::Private::keyToCardDone(const GpgME::Error &err)
{
if (err.isCanceled()) {
finished();
return;
}
if (err) {
error(xi18nc("@info", "<para>Copying the key to the card failed:</para><para><message>%1</message></para>", Formatting::errorAsString(err)));
}
updateConnection = connect(ReaderStatus::instance(), &ReaderStatus::updateFinished, q, [this]() {
updateDone();
});
ReaderStatus::mutableInstance()->updateCard(serialNumber(), appName);
}
void CertificateToCardCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToCardCommand::doStart()";
d->start();
}
void CertificateToCardCommand::doCancel()
{
}
void CertificateToCardCommand::Private::copyNextSubkey()
{
const auto subkey = remainingSubkeys[remainingSubkeys.size() - 1];
remainingSubkeys.pop_back();
auto cardSlot = getOpenPGPCardSlotForKey(subkey);
if (cardSlot.empty()) {
finished();
return;
}
gripsAndSlots.push_back(GripAndSlot{
std::string(subkey.keyGrip()),
cardSlot,
});
const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard<OpenPGPCard>(serialNumber());
if (!pgpCard) {
error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const auto time = QDateTime::fromSecsSinceEpoch(quint32(subkey.creationTime()), QTimeZone::utc());
const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss"));
const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3 %4")
.arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()), QString::fromStdString(cardSlot), timestamp);
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
- if (remainingSubkeys.size() > 0) {
+ if (!err && !err.isCanceled() && !remainingSubkeys.empty()) {
QMetaObject::invokeMethod(
q,
[this]() {
copyNextSubkey();
},
Qt::QueuedConnection);
} else {
keyToCardDone(err);
}
});
}
void CertificateToCardCommand::Private::deleteNextSubkey()
{
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;
}
const auto subkey = remainingSubkeys[remainingSubkeys.size() - 1];
remainingSubkeys.pop_back();
const auto cmd = QByteArray{"DELETE_KEY --force "} + subkey.keyGrip();
ReaderStatus::mutableInstance()->startSimpleTransaction(card, cmd, q, [this](const GpgME::Error &err) {
- if (err) {
+ if (err || err.isCanceled() || remainingSubkeys.empty()) {
deleteSecretKeyLocallyFinished(err);
} else {
- if (remainingSubkeys.empty()) {
- deleteSecretKeyLocallyFinished(err);
- } else {
- QMetaObject::invokeMethod(
- q,
- [this]() {
- deleteNextSubkey();
- },
- Qt::QueuedConnection);
- }
+ QMetaObject::invokeMethod(
+ q,
+ [this]() {
+ deleteNextSubkey();
+ },
+ Qt::QueuedConnection);
}
});
}
#undef q_func
#undef d_func
#include "moc_certificatetocardcommand.cpp"
diff --git a/src/dialogs/copytosmartcarddialog.cpp b/src/dialogs/copytosmartcarddialog.cpp
index 061eb6635..47764030c 100644
--- a/src/dialogs/copytosmartcarddialog.cpp
+++ b/src/dialogs/copytosmartcarddialog.cpp
@@ -1,169 +1,169 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "copytosmartcarddialog.h"
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <QButtonGroup>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include <QVBoxLayout>
using namespace Kleo;
using namespace Kleo::Dialogs;
class CopyToSmartcardDialog::Private
{
friend class ::Kleo::Dialogs::CopyToSmartcardDialog;
CopyToSmartcardDialog *const q;
public:
explicit Private(CopyToSmartcardDialog *qq);
private:
GpgME::Key key;
QString cardDisplayName;
struct UI {
QLabel *label = nullptr;
QDialogButtonBox *buttonBox = nullptr;
QRadioButton *deleteRadio = nullptr;
QRadioButton *fileBackupRadio = nullptr;
QRadioButton *printBackupRadio = nullptr;
QRadioButton *existingBackupRadio = nullptr;
QRadioButton *keepRadio = nullptr;
QPushButton *acceptButton = nullptr;
} ui;
void setUpUI(CopyToSmartcardDialog *q)
{
auto layout = new QVBoxLayout;
q->setLayout(layout);
ui.label = new QLabel;
layout->addWidget(ui.label);
layout->addStretch(1);
- ui.deleteRadio = new QRadioButton(i18nc("@option:radio", "Delete key from disk."), q);
- ui.keepRadio = new QRadioButton(i18nc("@option:radio", "Keep key on disk."), q);
+ ui.deleteRadio = new QRadioButton(i18nc("@option:radio", "Delete secret key from disk."), q);
+ ui.keepRadio = new QRadioButton(i18nc("@option:radio", "Keep secret key on disk."), q);
auto spacingLayout = new QHBoxLayout;
spacingLayout->addSpacing(32);
auto backupLayout = new QVBoxLayout;
spacingLayout->addLayout(backupLayout);
ui.fileBackupRadio = new QRadioButton(i18nc("@option:radio", "Make a backup of the secret key to a file."));
ui.printBackupRadio = new QRadioButton(i18nc("@option:radio", "Make a printed backup of the secret key."));
ui.existingBackupRadio = new QRadioButton(i18nc("@option:radio", "I already have a backup of the secret key."));
ui.fileBackupRadio->setEnabled(false);
ui.printBackupRadio->setEnabled(false);
ui.existingBackupRadio->setEnabled(false);
connect(ui.deleteRadio, &QRadioButton::toggled, ui.fileBackupRadio, &QRadioButton::setEnabled);
connect(ui.deleteRadio, &QRadioButton::toggled, ui.printBackupRadio, &QRadioButton::setEnabled);
connect(ui.deleteRadio, &QRadioButton::toggled, ui.existingBackupRadio, &QRadioButton::setEnabled);
connect(ui.deleteRadio, &QRadioButton::toggled, q, &CopyToSmartcardDialog::checkAcceptable);
connect(ui.fileBackupRadio, &QRadioButton::toggled, q, &CopyToSmartcardDialog::checkAcceptable);
connect(ui.printBackupRadio, &QRadioButton::toggled, q, &CopyToSmartcardDialog::checkAcceptable);
connect(ui.existingBackupRadio, &QRadioButton::toggled, q, &CopyToSmartcardDialog::checkAcceptable);
connect(ui.keepRadio, &QRadioButton::toggled, q, &CopyToSmartcardDialog::checkAcceptable);
auto buttons = new QButtonGroup(q);
buttons->addButton(ui.fileBackupRadio);
buttons->addButton(ui.printBackupRadio);
buttons->addButton(ui.existingBackupRadio);
backupLayout->addWidget(ui.fileBackupRadio);
backupLayout->addWidget(ui.printBackupRadio);
backupLayout->addWidget(ui.existingBackupRadio);
layout->addWidget(ui.deleteRadio);
layout->addLayout(spacingLayout);
layout->addWidget(ui.keepRadio);
ui.buttonBox = new QDialogButtonBox;
auto cancelButton = ui.buttonBox->addButton(QDialogButtonBox::Cancel);
connect(cancelButton, &QPushButton::clicked, q, &QDialog::reject);
ui.acceptButton = ui.buttonBox->addButton(i18nc("@action:button", "Copy to Card"), QDialogButtonBox::AcceptRole);
- connect(ui.acceptButton, &QPushButton::clicked, q, &QDialog::accept);
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept);
ui.acceptButton->setEnabled(false);
ui.acceptButton->setIcon(QIcon::fromTheme(QStringLiteral("auth-sim-locked")));
layout->addWidget(ui.buttonBox);
}
void update();
};
CopyToSmartcardDialog::Private::Private(CopyToSmartcardDialog *qq)
: q(qq)
{
setUpUI(q);
}
CopyToSmartcardDialog::CopyToSmartcardDialog(QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
setWindowTitle(i18nc("@title:dialog", "Copy Key to Smartcard"));
}
CopyToSmartcardDialog::~CopyToSmartcardDialog() = default;
GpgME::Key CopyToSmartcardDialog::key() const
{
return d->key;
}
void CopyToSmartcardDialog::setKey(const GpgME::Key &key)
{
d->key = key;
d->update();
}
void CopyToSmartcardDialog::Private::update()
{
ui.label->setText(xi18nc("@info",
"<para>Selected Key: <emphasis>%1</emphasis></para><para>Selected Smartcard: <emphasis>%2</emphasis></para><para>Choose one of "
"the following options to continue:</para>",
Formatting::summaryLine(key),
cardDisplayName));
}
QString CopyToSmartcardDialog::cardDisplayName() const
{
return d->cardDisplayName;
}
void CopyToSmartcardDialog::setCardDisplayName(const QString &cardDisplayName)
{
d->cardDisplayName = cardDisplayName;
d->update();
}
void CopyToSmartcardDialog::checkAcceptable()
{
d->ui.acceptButton->setEnabled(
d->ui.keepRadio->isChecked()
|| d->ui.deleteRadio && (d->ui.fileBackupRadio->isChecked() || d->ui.printBackupRadio->isChecked() || d->ui.existingBackupRadio->isChecked()));
}
CopyToSmartcardDialog::BackupChoice CopyToSmartcardDialog::backupChoice() const
{
if (d->ui.keepRadio->isChecked()) {
return BackupChoice::KeepKey;
} else if (d->ui.fileBackupRadio->isChecked()) {
return BackupChoice::FileBackup;
} else if (d->ui.printBackupRadio->isChecked()) {
return BackupChoice::PrintBackup;
}
return BackupChoice::ExistingBackup;
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Feb 4, 4:15 PM (1 d, 6 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c7/23/05c01a5fbd583883488e2eaf2439

Event Timeline