Commit 9c0468eb authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Add new implementation of ExportSecretKeyCommand

This uses GpgME's secret key export similar to
ExportSecretSubkeyCommand.

GnuPG-bug-id: 5755
parent 84b3b924
Pipeline #121377 passed with stage
in 2 minutes and 9 seconds
......@@ -112,6 +112,7 @@ if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.16.1")
set(QGPGME_SUPPORTS_WKDLOOKUP 1)
set(QGPGME_SUPPORTS_IMPORT_WITH_FILTER 1)
set(QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1)
set(QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1)
set(QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1)
endif()
......
......@@ -48,5 +48,8 @@
/* Defined if QGpgME supports setting key origin when importing keys */
#cmakedefine QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1
/* Defined if QGpgME supports the export of secret keys */
#cmakedefine QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1
/* Defined if QGpgME supports the export of secret subkeys */
#cmakedefine QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1
......@@ -236,6 +236,7 @@ set(_kleopatra_SRCS
commands/certifycertificatecommand.cpp
commands/revokecertificationcommand.cpp
commands/selftestcommand.cpp
commands/exportsecretkeycommand.cpp
commands/exportsecretkeycommand_old.cpp
commands/exportsecretsubkeycommand.cpp
commands/exportopenpgpcertstoservercommand.cpp
......
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportsecretkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportsecretkeycommand.h"
#include "command_p.h"
#include "fileoperationspreferences.h"
#include "utils/filedialog.h"
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/Protocol>
#include <QGpgME/ExportJob>
#include <QFileInfo>
#include <QStandardPaths>
#include <algorithm>
#include <memory>
#include <vector>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
namespace
{
QString getLastUsedExportDirectory()
{
KConfigGroup config{KSharedConfig::openConfig(), "ExportDialog"};
return config.readEntry("LastDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
}
void updateLastUsedExportDirectory(const QString &path)
{
KConfigGroup config{KSharedConfig::openConfig(), "ExportDialog"};
config.writeEntry("LastDirectory", QFileInfo{path}.absolutePath());
}
QString openPGPCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate,
FileOperationsPreferences().usePGPFileExt())};
}
QString cmsCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::CMS | Class::Binary | Class::ExportedPSM,
/*usePGPFileExt=*/false)};
}
QString certificateFileExtension(GpgME::Protocol protocol)
{
switch (protocol) {
case GpgME::OpenPGP:
return openPGPCertificateFileExtension();
case GpgME::CMS:
return cmsCertificateFileExtension();
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol;
return QStringLiteral("txt");
}
}
QString proposeFilename(const Key &key)
{
QString filename;
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID());
/* Not translated so it's better to use in tutorials etc. */
filename = QStringView{u"%1_%2_SECRET"}.arg(name, shortKeyID);
filename.replace(u'/', u'_');
return getLastUsedExportDirectory() + u'/' + filename + u'.' + certificateFileExtension(key.protocol());
}
QString secretKeyFileFilters(GpgME::Protocol protocol)
{
switch (protocol) {
case GpgME::OpenPGP:
return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"};
case GpgME::CMS:
return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.p12)"};
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol;
return i18nc("description of filename filter", "All Files") + QLatin1String{" (*)"};
}
}
QString requestFilename(const Key &key, const QString &proposedFilename, QWidget *parent)
{
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18nc("@title:window", "Secret Key Backup"),
QStringLiteral("imp"),
proposedFilename,
secretKeyFileFilters(key.protocol()));
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += u'.' + certificateFileExtension(key.protocol());
}
updateLastUsedExportDirectory(filename);
}
return filename;
}
QString errorCaption()
{
return i18nc("@title:window", "Secret Key Backup Error");
}
}
class ExportSecretKeyCommand::Private : public Command::Private
{
friend class ::ExportSecretKeyCommand;
ExportSecretKeyCommand *q_func() const
{
return static_cast<ExportSecretKeyCommand *>(q);
}
public:
explicit Private(ExportSecretKeyCommand *qq, KeyListController *c = nullptr);
~Private() override;
void start();
void cancel();
private:
std::unique_ptr<QGpgME::ExportJob> startExportJob(const Key &key);
void onExportJobResult(const Error &err, const QByteArray &keyData);
void showError(const Error &err);
private:
QString filename;
QPointer<QGpgME::ExportJob> job;
};
ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportSecretKeyCommand::Private::Private(ExportSecretKeyCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
ExportSecretKeyCommand::Private::~Private() = default;
void ExportSecretKeyCommand::Private::start()
{
const Key key = this->key();
if (key.isNull()) {
finished();
return;
}
filename = requestFilename(key, proposeFilename(key), parentWidgetOrView());
if (filename.isEmpty()) {
canceled();
return;
}
auto exportJob = startExportJob(key);
if (!exportJob) {
finished();
return;
}
job = exportJob.release();
}
void ExportSecretKeyCommand::Private::cancel()
{
if (job) {
job->slotCancel();
}
job.clear();
}
std::unique_ptr<QGpgME::ExportJob> ExportSecretKeyCommand::Private::startExportJob(const Key &key)
{
#ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT
const bool armor = key.protocol() == GpgME::OpenPGP && filename.endsWith(u".asc", Qt::CaseInsensitive);
const QGpgME::Protocol *const backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr<QGpgME::ExportJob> exportJob{backend->secretKeyExportJob(armor)};
Q_ASSERT(exportJob);
connect(exportJob.get(), &QGpgME::ExportJob::result,
q, [this](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(err, keyData);
});
connect(exportJob.get(), &QGpgME::Job::progress,
q, &Command::progress);
const GpgME::Error err = exportJob->start({QLatin1String{key.primaryFingerprint()}});
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Backing up secret key..."));
return exportJob;
#else
Q_UNUSED(key)
return {};
#endif
}
void ExportSecretKeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData)
{
if (err) {
showError(err);
finished();
return;
}
if (keyData.isEmpty()) {
error(i18nc("@info", "The result of the backup is empty. Maybe you entered an empty or a wrong passphrase."),
errorCaption());
finished();
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly)) {
error(xi18nc("@info", "Cannot open file <filename>%1</filename> for writing.", filename),
errorCaption());
finished();
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18nc("@info", "Writing key to file <filename>%1</filename> failed.", filename),
errorCaption());
finished();
return;
}
information(i18nc("@info", "The backup of the secret key was created successfully."),
i18nc("@title:window", "Secret Key Backup"));
finished();
}
void ExportSecretKeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred during the backup of the secret key:</para>"
"<para><message>%1</message></para>",
QString::fromLocal8Bit(err.asString())),
errorCaption());
}
ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *view, KeyListController *controller)
: Command{view, new Private{this, controller}}
{
}
ExportSecretKeyCommand::ExportSecretKeyCommand(const GpgME::Key &key)
: Command{key, new Private{this}}
{
}
ExportSecretKeyCommand::~ExportSecretKeyCommand() = default;
void ExportSecretKeyCommand::doStart()
{
d->start();
}
void ExportSecretKeyCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_exportsecretkeycommand.cpp"
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportsecretkeycommand.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "command.h"
namespace Kleo
{
namespace Commands
{
class ExportSecretKeyCommand : public Command
{
Q_OBJECT
public:
explicit ExportSecretKeyCommand(QAbstractItemView *view, KeyListController *parent);
explicit ExportSecretKeyCommand(const GpgME::Key &key);
~ExportSecretKeyCommand() override;
/* reimp */ static Restrictions restrictions()
{
return OnlyOneKey | NeedSecretKey;
}
private:
void doStart() override;
void doCancel() override;
private:
class Private;
inline Private *d_func();
inline const Private *d_func() const;
};
}
}
......@@ -23,7 +23,11 @@
#include "ui_advancedsettingsdialog.h"
#include "commands/exportsecretkeycommand_old.h"
#ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT
# include "commands/exportsecretkeycommand.h"
#else
# include "commands/exportsecretkeycommand_old.h"
#endif
#include "commands/exportopenpgpcertstoservercommand.h"
#include "commands/exportcertificatecommand.h"
......@@ -76,7 +80,9 @@ using namespace Kleo;
using namespace Kleo::NewCertificateUi;
using namespace Kleo::Commands;
using namespace GpgME;
#ifndef QGPGME_SUPPORTS_SECRET_KEY_EXPORT
using Kleo::Commands::Compat::ExportSecretKeyCommand;
#endif
static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes";
static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes";
......
......@@ -21,7 +21,11 @@
#include "kleopatra_debug.h"
#include "commands/exportcertificatecommand.h"
#include "commands/exportopenpgpcertstoservercommand.h"
#include "commands/exportsecretkeycommand_old.h"
#ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT
# include "commands/exportsecretkeycommand.h"
#else
# include "commands/exportsecretkeycommand_old.h"
#endif
#include "commands/importcertificatefromfilecommand.h"
#include "commands/changepassphrasecommand.h"
#include "commands/lookupcertificatescommand.h"
......@@ -68,7 +72,9 @@ using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
#ifndef QGPGME_SUPPORTS_SECRET_KEY_EXPORT
using Kleo::Commands::Compat::ExportSecretKeyCommand;
#endif
class KeyListController::Private
{
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment