diff --git a/lang/qt/src/Makefile.am b/lang/qt/src/Makefile.am index 2c8976ba..261eff60 100644 --- a/lang/qt/src/Makefile.am +++ b/lang/qt/src/Makefile.am @@ -36,12 +36,12 @@ qgpgme_sources = \ qgpgmerefreshkeysjob.cpp \ qgpgmesecretkeyexportjob.cpp qgpgmesignencryptjob.cpp \ qgpgmesignjob.cpp qgpgmesignkeyjob.cpp qgpgmeverifydetachedjob.cpp \ - qgpgmeverifyopaquejob.cpp threadedjobmixin.cpp \ + qgpgmeverifyopaquejob.cpp qgpgmewkdlookupjob.cpp threadedjobmixin.cpp \ qgpgmekeyformailboxjob.cpp qgpgme_debug.cpp \ qgpgmetofupolicyjob.cpp qgpgmequickjob.cpp \ defaultkeygenerationjob.cpp qgpgmewkspublishjob.cpp \ qgpgmegpgcardjob.cpp changeexpiryjob.cpp \ - dn.cpp cryptoconfig.cpp + dn.cpp cryptoconfig.cpp wkdlookupresult.cpp # If you add one here make sure that you also add one in camelcase qgpgme_headers= \ @@ -81,6 +81,8 @@ qgpgme_headers= \ verifydetachedjob.h \ defaultkeygenerationjob.h \ tofupolicyjob.h \ + wkdlookupjob.h \ + wkdlookupresult.h \ wkspublishjob.h \ gpgcardjob.h \ dn.h @@ -121,6 +123,8 @@ camelcase_headers= \ VerifyDetachedJob \ KeyForMailboxJob \ DefaultKeyGenerationJob \ + WKDLookupJob \ + WKDLookupResult \ WKSPublishJob \ TofuPolicyJob \ GpgCardJob @@ -152,6 +156,7 @@ private_qgpgme_headers = \ qgpgmesignkeyjob.h \ qgpgmeverifydetachedjob.h \ qgpgmeverifyopaquejob.h \ + qgpgmewkdlookupjob.h \ qgpgmekeyformailboxjob.h \ qgpgmewkspublishjob.h \ qgpgmetofupolicyjob.h \ @@ -201,6 +206,7 @@ qgpgme_moc_sources = \ qgpgmesignkeyjob.moc \ qgpgmeverifydetachedjob.moc \ qgpgmeverifyopaquejob.moc \ + qgpgmewkdlookupjob.moc \ qgpgmewkspublishjob.moc \ tofupolicyjob.moc \ qgpgmetofupolicyjob.moc \ @@ -211,6 +217,7 @@ qgpgme_moc_sources = \ specialjob.moc \ verifydetachedjob.moc \ verifyopaquejob.moc \ + wkdlookupjob.moc \ keyformailboxjob.moc \ wkspublishjob.moc \ qgpgmekeyformailboxjob.moc \ diff --git a/lang/qt/src/job.cpp b/lang/qt/src/job.cpp index c346a355..be637a6d 100644 --- a/lang/qt/src/job.cpp +++ b/lang/qt/src/job.cpp @@ -64,6 +64,7 @@ #include "adduseridjob.h" #include "specialjob.h" #include "keyformailboxjob.h" +#include "wkdlookupjob.h" #include "wkspublishjob.h" #include "tofupolicyjob.h" #include "threadedjobmixin.h" @@ -161,6 +162,7 @@ make_job_subclass(RefreshKeysJob) make_job_subclass(AddUserIDJob) make_job_subclass(SpecialJob) make_job_subclass(KeyForMailboxJob) +make_job_subclass(WKDLookupJob) make_job_subclass(WKSPublishJob) make_job_subclass(TofuPolicyJob) make_job_subclass(QuickJob) @@ -194,6 +196,7 @@ make_job_subclass(GpgCardJob) #include "adduseridjob.moc" #include "specialjob.moc" #include "keyformailboxjob.moc" +#include "wkdlookupjob.moc" #include "wkspublishjob.moc" #include "tofupolicyjob.moc" #include "quickjob.moc" diff --git a/lang/qt/src/protocol.h b/lang/qt/src/protocol.h index dcc7ade4..cffd53b2 100644 --- a/lang/qt/src/protocol.h +++ b/lang/qt/src/protocol.h @@ -64,6 +64,7 @@ class ChangePasswdJob; class AddUserIDJob; class SpecialJob; class KeyForMailboxJob; +class WKDLookupJob; class WKSPublishJob; class TofuPolicyJob; class QuickJob; @@ -155,6 +156,9 @@ public: /** Find the best key to use for a mailbox. */ virtual KeyForMailboxJob *keyForMailboxJob() const = 0; + /** This job looks up a key via WKD without importing it. */ + virtual WKDLookupJob *wkdLookupJob() const = 0; + /** A Job for interacting with gnupg's wks tools. */ virtual WKSPublishJob *wksPublishJob() const = 0; diff --git a/lang/qt/src/protocol_p.h b/lang/qt/src/protocol_p.h index 57c1ed81..da5ce011 100644 --- a/lang/qt/src/protocol_p.h +++ b/lang/qt/src/protocol_p.h @@ -58,6 +58,7 @@ #include "qgpgmechangepasswdjob.h" #include "qgpgmeadduseridjob.h" #include "qgpgmekeyformailboxjob.h" +#include "qgpgmewkdlookupjob.h" #include "qgpgmewkspublishjob.h" #include "qgpgmetofupolicyjob.h" #include "qgpgmequickjob.h" @@ -392,6 +393,18 @@ public: return new QGpgME::QGpgMEKeyForMailboxJob(context); } + QGpgME::WKDLookupJob *wkdLookupJob() const Q_DECL_OVERRIDE + { + if (mProtocol != GpgME::OpenPGP) { + return nullptr; + } + auto context = GpgME::Context::createForEngine(GpgME::AssuanEngine); + if (!context) { + return nullptr; + } + return new QGpgME::QGpgMEWKDLookupJob(context.release()); + } + QGpgME::WKSPublishJob *wksPublishJob() const Q_DECL_OVERRIDE { if (mProtocol != GpgME::OpenPGP) { diff --git a/lang/qt/src/qgpgmewkdlookupjob.cpp b/lang/qt/src/qgpgmewkdlookupjob.cpp new file mode 100644 index 00000000..fcb757e1 --- /dev/null +++ b/lang/qt/src/qgpgmewkdlookupjob.cpp @@ -0,0 +1,182 @@ +/* + qgpgmewkdlookupjob.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2021 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "qgpgmewkdlookupjob.h" + +#include "qgpgme_debug.h" + +#include +#include +#include + +#include + +using namespace QGpgME; +using namespace GpgME; + +QGpgMEWKDLookupJob::QGpgMEWKDLookupJob(Context *context) + : mixin_type{context} +{ + lateInitialization(); +} + +QGpgMEWKDLookupJob::~QGpgMEWKDLookupJob() = default; + +static GpgME::Error startDirmngr(Context *assuanCtx) +{ + Error err; + + auto spawnCtx = std::unique_ptr{Context::createForEngine(SpawnEngine, &err)}; + if (err) { + qCDebug(QGPGME_LOG) << "Error: Failed to get context for spawn engine (" << err.asString() << ")"; + } + + const auto dirmngrProgram = GpgME::dirInfo("dirmngr-name"); + const auto homedir = GpgME::dirInfo("homedir"); + const char *argv[] = { + dirmngrProgram, + "--homedir", + homedir, + "--daemon", + NULL + }; + auto ignoreIO = Data{Data::null}; + if (!err) { + qCDebug(QGPGME_LOG) << "Starting dirmngr ..."; + err = spawnCtx->spawnAsync(dirmngrProgram, argv, + ignoreIO, ignoreIO, ignoreIO, + Context::SpawnDetached); + } + + if (!err) { + // wait for socket to become available + int cnt = 0; + do { + ++cnt; + qCDebug(QGPGME_LOG) << "Waiting for dirmngr to start ..."; + QThread::msleep(250 * cnt); + err = assuanCtx->assuanTransact("GETINFO version"); + } while (err.code() == GPG_ERR_ASS_CONNECT_FAILED && cnt < 5); + } + + return err; +} + +static GpgME::Error setUpDirmngrAssuanConnection(Context *ctx) +{ + Error err; + + const std::string dirmngrSocket = GpgME::dirInfo("dirmngr-socket"); + err = ctx->setEngineFileName(dirmngrSocket.c_str()); + + if (!err) { + err = ctx->setEngineHomeDirectory(""); + } + + if (!err) { + // try do connect to dirmngr + err = ctx->assuanTransact("GETINFO version"); + if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) { + err = startDirmngr(ctx); + } + } + + return err; +} + +static GpgME::Error run_wkd_get(Context *ctx, const QString &email) +{ + Error err; + + const auto cmd = std::string{"WKD_GET "} + email.toUtf8().toStdString(); + err = ctx->assuanTransact(cmd.c_str()); + if (err.code() == GPG_ERR_NO_NAME || err.code() == GPG_ERR_NO_DATA) { + // ignore those benign errors; GPG_ERR_NO_NAME indicates that the domain + // doesn't exist (on first request); GPG_ERR_NO_DATA indicates that + // no key for email is available via WKD or that the domain doesn't + // support WKD or that the domain doesn't exist (on subsequent requests + // using dirmngr's internal cache) + qCDebug(QGPGME_LOG) << "WKD_GET returned" << err.asString() << "; ignoring..."; + err = {}; + } + if (err) { + qCDebug(QGPGME_LOG) << "WKD_GET failed with" << err.asString(); + } + + return err; +} + +static QGpgMEWKDLookupJob::result_type lookup_keys(Context *ctx, const QString &email) +{ + WKDLookupResult result; + + Error err = setUpDirmngrAssuanConnection(ctx); + + if (!err) { + err = run_wkd_get(ctx, email); + } + + if (!err) { + const auto transaction = std::unique_ptr(dynamic_cast(ctx->takeLastAssuanTransaction().release())); + const auto source = transaction->firstStatusLine("SOURCE"); + const auto rawData = transaction->data(); + if (rawData.size() == 0) { + qCDebug(QGPGME_LOG) << "No key found for" << email; + result = WKDLookupResult{GpgME::Data::null, {}, {}}; + } else { + qCDebug(QGPGME_LOG) << "Found key for" << email << "at" << source.c_str(); + result = WKDLookupResult{GpgME::Data{rawData.c_str(), rawData.size()}, source, {}}; + } + } + + return std::make_tuple(err ? WKDLookupResult{err} : result, QString{}, Error{}); +} + +Error QGpgMEWKDLookupJob::start(const QString &email) +{ + run(std::bind(&lookup_keys, std::placeholders::_1, email)); + return Error(); +} + +WKDLookupResult QGpgMEWKDLookupJob::exec(const QString &email) +{ + const result_type r = lookup_keys(context(), email); + resultHook(r); + return std::get<0>(r); +} + +#include "qgpgmewkdlookupjob.moc" diff --git a/lang/qt/src/qgpgmewkdlookupjob.h b/lang/qt/src/qgpgmewkdlookupjob.h new file mode 100644 index 00000000..61f9465c --- /dev/null +++ b/lang/qt/src/qgpgmewkdlookupjob.h @@ -0,0 +1,70 @@ +/* + qgpgmewkdlookupjob.h + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2021 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __QGPGME_QGPGMEWKDLOOKUPJOB_H__ +#define __QGPGME_QGPGMEWKDLOOKUPJOB_H__ + +#include "threadedjobmixin.h" +#include "wkdlookupjob.h" +#include "wkdlookupresult.h" + +namespace QGpgME +{ +class WKDLookupResult; + +class QGpgMEWKDLookupJob +#ifdef Q_MOC_RUN + : public WKDLookupJob +#else + : public _detail::ThreadedJobMixin > +#endif +{ + Q_OBJECT +#ifdef Q_MOC_RUN +public Q_SLOTS: + void slotFinished(); +#endif +public: + explicit QGpgMEWKDLookupJob(GpgME::Context *context); + ~QGpgMEWKDLookupJob(); + + /* from WKDLookupJob */ + GpgME::Error start(const QString &email) Q_DECL_OVERRIDE; + + /* from WKDLookupJob */ + WKDLookupResult exec(const QString &email) Q_DECL_OVERRIDE; +}; + +} + +#endif // __QGPGME_QGPGMEWKDLOOKUPJOB_H__ diff --git a/lang/qt/src/wkdlookupjob.h b/lang/qt/src/wkdlookupjob.h new file mode 100644 index 00000000..ae228744 --- /dev/null +++ b/lang/qt/src/wkdlookupjob.h @@ -0,0 +1,78 @@ +/* + wkdlookupjob.h + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2021 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __QGPGME_WKDLOOKUPJOB_H__ +#define __QGPGME_WKDLOOKUPJOB_H__ + +#include "job.h" +#include "qgpgme_export.h" + +class QString; + +namespace GpgME +{ +class Data; +class Error; +} + +namespace QGpgME +{ + +class WKDLookupResult; + +class QGPGME_EXPORT WKDLookupJob : public Job +{ + Q_OBJECT +protected: + explicit WKDLookupJob(QObject *parent); + +public: + ~WKDLookupJob(); + + /** + Starts a key lookup operation for the email address \a email via WKD. + */ + virtual GpgME::Error start(const QString &email) = 0; + + /** + Runs a key lookup operation for the email address \a email via WKD. + */ + virtual WKDLookupResult exec(const QString &email) = 0; + +Q_SIGNALS: + void result(const WKDLookupResult &result, const QString &auditLogAsHtml = {}, const GpgME::Error &auditLogError = {}); +}; + +} + +#endif // __QGPGME_WKDLOOKUPJOB_H__ diff --git a/lang/qt/src/wkdlookupresult.cpp b/lang/qt/src/wkdlookupresult.cpp new file mode 100644 index 00000000..71aa75cf --- /dev/null +++ b/lang/qt/src/wkdlookupresult.cpp @@ -0,0 +1,111 @@ +/* + wkdlookupresult.cpp - wraps the result of a WKDLookupJob + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2021 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "wkdlookupresult.h" + +#include + +using namespace QGpgME; +using namespace GpgME; + +class WKDLookupResult::Private +{ +public: + GpgME::Data keyData; + std::string source; +}; + +WKDLookupResult::WKDLookupResult() = default; + +WKDLookupResult::~WKDLookupResult() = default; + +WKDLookupResult::WKDLookupResult(const Error &error) + : Result{error} + , d{} +{ +} + +WKDLookupResult::WKDLookupResult(const Data &keyData, const std::string &source, const Error &error) + : Result{error} + , d{new Private{keyData, source}} +{ +} + +WKDLookupResult::WKDLookupResult(const WKDLookupResult &other) + : Result{other} +{ + if (other.d) { + d.reset(new Private{*other.d}); + } +} + +WKDLookupResult &WKDLookupResult::operator=(const WKDLookupResult &other) +{ + auto tmp = other; + swap(tmp); + return *this; +} + +WKDLookupResult::WKDLookupResult(WKDLookupResult &&other) = default; + +WKDLookupResult &WKDLookupResult::operator=(WKDLookupResult &&other) = default; + +void WKDLookupResult::swap(WKDLookupResult &other) noexcept +{ + Result::swap(other); + std::swap(this->d, other.d); +} + +bool WKDLookupResult::isNull() const +{ + return !d && !bool(error()); +} + +Data WKDLookupResult::keyData() const +{ + return d ? d->keyData : Data{}; +} + +std::string WKDLookupResult::source() const +{ + return d ? d->source : std::string{}; +} + +void QGpgME::swap(WKDLookupResult &a, WKDLookupResult &b) +{ + a.swap(b); +} diff --git a/lang/qt/src/wkdlookupresult.h b/lang/qt/src/wkdlookupresult.h new file mode 100644 index 00000000..c40220a6 --- /dev/null +++ b/lang/qt/src/wkdlookupresult.h @@ -0,0 +1,83 @@ +/* + wkdlookupresult.h - wraps the result of a WKDLookupJob + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2021 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __QGPGME_WKDLOOKUPRESULT_H__ +#define __QGPGME_WKDLOOKUPRESULT_H__ + +#include "qgpgme_export.h" + +#include + +#include + +namespace GpgME +{ +class Data; +class Error; +} + +namespace QGpgME +{ + +class QGPGME_EXPORT WKDLookupResult : public GpgME::Result +{ +public: + WKDLookupResult(); + ~WKDLookupResult(); + + explicit WKDLookupResult(const GpgME::Error &err); + explicit WKDLookupResult(const GpgME::Data &keyData, const std::string &source, const GpgME::Error &err); + + WKDLookupResult(const WKDLookupResult &other); + WKDLookupResult &operator=(const WKDLookupResult &other); + + WKDLookupResult(WKDLookupResult &&other); + WKDLookupResult &operator=(WKDLookupResult &&other); + + void swap(WKDLookupResult &other) noexcept; + + bool isNull() const; + + GpgME::Data keyData() const; + std::string source() const; + +private: + class Private; + std::unique_ptr d; +}; + +QGPGME_EXPORT void swap(WKDLookupResult &a, WKDLookupResult &b); + +} + +#endif // __QGPGME_WKDLOOKUPRESULT_H__ diff --git a/lang/qt/tests/Makefile.am b/lang/qt/tests/Makefile.am index 8c44681b..b7ec546e 100644 --- a/lang/qt/tests/Makefile.am +++ b/lang/qt/tests/Makefile.am @@ -24,16 +24,19 @@ GPG = gpg GNUPGHOME=$(abs_builddir) TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) -EXTRA_DIST = initial.test +EXTRA_DIST = initial.test final.test -TESTS = initial.test t-keylist t-keylocate t-ownertrust t-tofuinfo \ - t-encrypt t-verify t-various t-config t-remarks t-trustsignatures \ - t-changeexpiryjob +the_tests = \ + t-keylist t-keylocate t-ownertrust t-tofuinfo \ + t-encrypt t-verify t-various t-config t-remarks t-trustsignatures \ + t-changeexpiryjob t-wkdlookup + +TESTS = initial.test $(the_tests) final.test moc_files = t-keylist.moc t-keylocate.moc t-ownertrust.moc t-tofuinfo.moc \ t-encrypt.moc t-support.hmoc t-wkspublish.moc t-verify.moc \ t-various.moc t-config.moc t-remarks.moc t-trustsignatures.moc \ - t-changeexpiryjob.moc + t-changeexpiryjob.moc t-wkdlookup.moc AM_LDFLAGS = -no-install @@ -61,6 +64,7 @@ t_config_SOURCES = t-config.cpp $(support_src) t_remarks_SOURCES = t-remarks.cpp $(support_src) t_trustsignatures_SOURCES = t-trustsignatures.cpp $(support_src) t_changeexpiryjob_SOURCES = t-changeexpiryjob.cpp $(support_src) +t_wkdlookup_SOURCES = t-wkdlookup.cpp $(support_src) run_keyformailboxjob_SOURCES = run-keyformailboxjob.cpp nodist_t_keylist_SOURCES = $(moc_files) @@ -69,7 +73,7 @@ BUILT_SOURCES = $(moc_files) pubring-stamp noinst_PROGRAMS = t-keylist t-keylocate t-ownertrust t-tofuinfo t-encrypt \ run-keyformailboxjob t-wkspublish t-verify t-various t-config t-remarks \ - t-trustsignatures t-changeexpiryjob + t-trustsignatures t-changeexpiryjob t-wkdlookup CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \ gpg-agent.conf pubring.kbx~ S.gpg-agent gpg.conf pubring.gpg~ \ diff --git a/lang/qt/tests/final.test b/lang/qt/tests/final.test new file mode 100755 index 00000000..f28aaa4c --- /dev/null +++ b/lang/qt/tests/final.test @@ -0,0 +1,6 @@ +#!/bin/sh + +# stop the dirmngr that may have been started +gpgconf --kill dirmngr + +exit 0 diff --git a/lang/qt/tests/t-support.h b/lang/qt/tests/t-support.h index 77bef56d..22ba473c 100644 --- a/lang/qt/tests/t-support.h +++ b/lang/qt/tests/t-support.h @@ -34,9 +34,19 @@ #include "interfaces/passphraseprovider.h" #include +#include #include +namespace QTest +{ +template <> +inline char *toString(const std::string &s) +{ + return QTest::toString(s.c_str()); +} +} + namespace GpgME { class TestPassphraseProvider : public PassphraseProvider diff --git a/lang/qt/tests/t-wkdlookup.cpp b/lang/qt/tests/t-wkdlookup.cpp new file mode 100644 index 00000000..13c70269 --- /dev/null +++ b/lang/qt/tests/t-wkdlookup.cpp @@ -0,0 +1,148 @@ +/* t-wkdlookup.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2021 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "t-support.h" + +#include "data.h" +#include "engineinfo.h" +#include "protocol.h" +#include "wkdlookupjob.h" +#include "wkdlookupresult.h" + +#include +#include +#include + +#include + +using namespace QGpgME; +using namespace GpgME; + +static const char *requiredVersion = "2.1.12"; + +namespace +{ +bool keyHasUserIDWithMatchingEmailAddress(const Key &key, const QString &expectedEmailAddress) +{ + const auto email = expectedEmailAddress.toLower(); + const auto userIds = key.userIDs(); + return std::any_of( + std::begin(userIds), std::end(userIds), + [email](const UserID &uid) { + return email == QString::fromUtf8(uid.email()).toLower(); + }); +} +} + +class WKDLookupTest : public QGpgMETest +{ + Q_OBJECT + +Q_SIGNALS: + void asyncDone(); + +private Q_SLOTS: + +#ifndef DO_ONLINE_TESTS + void testWKDLookupAsync() + { + if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < requiredVersion) { + QSKIP("dirmngr does not yet support WKD lookup"); + } + const QString email = QLatin1String{"wk@gnupg.org"}; + + WKDLookupResult result; + auto *job = openpgp()->wkdLookupJob(); + connect(job, &WKDLookupJob::result, job, [this, &result](const WKDLookupResult &result_, const QString &, const Error &) + { + result = result_; + Q_EMIT asyncDone(); + }); + job->start(email); + QSignalSpy spy (this, SIGNAL(asyncDone())); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); + + QVERIFY(result.error().code() == GPG_ERR_NO_ERROR); + QCOMPARE(result.source(), "https://openpgpkey.gnupg.org"); + const auto keys = result.keyData().toKeys(GpgME::OpenPGP); + QVERIFY(keys.size() == 1); + QVERIFY(keyHasUserIDWithMatchingEmailAddress(keys.front(), email)); + } + + void testWKDLookupSync() + { + if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < requiredVersion) { + QSKIP("dirmngr does not yet support WKD lookup"); + } + const QString email = QLatin1String{"wk@gnupg.org"}; + + auto *job = openpgp()->wkdLookupJob(); + const auto result = job->exec(email); + + QVERIFY(result.error().code() == GPG_ERR_NO_ERROR); + QCOMPARE(result.source(), "https://openpgpkey.gnupg.org"); + const auto keys = result.keyData().toKeys(GpgME::OpenPGP); + QVERIFY(keys.size() == 1); + QVERIFY(keyHasUserIDWithMatchingEmailAddress(keys.front(), email)); + } + + void testLookupWithNoResultAsync() + { + if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < requiredVersion) { + QSKIP("dirmngr does not yet support WKD lookup"); + } + const QString email = QLatin1String{"alfa@example.net"}; + + WKDLookupResult result; + auto *job = openpgp()->wkdLookupJob(); + connect(job, &WKDLookupJob::result, job, [this, &result](const WKDLookupResult &result_, const QString &, const Error &) + { + result = result_; + Q_EMIT asyncDone(); + }); + job->start(email); + QSignalSpy spy (this, SIGNAL(asyncDone())); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); + + QVERIFY(result.error().code() == GPG_ERR_NO_ERROR); + QCOMPARE(result.source(), ""); + QVERIFY(result.keyData().isNull()); + } +#endif +}; + +QTEST_MAIN(WKDLookupTest) + +#include "t-wkdlookup.moc"