qt: Add job to set the primary user ID of OpenPGP keys

* lang/qt/src/qgpgmesetprimaryuseridjob.cpp,
lang/qt/src/qgpgmesetprimaryuseridjob.h,
lang/qt/src/setprimaryuseridjob.h: New.
* lang/qt/src/protocol.h (class Protocol): Add pure virtual member
function setPrimaryUserIDJob.
* lang/qt/src/protocol_p.h (Protocol::setPrimaryUserIDJob): New.
* lang/qt/src/job.cpp, lang/qt/src/Makefile.am: Update accordingly.

* lang/qt/tests/t-setprimaryuserid.cpp: New.
* lang/qt/tests/Makefile.am: Add new test.
--

GnuPG-bug-id: 5938
This commit is contained in:
Ingo Klöcker 2022-08-09 12:19:04 +02:00
parent 125867f268
commit db7d79063f
10 changed files with 410 additions and 3 deletions

4
NEWS
View File

@ -7,7 +7,7 @@ Noteworthy changes in version 1.17.2 (unreleased)
* cpp, qt: Support revocation of own OpenPGP keys. [#5904]
* cpp: Support setting the primary user ID. [#5938]
* cpp, qt: Support setting the primary user ID. [#5938]
* Interface changes relative to the 1.17.1 release:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -23,6 +23,8 @@ Noteworthy changes in version 1.17.2 (unreleased)
cpp: Context::startSetPrimaryUid NEW.
qt: RevokeKeyJob NEW.
qt: Protocol::revokeKeyJob NEW.
qt: SetPrimaryUserIDJob NEW.
qt: Protocol::setPrimaryUserIDJob NEW.
Release-info: https://dev.gnupg.org/Txxxx

View File

@ -37,6 +37,7 @@ qgpgme_sources = \
qgpgmereceivekeysjob.cpp \
qgpgmerefreshsmimekeysjob.cpp \
qgpgmerevokekeyjob.cpp \
qgpgmesetprimaryuseridjob.cpp \
qgpgmesignencryptjob.cpp \
qgpgmesignjob.cpp qgpgmesignkeyjob.cpp qgpgmeverifydetachedjob.cpp \
qgpgmeverifyopaquejob.cpp qgpgmewkdlookupjob.cpp threadedjobmixin.cpp \
@ -73,6 +74,7 @@ qgpgme_headers= \
quickjob.h \
receivekeysjob.h \
revokekeyjob.h \
setprimaryuseridjob.h \
specialjob.h \
signjob.h \
signkeyjob.h \
@ -118,6 +120,7 @@ camelcase_headers= \
QuickJob \
ReceiveKeysJob \
RevokeKeyJob \
SetPrimaryUserIDJob \
SpecialJob \
SignJob \
SignKeyJob \
@ -164,6 +167,7 @@ private_qgpgme_headers = \
qgpgmereceivekeysjob.h \
qgpgmerefreshsmimekeysjob.h \
qgpgmerevokekeyjob.h \
qgpgmesetprimaryuseridjob.h \
qgpgmesignencryptjob.h \
qgpgmesignjob.h \
qgpgmesignkeyjob.h \
@ -218,6 +222,7 @@ qgpgme_moc_sources = \
qgpgmereceivekeysjob.moc \
qgpgmerefreshsmimekeysjob.moc \
qgpgmerevokekeyjob.moc \
qgpgmesetprimaryuseridjob.moc \
qgpgmesignencryptjob.moc \
qgpgmesignjob.moc \
qgpgmesignkeyjob.moc \
@ -230,6 +235,7 @@ qgpgme_moc_sources = \
receivekeysjob.moc \
refreshkeysjob.moc \
revokekeyjob.moc \
setprimaryuseridjob.moc \
signencryptjob.moc \
signjob.moc \
signkeyjob.moc \

View File

@ -73,6 +73,7 @@
#include "gpgcardjob.h"
#include "receivekeysjob.h"
#include "revokekeyjob.h"
#include "setprimaryuseridjob.h"
#include <QCoreApplication>
#include <QDebug>
@ -174,6 +175,7 @@ make_job_subclass(TofuPolicyJob)
make_job_subclass(QuickJob)
make_job_subclass(GpgCardJob)
make_job_subclass(RevokeKeyJob)
make_job_subclass(SetPrimaryUserIDJob)
#undef make_job_subclass
@ -211,3 +213,4 @@ make_job_subclass(RevokeKeyJob)
#include "gpgcardjob.moc"
#include "receivekeysjob.moc"
#include "revokekeyjob.moc"
#include "setprimaryuseridjob.moc"

View File

@ -72,6 +72,7 @@ class QuickJob;
class GpgCardJob;
class ReceiveKeysJob;
class RevokeKeyJob;
class SetPrimaryUserIDJob;
/** The main entry point for QGpgME Comes in OpenPGP and SMIME(CMS) flavors.
*
@ -182,6 +183,12 @@ public:
virtual ReceiveKeysJob *receiveKeysJob() const = 0;
virtual RevokeKeyJob *revokeKeyJob() const = 0;
/**
* Returns a job for flagging a user ID as the primary user ID of an
* OpenPGP key.
*/
virtual SetPrimaryUserIDJob *setPrimaryUserIDJob() const = 0;
};
/** Obtain a reference to the OpenPGP Protocol.

View File

@ -66,6 +66,7 @@
#include "qgpgmequickjob.h"
#include "qgpgmereceivekeysjob.h"
#include "qgpgmerevokekeyjob.h"
#include "qgpgmesetprimaryuseridjob.h"
namespace
{
@ -493,6 +494,18 @@ public:
}
return new QGpgME::QGpgMERevokeKeyJob(context);
}
QGpgME::SetPrimaryUserIDJob *setPrimaryUserIDJob() const override
{
if (mProtocol != GpgME::OpenPGP) {
return nullptr;
}
GpgME::Context *context = GpgME::Context::createForProtocol(mProtocol);
if (!context) {
return nullptr;
}
return new QGpgME::QGpgMESetPrimaryUserIDJob{context};
}
};
}

View File

@ -0,0 +1,75 @@
/*
qgpgmesetprimaryuseridjob.cpp
This file is part of qgpgme, the Qt API binding for gpgme
Copyright (c) 2022 g10 Code GmbH
Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
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 "qgpgmesetprimaryuseridjob.h"
#include "util.h"
#include <engineinfo.h>
using namespace QGpgME;
using namespace GpgME;
static bool quickSetPrimayUidSupportsUidHash()
{
return GpgME::engineInfo(GpgME::GpgEngine).engineVersion() >= "2.3.8";
}
static QGpgMESetPrimaryUserIDJob::result_type set_primary_userid(Context *ctx, const GpgME::UserID &userId)
{
auto err = ctx->setPrimaryUid(userId.parent(), quickSetPrimayUidSupportsUidHash() ? userId.uidhash() : userId.id());
return std::make_tuple(err, QString(), Error());
}
QGpgMESetPrimaryUserIDJob::QGpgMESetPrimaryUserIDJob(Context *context)
: mixin_type{context}
{
lateInitialization();
}
QGpgMESetPrimaryUserIDJob::~QGpgMESetPrimaryUserIDJob() = default;
GpgME::Error QGpgMESetPrimaryUserIDJob::start(const GpgME::UserID &userId)
{
if (userId.isNull()) {
return Error{make_error(GPG_ERR_INV_ARG)};
}
run([userId](Context *ctx) { return set_primary_userid(ctx, userId); });
return {};
}
#include "qgpgmesetprimaryuseridjob.moc"

View File

@ -0,0 +1,64 @@
/*
qgpgmesetprimaryuseridjob.h
This file is part of qgpgme, the Qt API binding for gpgme
Copyright (c) 2022 g10 Code GmbH
Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
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_QGPGMESETPRIMARYUSERIDJOB_H__
#define __QGPGME_QGPGMESETPRIMARYUSERIDJOB_H__
#include "setprimaryuseridjob.h"
#include "threadedjobmixin.h"
namespace QGpgME
{
class QGpgMESetPrimaryUserIDJob
#ifdef Q_MOC_RUN
: public SetPrimaryUserIDJob
#else
: public _detail::ThreadedJobMixin<SetPrimaryUserIDJob>
#endif
{
Q_OBJECT
#ifdef Q_MOC_RUN
public Q_SLOTS:
void slotFinished();
#endif
public:
explicit QGpgMESetPrimaryUserIDJob(GpgME::Context *context);
~QGpgMESetPrimaryUserIDJob() override;
GpgME::Error start(const GpgME::UserID &userId) override;
};
}
#endif // __QGPGME_QGPGMESETPRIMARYUSERIDJOB_H__

View File

@ -0,0 +1,69 @@
/*
setprimaryuseridjob.h
This file is part of qgpgme, the Qt API binding for gpgme
Copyright (c) 2022 g10 Code GmbH
Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
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_SETPRIMARYUSERIDJOB_H__
#define __QGPGME_SETPRIMARYUSERIDJOB_H__
#include "job.h"
#include "qgpgme_export.h"
namespace GpgME
{
class Error;
class UserID;
}
namespace QGpgME
{
class QGPGME_EXPORT SetPrimaryUserIDJob : public Job
{
Q_OBJECT
public:
explicit SetPrimaryUserIDJob(QObject *parent);
~SetPrimaryUserIDJob() override;
/**
* Starts setting user ID \a userId as the primary user ID.
*/
virtual GpgME::Error start(const GpgME::UserID &userId) = 0;
Q_SIGNALS:
void result(const GpgME::Error &error,
const QString &auditLogAsHtml = QString(), const GpgME::Error &auditLogError = GpgME::Error());
};
}
#endif // __QGPGME_SETPRIMARYUSERIDJOB_H__

View File

@ -30,7 +30,7 @@ the_tests = \
t-addexistingsubkey \
t-keylist t-keylocate t-ownertrust t-tofuinfo \
t-encrypt t-verify t-various t-config t-remarks t-trustsignatures \
t-changeexpiryjob t-wkdlookup t-import t-revokekey
t-changeexpiryjob t-wkdlookup t-import t-revokekey t-setprimaryuserid
TESTS = initial.test $(the_tests) final.test
@ -39,7 +39,8 @@ 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-wkdlookup.moc t-import.moc t-revokekey.moc
t-changeexpiryjob.moc t-wkdlookup.moc t-import.moc t-revokekey.moc \
t-setprimaryuserid.moc
AM_LDFLAGS = -no-install
@ -71,6 +72,7 @@ t_changeexpiryjob_SOURCES = t-changeexpiryjob.cpp $(support_src)
t_wkdlookup_SOURCES = t-wkdlookup.cpp $(support_src)
t_import_SOURCES = t-import.cpp $(support_src)
t_revokekey_SOURCES = t-revokekey.cpp $(support_src)
t_setprimaryuserid_SOURCES = t-setprimaryuserid.cpp $(support_src)
run_exportjob_SOURCES = run-exportjob.cpp
run_importjob_SOURCES = run-importjob.cpp
run_keyformailboxjob_SOURCES = run-keyformailboxjob.cpp
@ -86,6 +88,7 @@ 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-wkdlookup t-import t-revokekey \
t-setprimaryuserid \
run-importjob run-exportjob run-receivekeysjob run-refreshkeysjob
CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \

View File

@ -0,0 +1,165 @@
/* t-setprimaryuserid.cpp
This file is part of qgpgme, the Qt API binding for gpgme
Copyright (c) 2022 g10 Code GmbH
Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
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 <keylistjob.h>
#include <protocol.h>
#include <context.h>
#include <engineinfo.h>
#include <keylistresult.h>
using namespace QGpgME;
using namespace GpgME;
class TestSetPrimaryUserID: public QGpgMETest
{
Q_OBJECT
private Q_SLOTS:
void testSetPrimaryUserID()
{
Key key;
{
std::unique_ptr<KeyListJob> job{openpgp()->keyListJob()};
std::vector<GpgME::Key> keys;
GpgME::KeyListResult result = job->exec({QStringLiteral("alfa@example.net")}, true, keys);
QVERIFY(!result.error());
QVERIFY(keys.size() == 1);
key = keys.front();
}
QCOMPARE(key.numUserIDs(), 3u);
const std::string oldPrimaryUserId = key.userID(0).id();
const std::string newPrimaryUserId = key.userID(1).id();
const std::string newPrimaryUserIdHash = key.userID(1).uidhash();
{
std::unique_ptr<Context> ctx{Context::createForProtocol(key.protocol())};
QVERIFY(ctx);
hookUpPassphraseProvider(ctx.get());
if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() >= "2.3.8") {
QVERIFY(!ctx->setPrimaryUid(key, newPrimaryUserIdHash.c_str()));
} else {
QVERIFY(!ctx->setPrimaryUid(key, newPrimaryUserId.c_str()));
}
}
key.update();
QCOMPARE(key.userID(0).id(), newPrimaryUserId);
{
std::unique_ptr<Context> ctx{Context::createForProtocol(key.protocol())};
QVERIFY(ctx);
hookUpPassphraseProvider(ctx.get());
QVERIFY(!ctx->setPrimaryUid(key, oldPrimaryUserId.c_str()));
}
key.update();
QCOMPARE(key.userID(0).id(), oldPrimaryUserId);
}
void testErrorHandling_noSecretKey()
{
if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.3.8") {
QSKIP("gpg < 2.3.8 does not report status error");
}
Key key;
{
std::unique_ptr<KeyListJob> job{openpgp()->keyListJob()};
std::vector<GpgME::Key> keys;
GpgME::KeyListResult result = job->exec({QStringLiteral("bravo@example.net")}, false, keys);
QVERIFY(!result.error());
QVERIFY(keys.size() == 1);
key = keys.front();
}
QCOMPARE(key.numUserIDs(), 2u);
const std::string newPrimaryUserId = key.userID(1).id();
{
std::unique_ptr<Context> ctx{Context::createForProtocol(key.protocol())};
QVERIFY(ctx);
auto err = ctx->setPrimaryUid(key, newPrimaryUserId.c_str());
QCOMPARE(err.code(), static_cast<int>(GPG_ERR_NO_SECKEY));
}
}
void testErrorHandling_noUserID()
{
if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.3.8") {
QSKIP("gpg < 2.3.8 does not report status error");
}
Key key;
{
std::unique_ptr<KeyListJob> job{openpgp()->keyListJob()};
std::vector<GpgME::Key> keys;
GpgME::KeyListResult result = job->exec({QStringLiteral("alfa@example.net")}, true, keys);
QVERIFY(!result.error());
QVERIFY(keys.size() == 1);
key = keys.front();
}
{
std::unique_ptr<Context> ctx{Context::createForProtocol(key.protocol())};
QVERIFY(ctx);
auto err = ctx->setPrimaryUid(key, "bravo");
QCOMPARE(err.code(), static_cast<int>(GPG_ERR_NO_USER_ID));
}
}
void initTestCase()
{
QGpgMETest::initTestCase();
const QString gpgHome = qgetenv("GNUPGHOME");
QVERIFY(copyKeyrings(gpgHome, mDir.path()));
qputenv("GNUPGHOME", mDir.path().toUtf8());
QFile conf(mDir.path() + QStringLiteral("/gpg.conf"));
QVERIFY(conf.open(QIODevice::WriteOnly));
if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() >= "2.2.18") {
conf.write("allow-weak-key-signatures\n");
}
conf.close();
}
private:
QTemporaryDir mDir;
};
QTEST_MAIN(TestSetPrimaryUserID)
#include "t-setprimaryuserid.moc"