Re: Support WKS publishing from within Kleopatra
Felix Tiede <[email protected]>
| Newsgroups | gmane.comp.kde.devel.pim |
|---|---|
| Message-ID | <3301521.Luzu6u9BVM@pip> |
Hey Ingo, Am Dienstag, 5. April 2022, 09:24:20 CEST schrieb Ingo Klöcker: > Hi Felix, > > On Montag, 4. April 2022 17:42:15 CEST Felix Tiede wrote: > > Kmail's account wizard already supports it, yet it is unavailable for > > existing keys and accounts: Publish a PGP key on the mail provider's WKD > > using the WKS process. > > Yes, that's still missing in Kleopatra. See https://dev.gnupg.org/T5334 > which is the high-level task for improving the support for WKS/WKD in > Kleopatra. Interesting. My patch so far only supports publishing a key, not searching/downloading it. > > > Some time ago I fiddled with it, basically copying src/commands/ > > exportopenpgpcertstoserver.*, modifying it to create a publish request > > with > > a command line call to gpg-wks-client, then reading that request from a > > temporary file, building an email from it and attempting to send it using > > the Akonadi transport associated with the sender mail address. > > That patch was against Kleopatra-18.x. > > > > Back then it was only supported to publish at the key's first user id's > > provider and address, it has meanwhile been amended to support publishing > > for each key's user id. > > Sounds good. Thanks. It's currently implemented as a context menu entry for each User ID, like the entry for the key itself in the keylist, where the latter uses the key's first user id at all times, the former tries to get a mail transport for the ID before trying. > > > The other day I finally got round to reproduce the same with > > Kleopatra-21.12, which in turn required a patch for Libkleo-21.12, since I > > need Libkleo to provide me with the path to the gpg-wks-client binary. > > > > And now for the point of writing it all: > > I want to write a MR for Kleopatra to get the feature into the main > > distribution of Kleopatra. > > So, as a first step I created a MR for Libkleo to provide Kleopatra with > > the path to the gpg-wks-client binary - and in the course of this MR I've > > learned about QGpgME namespace which provides a direct interface to > > GnuPG's WKS client. > > > > However, since I'm not really that proficient in rewriting my old patch to > > work with QGpgME::WKSPublishJob, I am afraid I'd need assistance with > > that. > > That's what this mailing list is about. For more direct assistance we could > also meet at the #kontact IRC channel at Libera.Chat. I'm rarely on IRC these days, but it's an option, yes. > > > So, would a MR with "the old way" of doing it - calling an external > > binary, > > creating, writing, reading, parsing a temporary file - be accepted into > > Kleopatra or do I need to fully rewrite it for QGpgME::WKSPublishJob > > before > > creating the MR? > > I'm afraid that I won't accept such an MR. Using QGpgME Jobs is the > recommended way to interact with GnuPG via GpgME. (Another way is via Assuan > transactions, e.g. for smartcard support, but that's not an option here.) I've attached the (old) kleopatra-21.12.3 patch to this mail, so you can have a look at what I did and we can discuss porting it to QGpgME. Or I can create a MR anyway and we can discuss via the review process, whatever works better. > > BTW, I don't know your current status, e.g. whether you are a student, but > in case you are interested: I'm pretty sure g10 Code GmbH would pay for > your work. They are looking for developers and maybe even student workers. Actually, I'm neither a professional developer nor looking for a job. If g10 Code is indeed interested in paying me for this work, it would be possible for me to accept payment in a legal manner with appropriate taxing information, though. Regards, Felix
kleopatra-21.12.3-wks.patch
(text/x-patch, 16.4 KB)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2cb93570..8f74be25 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,6 +29,8 @@ option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF)
# Standalone build. Find / include everything necessary.
set(KF5_MIN_VERSION "5.87.0")
+set(KIDENTITYMANAGEMENT_VERSION "5.19.3")
+set(KMAILTRANSPORT_VERSION "5.19.3")
set(KMIME_VERSION "5.19.3")
set(LIBKLEO_VERSION "5.19.3")
set(QT_REQUIRED_VERSION "5.15.2")
@@ -112,6 +114,9 @@ endif()
# Kdepimlibs packages
find_package(KF5Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KF5Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED)
+find_package(KF5IdentityManagement ${KIDENTITYMANAGEMENT_VERSION} CONFIG REQUIRED)
+find_package(KF5MailTransport ${KMAILTRANSPORT_VERSION} CONFIG REQUIRED)
+find_package(KF5MailTransportAkonadi ${KMAILTRANSPORT_VERSION} CONFIG REQUIRED)
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 52fe5f16..ad025c08 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -242,6 +242,7 @@ set(_kleopatra_SRCS
commands/selftestcommand.cpp
commands/exportsecretkeycommand.cpp
commands/exportopenpgpcertstoservercommand.cpp
+ commands/exportopenpgpcerttoprovidercommand.cpp
commands/adduseridcommand.cpp
commands/newcertificatecommand.cpp
commands/setinitialpincommand.cpp
@@ -370,6 +371,9 @@ target_link_libraries(kleopatra_bin
KF5::CoreAddons
KF5::ItemModels
KF5::Crash
+ KF5::IdentityManagement # Export OpenPGP keys using WKS
+ KF5::MailTransport
+ KF5::MailTransportAkonadi
Qt::Network
Qt::PrintSupport # Printing secret keys
${_kleopatra_uiserver_extra_libs}
diff --git a/src/commands/exportopenpgpcerttoprovidercommand.cpp b/src/commands/exportopenpgpcerttoprovidercommand.cpp
new file mode 100644
index 00000000..d50f7ade
--- /dev/null
+++ b/src/commands/exportopenpgpcerttoprovidercommand.cpp
@@ -0,0 +1,188 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ commands/exportopenpgpcerttoprovidercommand.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
+ SPDX-FileCopyrightText: 2019-2022 Felix Tiede
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <config-kleopatra.h>
+
+#include "exportopenpgpcerttoprovidercommand.h"
+
+#include "command_p.h"
+
+#include <Libkleo/GnuPG>
+
+#include <gpgme++/key.h>
+
+#include <kidentitymanagement/identity.h>
+#include <MailTransport/TransportManager>
+#include <MailTransportAkonadi/MessageQueueJob>
+
+#include <KLocalizedString>
+#include <KMessageBox>
+
+#include <QString>
+
+using namespace Kleo;
+using namespace Kleo::Commands;
+using namespace GpgME;
+
+const KIdentityManagement::IdentityManager *ExportOpenPGPCertToProviderCommand::mailIdManager = new KIdentityManagement::IdentityManager(true);
+
+ExportOpenPGPCertToProviderCommand::ExportOpenPGPCertToProviderCommand(KeyListController *c)
+ : GnuPGProcessCommand(c),
+ uid(GpgME::UserID())
+{
+ wksMail.open();
+ wksMail.close();
+}
+
+ExportOpenPGPCertToProviderCommand::ExportOpenPGPCertToProviderCommand(QAbstractItemView *v, KeyListController *c)
+ : GnuPGProcessCommand(v, c),
+ uid(GpgME::UserID())
+{
+ wksMail.open();
+ wksMail.close();
+}
+
+ExportOpenPGPCertToProviderCommand::ExportOpenPGPCertToProviderCommand(const Key &key)
+ : GnuPGProcessCommand(key),
+ uid(GpgME::UserID())
+{
+ wksMail.open();
+ wksMail.close();
+}
+
+ExportOpenPGPCertToProviderCommand::ExportOpenPGPCertToProviderCommand(const UserID &uid)
+ : GnuPGProcessCommand(uid.parent()),
+ uid(uid)
+{
+ wksMail.open();
+ wksMail.close();
+}
+
+ExportOpenPGPCertToProviderCommand::~ExportOpenPGPCertToProviderCommand() {}
+
+bool ExportOpenPGPCertToProviderCommand::preStartHook(QWidget *parent) const
+{
+ QString sender;
+ if (uid.isNull())
+ sender = QString::fromLatin1(d->keys().at(0).userID(0).addrSpec().data());
+ else
+ sender = QString::fromLatin1(uid.addrSpec().data());
+
+ KIdentityManagement::Identity identity = ExportOpenPGPCertToProviderCommand::mailIdManager->identityForAddress(sender);
+ if (identity.isNull())
+ identity = ExportOpenPGPCertToProviderCommand::mailIdManager->defaultIdentity();
+
+ if (identity.transport().isEmpty()) {
+ KMessageBox::error(parent,
+ xi18nc("@warning",
+ "<para><email>%1</email> has no usable transport for mailing a key available, "
+ "WKS upload not possible.</para>", sender),
+ xi18nc("@title:window", "OpenPGP Certificate Export"));
+ return false;
+ }
+ return KMessageBox::warningContinueCancel(parent,
+ xi18nc("@info",
+ "<para>Not every mail provider supports WKS, so any key being "
+ "exported this way may fail individually.</para><para>If exported, "
+ "a confirmation request mail will be sent to <email>%1</email> "
+ "which needs to be acknowledged with a mail program to complete the "
+ "export process.</para><para><application>KMail</application> "
+ "can handle these mails, but not all mail programs can.</para>"
+ "<para>Once exported, the standard does not (yet) allow for "
+ "automated removal of a published key.</para>"
+ "<para>Are you sure you want to continue?</para>", sender),
+ xi18nc("@title:window", "OpenPGP Certificate Export"),
+ KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
+ QStringLiteral("warn-export-openpgp-wks-unsupported"))
+ == KMessageBox::Continue;
+}
+
+void ExportOpenPGPCertToProviderCommand::postSuccessHook(QWidget *parent)
+{
+ QString sender;
+ if (uid.isNull())
+ sender = QString::fromLatin1(d->keys().at(0).userID(0).addrSpec().data());
+ else
+ sender = QString::fromLatin1(uid.addrSpec().data());
+
+ KIdentityManagement::Identity identity = ExportOpenPGPCertToProviderCommand::mailIdManager->identityForAddress(sender);
+ if (identity.isNull())
+ identity = ExportOpenPGPCertToProviderCommand::mailIdManager->defaultIdentity();
+ MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportByName(
+ identity.transport());
+
+ if (!transport)
+ return;
+
+ wksMail.open();
+ KMime::Message *msg = new KMime::Message();
+
+ msg->setContent(KMime::CRLFtoLF(wksMail.readAll()));
+ msg->parse();
+ wksMail.close();
+
+ MailTransport::MessageQueueJob *job = new MailTransport::MessageQueueJob(parent);
+ job->transportAttribute().setTransportId(transport->id());
+ job->addressAttribute().setFrom(msg->from()->asUnicodeString());
+ job->addressAttribute().setTo(msg->to()->displayNames());
+ job->setMessage(KMime::Message::Ptr(msg));
+
+ job->start();
+}
+
+QStringList ExportOpenPGPCertToProviderCommand::arguments() const
+{
+ QStringList result;
+ result << gpgWksClientPath();
+ result << QStringLiteral("--output") << wksMail.fileName();
+ result << QStringLiteral("--create");
+ Q_FOREACH (const Key &key, d->keys()) {
+ result << QLatin1String(key.primaryFingerprint());
+ if (!uid.isNull()) {
+ result << QLatin1String(uid.addrSpec().data());
+ }
+ else {
+ result << QLatin1String(key.userID(0).email());
+ }
+ }
+ return result;
+}
+
+QString ExportOpenPGPCertToProviderCommand::errorCaption() const
+{
+ return i18nc("@title:window", "OpenPGP Certificate Export Error");
+}
+
+QString ExportOpenPGPCertToProviderCommand::successCaption() const
+{
+ return i18nc("@title:window", "OpenPGP Certificate Export Finished");
+}
+
+QString ExportOpenPGPCertToProviderCommand::crashExitMessage(const QStringList &args) const
+{
+ return xi18nc("@info",
+ "<para>The GPG process that tried to export OpenPGP certificates "
+ "ended prematurely because of an unexpected error.</para>"
+ "<para>Please check the output of <icode>%1</icode> for details.</para>", args.join(QLatin1Char(' ')));
+}
+
+QString ExportOpenPGPCertToProviderCommand::errorExitMessage(const QStringList &args) const
+{
+ return xi18nc("@info",
+ "<para>An error occurred while trying to export OpenPGP certificates.</para> "
+ "<para>The output from <command>%1</command> was: <message>%2</message></para>",
+ args[0], errorString());
+}
+
+QString ExportOpenPGPCertToProviderCommand::successMessage(const QStringList&) const
+{
+ return i18nc("@info", "OpenPGP certificates exported successfully.");
+}
+
diff --git a/src/commands/exportopenpgpcerttoprovidercommand.h b/src/commands/exportopenpgpcerttoprovidercommand.h
new file mode 100644
index 00000000..f7e1798d
--- /dev/null
+++ b/src/commands/exportopenpgpcerttoprovidercommand.h
@@ -0,0 +1,65 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ commands/exportopenpgpcertstoservercommand.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
+ SPDX-FileCopyrightText: 2019 Felix Tiede
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef __KLEOPATRA_COMMMANDS_EXPORTOPENPGPCERTTOPROVIDERCOMMAND_H__
+#define __KLEOPATRA_COMMMANDS_EXPORTOPENPGPCERTTOPROVIDERCOMMAND_H__
+
+#include <commands/gnupgprocesscommand.h>
+
+#include <kidentitymanagement/identitymanager.h>
+
+#include <QtCore/QTemporaryFile>
+
+#include <gpgme++/key.h>
+
+namespace Kleo
+{
+namespace Commands
+{
+
+class ExportOpenPGPCertToProviderCommand : public GnuPGProcessCommand
+{
+ Q_OBJECT
+public:
+ explicit ExportOpenPGPCertToProviderCommand(QAbstractItemView *view, KeyListController *parent);
+ explicit ExportOpenPGPCertToProviderCommand(KeyListController *parent);
+ explicit ExportOpenPGPCertToProviderCommand(const GpgME::Key &key);
+ explicit ExportOpenPGPCertToProviderCommand(const GpgME::UserID &uid);
+
+ ~ExportOpenPGPCertToProviderCommand() override;
+
+ static Restrictions restrictions()
+ {
+ return OnlyOneKey | NeedSecretKey | MustBeOpenPGP;
+ }
+
+private:
+ bool preStartHook(QWidget *) const override;
+ void postSuccessHook(QWidget *) override;
+
+ QStringList arguments() const override;
+
+ QString errorCaption() const override;
+ QString successCaption() const override;
+
+ QString crashExitMessage(const QStringList &) const override;
+ QString errorExitMessage(const QStringList &) const override;
+ QString successMessage(const QStringList &) const override;
+
+ GpgME::UserID uid;
+
+ QTemporaryFile wksMail;
+ static const KIdentityManagement::IdentityManager *mailIdManager;
+};
+
+}
+}
+
+#endif // __KLEOPATRA_COMMMANDS_EXPORTOPENPGPCERTTOPROVIDERCOMMAND_H__
diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp
index f637075e..608e5ead 100644
--- a/src/dialogs/certificatedetailswidget.cpp
+++ b/src/dialogs/certificatedetailswidget.cpp
@@ -1,5 +1,6 @@
/* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Intevation GmbH
+ SPDX-FileCopyrightText: 2019-2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -17,6 +18,7 @@
#include "commands/changepassphrasecommand.h"
#include "commands/changeexpirycommand.h"
#include "commands/certifycertificatecommand.h"
+#include "commands/exportopenpgpcerttoprovidercommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/adduseridcommand.h"
#include "commands/genrevokecommand.h"
@@ -90,6 +92,7 @@ public:
void webOfTrustClicked();
void exportClicked();
void addUserID();
+ void exportUserIDToProvider();
void changePassphrase();
void changeExpiration();
void keysMayHaveChanged();
@@ -599,6 +602,16 @@ void CertificateDetailsWidget::Private::addUserID()
cmd->start();
}
+void CertificateDetailsWidget::Private::exportUserIDToProvider()
+{
+ auto userID = key.userID(0);
+
+ auto item = ui.userIDTable->currentItem();
+ if (item) {
+ userID = item->data(0, Qt::UserRole).value<GpgME::UserID>();
+ }
+}
+
void CertificateDetailsWidget::Private::keysMayHaveChanged()
{
auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint());
@@ -643,6 +656,17 @@ void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QP
});
cmd->start();
});
+ if (key.hasSecret() && key.protocol() == GpgME::OpenPGP) {
+ menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export-provider")),
+ i18n("Publish at mail provider ..."),
+ q, [this, userID]() {
+ auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(userID);
+ ui.userIDTable->setEnabled(false);
+ connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished,
+ q, [this]() { ui.userIDTable->setEnabled(true); });
+ cmd->start();
+ });
+ }
if (Kleo::Commands::RevokeCertificationCommand::isSupported()) {
menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")),
i18n("Revoke Certification..."),
diff --git a/src/kleopatra.rc b/src/kleopatra.rc
index 13281f5d..79434dbf 100644
--- a/src/kleopatra.rc
+++ b/src/kleopatra.rc
@@ -12,6 +12,7 @@
<Action name="file_export_secret_keys"/>
<Action name="file_export_paper_key"/>
<Action name="file_export_certificates_to_server"/>
+ <Action name="file_export_certificate_to_provider"/>
<Separator/>
<Action name="file_decrypt_verify_files"/>
<Action name="file_sign_encrypt_files"/>
@@ -126,6 +127,7 @@
<Action name="file_export_secret_keys"/>
<Action name="file_export_paper_key"/>
<Action name="file_export_certificates_to_server"/>
+ <Action name="file_export_certificate_to_provider"/>
<Separator/>
<Action name="view_certificate_details"/>
</Menu>
diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp
index ba1d8004..7deaea6d 100644
--- a/src/view/keylistcontroller.cpp
+++ b/src/view/keylistcontroller.cpp
@@ -3,6 +3,7 @@
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
+ SPDX-FileCopyrightText: 2019-2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -20,6 +21,7 @@
#include "kleopatra_debug.h"
#include "commands/exportcertificatecommand.h"
#include "commands/exportopenpgpcertstoservercommand.h"
+#include "commands/exportopenpgpcerttoprovidercommand.h"
#include "commands/exportsecretkeycommand.h"
#include "commands/importcertificatefromfilecommand.h"
#include "commands/changepassphrasecommand.h"
@@ -358,6 +360,10 @@ void KeyListController::createActions(KActionCollection *coll)
"file_export_certificates_to_server", i18n("Publish on Server..."), i18n("Publish the selected certificate (public key) on a public keyserver"),
"view-certificate-export-server", nullptr, nullptr, QStringLiteral("Ctrl+Shift+E"), false, true
},
+ {
+ "file_export_certificate_to_provider", i18n("Publish at Mailprovider..."), i18n("Publish the selected certificate (public key) at mail provider's Web Key Directory if offered"),
+ "view-certificate-export-provider", nullptr, nullptr, QString(), false, true
+ },
{
"file_export_secret_keys", i18n("Backup Secret Keys..."), QString(),
"view-certificate-export-secret", nullptr, nullptr, QString(), false, true
@@ -491,6 +497,7 @@ void KeyListController::createActions(KActionCollection *coll)
registerActionForCommand<ExportSecretKeyCommand>(coll->action(QStringLiteral("file_export_secret_keys")));
registerActionForCommand<ExportPaperKeyCommand>(coll->action(QStringLiteral("file_export_paper_key")));
registerActionForCommand<ExportOpenPGPCertsToServerCommand>(coll->action(QStringLiteral("file_export_certificates_to_server")));
+ registerActionForCommand<ExportOpenPGPCertToProviderCommand>(coll->action(QStringLiteral("file_export_certificate_to_provider")));
//---
registerActionForCommand<DecryptVerifyFilesCommand>(coll->action(QStringLiteral("file_decrypt_verify_files")));
registerActionForCommand<SignEncryptFilesCommand>(coll->action(QStringLiteral("file_sign_encrypt_files")));
signature.asc
(application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEJijiGzDq8UDHCPC0QGDR0So3spMFAmJMJ7UACgkQQGDR0So3 spP8yxAArk5gaHW65wjVJaPdkhEgovCKWngclMXGK9oapzYkXvRi2Cj3hGOwkzfx tkW/FLJlFiWAHyOvQ97wDkkB4qLERiAdHD6L9zWSlfNpwv4P+lVjW7n8V/8m2eKD 6WFjL+61gtPDhpv9qdB1bDJXZxBBrS+EI1L7cqvdeD2f/t8ucX5cTCK+CaxMYgmp dUBJBdfdQvuqPkd2lY0sl491iFTK1TzjFA7o2oolcPUdujGmDgDyf9zovE+Eg3JM v9ly8SXHMkDQNG2elX9xbt/NiuV5mPmPQt4k5lOo4MqyM8mY3G8sp9cn3lPXjlnX ng3WUOtJ/75Fh/IgI65MehpVWPvfUypU28eOV6UZh7n+3En4Lwa2guhaBZXl51tE wvELRm5fXcTbseqekoKH4kmsbuSWSt8Fpu6AXciY+qJkY/z8axoRW4xu7+NAy0lT G5E8XhSQQF5FJV0WGGLzhiW+rg03C52jjsa6g8bhBrFDPiCyYFfcil4/Ml3P6cDw NkytnyvzfaGqKkL9LUs+/x09RtssssK4wfIwVMWIAfJhjYMqsTMwTLRFYwqRBeZ7 M4Qb42XGCgM+iJ/AdU8FhqzGQB3JvZYNApoYFyXhiWOmUYyX60o3/aYHvGa/YvVS +L3JuLZlbjAVAnivUPXoltLjI274nrTkdMOBeKAwlAzkUTfX/GI= =xOie -----END PGP SIGNATURE-----