Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F35253394
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
22 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment