qt: Add job to add existing subkeys to other keys

* lang/qt/src/addexistingsubkeyjob.h,
lang/qt/src/qgpgmeaddexistingsubkeyjob.cpp,
lang/qt/src/qgpgmeaddexistingsubkeyjob.h: New.
* lang/qt/src/protocol.h (class Protocol): Add pure virtual member
function addExistingSubkeyJob.
* lang/qt/src/protocol_p.h (Protocol::addExistingSubkeyJob): Implement.
* lang/qt/src/job.cpp, lang/qt/src/Makefile.am: Update accordingly.

* lang/qt/tests/Makefile.am (the_tests, moc_files, noinst_PROGRAMS):
Add new test.
(t_addexistingsubkey_SOURCES): New.
* lang/qt/tests/t-addexistingsubkey.cpp: New.
* lang/qt/tests/t-support.h (VERIFY_OR_RETURN_VALUE,
COMPARE_OR_RETURN_VALUE, VERIFY_OR_OBJECT, COMPARE_OR_OBJECT,
VERIFY_OR_FALSE, COMPARE_OR_FALSE): New.
* lang/qt/tests/t-support.h, lang/qt/tests/t-support.cpp
(class QQGpgMETest): New member function importSecretKeys.
--

The new job allows adding existing subkeys to other keys as with the
"addkey" edit-key command of gpg. The added subkey will have the same
expiration date (+/- 1 second) as the original subkey.

GnuPG-bug-id: 5770
This commit is contained in:
Ingo Klöcker 2022-01-13 12:51:38 +01:00
parent d308910cdf
commit 4d913a8aa5
12 changed files with 601 additions and 9 deletions

4
NEWS
View File

@ -12,7 +12,7 @@ Noteworthy changes in version 1.16.1 (unreleased)
* cpp,qt: Add support for export of secret keys and secret subkeys.
[#5757]
* cpp: Support for adding existing subkeys to other keys. [#5770]
* cpp,qt: Support for adding existing subkeys to other keys. [#5770]
* qt: Extend ChangeExpiryJob to change expiration of primary key
and of subkeys at the same time. [#4717]
@ -35,6 +35,8 @@ Noteworthy changes in version 1.16.1 (unreleased)
qt: ChangeExpiryJob::Options NEW.
qt: ChangeExpiryJob::setOptions NEW.
qt: ChangeExpiryJob::options NEW.
qt: AddExistingSubkeyJob NEW.
qt: Protocol::addExistingSubkeyJob NEW.
Noteworthy changes in version 1.16.0 (2021-06-24)
-------------------------------------------------

View File

@ -27,6 +27,7 @@ qgpgme_sources = \
dataprovider.cpp \
debug.cpp \
job.cpp multideletejob.cpp qgpgmeadduseridjob.cpp \
qgpgmeaddexistingsubkeyjob.cpp \
qgpgmebackend.cpp qgpgmechangeexpiryjob.cpp qgpgmechangeownertrustjob.cpp \
qgpgmechangepasswdjob.cpp qgpgmedecryptjob.cpp \
qgpgmedecryptverifyjob.cpp qgpgmedeletejob.cpp qgpgmedownloadjob.cpp \
@ -46,6 +47,7 @@ qgpgme_sources = \
# If you add one here make sure that you also add one in camelcase
qgpgme_headers= \
abstractimportjob.h \
addexistingsubkeyjob.h \
adduseridjob.h \
changeexpiryjob.h \
changeownertrustjob.h \
@ -88,6 +90,7 @@ qgpgme_headers= \
dn.h
camelcase_headers= \
AddExistingSubkeyJob \
AddUserIDJob \
AbstractImportJob \
ChangeExpiryJob \
@ -133,6 +136,7 @@ private_qgpgme_headers = \
qgpgme_export.h \
protocol_p.h \
job_p.h \
qgpgmeaddexistingsubkeyjob.h \
qgpgmeadduseridjob.h \
qgpgmebackend.h \
qgpgmechangeexpiryjob.h \
@ -165,6 +169,7 @@ private_qgpgme_headers = \
qgpgme_moc_sources = \
abstractimportjob.moc \
addexistingsubkeyjob.moc \
adduseridjob.moc \
changeexpiryjob.moc \
changeownertrustjob.moc \
@ -183,6 +188,7 @@ qgpgme_moc_sources = \
keylistjob.moc \
listallkeysjob.moc \
multideletejob.moc \
qgpgmeaddexistingsubkeyjob.moc \
qgpgmeadduseridjob.moc \
qgpgmechangeexpiryjob.moc \
qgpgmechangeownertrustjob.moc \

View File

@ -0,0 +1,79 @@
/*
addexistingsubkeyjob.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_ADDEXISTINGSUBKEYJOB_H__
#define __QGPGME_ADDEXISTINGSUBKEYJOB_H__
#include "job.h"
#include "qgpgme_export.h"
class QString;
namespace GpgME
{
class Error;
class Key;
class Subkey;
}
namespace QGpgME
{
class QGPGME_EXPORT AddExistingSubkeyJob : public Job
{
Q_OBJECT
protected:
explicit AddExistingSubkeyJob(QObject *parent);
public:
~AddExistingSubkeyJob();
/**
Starts the operation. \a key is the key to add the subkey \a subkey to.
The job deletes itself after it has completed the operation.
*/
virtual GpgME::Error start(const GpgME::Key &key, const GpgME::Subkey &subkey) = 0;
/**
Runs the operation. \a key is the key to add the subkey \a subkey to.
*/
virtual GpgME::Error exec(const GpgME::Key &key, const GpgME::Subkey &subkey) = 0;
Q_SIGNALS:
void result(const GpgME::Error &result, const QString &auditLogAsHtml = {}, const GpgME::Error &auditLogError = {});
};
}
#endif // __QGPGME_ADDEXISTINGSUBKEYJOB_H__

View File

@ -61,6 +61,7 @@
#include "downloadjob.h"
#include "deletejob.h"
#include "refreshkeysjob.h"
#include "addexistingsubkeyjob.h"
#include "adduseridjob.h"
#include "specialjob.h"
#include "keyformailboxjob.h"
@ -160,6 +161,7 @@ make_job_subclass(ChangePasswdJob)
make_job_subclass(DownloadJob)
make_job_subclass(DeleteJob)
make_job_subclass(RefreshKeysJob)
make_job_subclass(AddExistingSubkeyJob)
make_job_subclass(AddUserIDJob)
make_job_subclass(SpecialJob)
make_job_subclass(KeyForMailboxJob)
@ -194,6 +196,7 @@ make_job_subclass(GpgCardJob)
#include "downloadjob.moc"
#include "deletejob.moc"
#include "refreshkeysjob.moc"
#include "addexistingsubkeyjob.moc"
#include "adduseridjob.moc"
#include "specialjob.moc"
#include "keyformailboxjob.moc"

View File

@ -40,6 +40,7 @@
#include "qgpgme_export.h"
namespace QGpgME {
class AddExistingSubkeyJob;
class CryptoConfig;
class KeyListJob;
class ListAllKeysJob;
@ -138,6 +139,7 @@ public:
virtual SignKeyJob *signKeyJob() const = 0;
virtual ChangePasswdJob *changePasswdJob() const = 0;
virtual ChangeOwnerTrustJob *changeOwnerTrustJob() const = 0;
virtual AddExistingSubkeyJob *addExistingSubkeyJob() const = 0;
virtual AddUserIDJob *addUserIDJob() const = 0;
virtual SpecialJob *specialJob(const char *type, const QMap<QString, QVariant> &args) const = 0;

View File

@ -57,6 +57,7 @@
#include "qgpgmechangeexpiryjob.h"
#include "qgpgmechangeownertrustjob.h"
#include "qgpgmechangepasswdjob.h"
#include "qgpgmeaddexistingsubkeyjob.h"
#include "qgpgmeadduseridjob.h"
#include "qgpgmekeyformailboxjob.h"
#include "qgpgmewkdlookupjob.h"
@ -371,6 +372,19 @@ public:
return new QGpgME::QGpgMEChangeOwnerTrustJob(context);
}
QGpgME:: AddExistingSubkeyJob *addExistingSubkeyJob() const override
{
if (mProtocol != GpgME::OpenPGP) {
return nullptr; // only supported by gpg
}
GpgME::Context *context = GpgME::Context::createForProtocol(mProtocol);
if (!context) {
return nullptr;
}
return new QGpgME::QGpgMEAddExistingSubkeyJob{context};
}
QGpgME::AddUserIDJob *addUserIDJob() const Q_DECL_OVERRIDE
{
if (mProtocol != GpgME::OpenPGP) {

View File

@ -0,0 +1,96 @@
/*
qgpgmeaddexistingsubkeyjob.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 "qgpgmeaddexistingsubkeyjob.h"
#include "dataprovider.h"
#include <QDateTime>
#include "context.h"
#include "data.h"
#include "gpgaddexistingsubkeyeditinteractor.h"
#include "key.h"
#include <gpg-error.h>
using namespace QGpgME;
using namespace GpgME;
QGpgMEAddExistingSubkeyJob::QGpgMEAddExistingSubkeyJob(Context *context)
: mixin_type{context}
{
lateInitialization();
}
QGpgMEAddExistingSubkeyJob::~QGpgMEAddExistingSubkeyJob() = default;
static QGpgMEAddExistingSubkeyJob::result_type add_subkey(Context *ctx, const Key &key, const Subkey &subkey)
{
std::unique_ptr<GpgAddExistingSubkeyEditInteractor> interactor{new GpgAddExistingSubkeyEditInteractor{subkey.keyGrip()}};
if (!subkey.neverExpires()) {
const auto expiry = QDateTime::fromSecsSinceEpoch(subkey.expirationTime(), Qt::UTC).toString(u"yyyyMMdd'T'hhmmss").toStdString();
interactor->setExpiry(expiry);
}
QGpgME::QByteArrayDataProvider dp;
Data data(&dp);
assert(!data.isNull());
ctx->setFlag("extended-edit", "1");
const Error err = ctx->edit(key, std::unique_ptr<EditInteractor>(interactor.release()), data);
Error ae;
const QString log = _detail::audit_log_as_html(ctx, ae);
return std::make_tuple(err, log, ae);
}
Error QGpgMEAddExistingSubkeyJob::start(const GpgME::Key &key, const GpgME::Subkey &subkey)
{
run(std::bind(&add_subkey, std::placeholders::_1, key, subkey));
return {};
}
Error QGpgMEAddExistingSubkeyJob::exec(const GpgME::Key &key, const GpgME::Subkey &subkey)
{
const result_type r = add_subkey(context(), key, subkey);
resultHook(r);
return std::get<0>(r);
}
#include "qgpgmeaddexistingsubkeyjob.moc"

View File

@ -0,0 +1,68 @@
/*
qgpgmeaddexistingsubkeyjob.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_QGPGMEADDEXISTINGSUBKEYJOB_H__
#define __QGPGME_QGPGMEADDEXISTINGSUBKEYJOB_H__
#include "threadedjobmixin.h"
#include "addexistingsubkeyjob.h"
namespace QGpgME
{
class QGpgMEAddExistingSubkeyJob
#ifdef Q_MOC_RUN
: public AddExistingSubkeyJob
#else
: public _detail::ThreadedJobMixin<AddExistingSubkeyJob>
#endif
{
Q_OBJECT
#ifdef Q_MOC_RUN
public Q_SLOTS:
void slotFinished();
#endif
public:
explicit QGpgMEAddExistingSubkeyJob(GpgME::Context *context);
~QGpgMEAddExistingSubkeyJob();
/* from AddExistingSubkeyJob */
GpgME::Error start(const GpgME::Key &key, const GpgME::Subkey &subkey) override;
/* from AddExistingSubkeyJob */
GpgME::Error exec(const GpgME::Key &key, const GpgME::Subkey &subkey) override;
};
}
#endif // __QGPGME_QGPGMEADDEXISTINGSUBKEYJOB_H__

View File

@ -27,13 +27,16 @@ TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME)
EXTRA_DIST = initial.test final.test
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
TESTS = initial.test $(the_tests) final.test
moc_files = t-keylist.moc t-keylocate.moc t-ownertrust.moc t-tofuinfo.moc \
moc_files = \
t-addexistingsubkey.moc \
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
@ -52,6 +55,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/lang/cpp/src -I$(top_builddir)/src \
support_src = t-support.h t-support.cpp
t_addexistingsubkey_SOURCES = t-addexistingsubkey.cpp $(support_src)
t_keylist_SOURCES = t-keylist.cpp $(support_src)
t_keylocate_SOURCES = t-keylocate.cpp $(support_src)
t_ownertrust_SOURCES = t-ownertrust.cpp $(support_src)
@ -74,7 +78,9 @@ nodist_t_keylist_SOURCES = $(moc_files)
BUILT_SOURCES = $(moc_files) pubring-stamp
noinst_PROGRAMS = t-keylist t-keylocate t-ownertrust t-tofuinfo t-encrypt \
noinst_PROGRAMS = \
t-addexistingsubkey \
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 run-importjob \
run-exportjob

View File

@ -0,0 +1,260 @@
/* t-addexistingsubkey.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 "addexistingsubkeyjob.h"
#include "protocol.h"
#include <QSignalSpy>
#include <QTest>
#include "context.h"
#include "data.h"
#include "engineinfo.h"
#include <algorithm>
using namespace QGpgME;
using namespace GpgME;
static const char *requiredVersion = "2.3.5";
/* Test keys
sec# ed25519 2022-01-13 [SC]
1CB8C6A0317AA83F44FE009932392C82B814C8E0
uid [ unknown] source-key@example.net
ssb cv25519 2022-01-13 [E]
ssb cv25519 2022-01-13 [E] [expires: 2100-01-01]
sec ed25519 2022-01-13 [SC]
C3C87F0A3920B01F9E4450EA2B79F21D4DD10BFC
uid [ unknown] target-key@example.net
ssb cv25519 2022-01-13 [E]
* generated with
export GNUPGHOME=$(mktemp -d)
gpg -K
gpg --batch --pinentry-mode loopback --passphrase abc --quick-gen-key source-key@example.net default default never
fpr=$(gpg -k --with-colons source-key@example.net | grep ^fpr | head -1 | cut -d ':' -f 10)
gpg --batch --pinentry-mode loopback --passphrase abc --quick-add-key ${fpr} default default 21000101T120000
gpg --batch --pinentry-mode loopback --passphrase abc --quick-gen-key target-key@example.net default default never
gpg -K
gpg --export-secret-subkeys --armor --batch --pinentry-mode loopback --passphrase abc --comment source-key@example.net source-key@example.net | sed 's/\(.*\)/ "\1\\n"/'
gpg --export-secret-keys --armor --batch --pinentry-mode loopback --passphrase abc --comment target-key@example.net target-key@example.net | sed 's/\(.*\)/ "\1\\n"/'
#rm -rf ${GNUPGHOME}
unset GNUPGHOME
*/
static const char *testKeyData =
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
"Comment: source-key@example.net\n"
"\n"
"lDsEYd/ujBYJKwYBBAHaRw8BAQdAwiZPINTcrpgmu6ZWSaPZlcRSd4nDuofVMhe7\n"
"c2XrFyT/AGUAR05VAbQWc291cmNlLWtleUBleGFtcGxlLm5ldIiUBBMWCgA8FiEE\n"
"HLjGoDF6qD9E/gCZMjksgrgUyOAFAmHf7owCGwMFCwkIBwIDIgIBBhUKCQgLAgQW\n"
"AgMBAh4HAheAAAoJEDI5LIK4FMjgupIA/Au2YEAT9dYdJd0eJCJerG5YAeoB+uBs\n"
"mBkgr6xXE0bIAP43b6u1Jtvf/Wm3BhRbLd5Tg67Ba4CIZ8ZLGng73FBoBpyLBGHf\n"
"7owSCisGAQQBl1UBBQEBB0Cpg8Qof/WShxROZZtmPnw24vTk0R8nIAF1CZJ0bG/C\n"
"SwMBCAf+BwMCtzxziVxQEor8w/VVzHp4/hVSCUyrpiX7Djf04cIMs2bFPduZLgxb\n"
"c1SXhlgiqU0YBNntbGGNdKjTP6FMbYWq1+NwQm6ZXtC76LPG7syM94h4BBgWCgAg\n"
"FiEEHLjGoDF6qD9E/gCZMjksgrgUyOAFAmHf7owCGwwACgkQMjksgrgUyOCI0wEA\n"
"+f56fkvDDUwMOMw7n4+GKpfJXpWhVL08ttccbBOa/9IA/2HYA/78ZaD8E5EyqAEK\n"
"Aj9Au+2oJu9V5qo92QEoqwYHnIsEYd/vgxIKKwYBBAGXVQEFAQEHQBa9FxJkm/9D\n"
"xABildkaYMrbJbu8BPk6uv9V8aLmv9FnAwEIB/4HAwIPhcbN8s6OzPz8/g78TrCh\n"
"xqQb2kygCEj+OQ4/XXU3lus2b5xS5h44LGt99Wisqx+wVPDXmPDJOaxjhHXDmJxd\n"
"/LplIEhykojSm3uUDxERiH4EGBYKACYWIQQcuMagMXqoP0T+AJkyOSyCuBTI4AUC\n"
"Yd/vgwIbDAUJkqcQPQAKCRAyOSyCuBTI4IUjAP9BTfOD+jy6lLmzNO9pquRSAxi/\n"
"PQuglGtpS0LQEJMEOwD+PFnsMe2EtQ+WVSDBeB7O0m61EXeY+RhpuhNtsNXVuwc=\n"
"=wIPU\n"
"-----END PGP PRIVATE KEY BLOCK-----\n"
"-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
"Comment: target-key@example.net\n"
"\n"
"lIYEYd/v/RYJKwYBBAHaRw8BAQdAKoILWXG3yaLb2EniNKQLUjwsrvy5vgAN299J\n"
"W5cFbrz+BwMC/uKbCq3sK5H8QVtEQ/IxGmjWNBpy6c8EDlOG4APi4o4VE+bEYD8w\n"
"J3Kk/lzSm6ZT5vC6DDASks797omjXD+J7zZ0vtTPvheYi/nsVz2UebQWdGFyZ2V0\n"
"LWtleUBleGFtcGxlLm5ldIiUBBMWCgA8FiEEw8h/CjkgsB+eRFDqK3nyHU3RC/wF\n"
"AmHf7/0CGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4HAheAAAoJECt58h1N0Qv8\n"
"rXcBAPxnkXqpp4IY3iTKV5XAdo7Uys7U/joUD73rj2XEvgI1AQDhKK4PLxPhf3ki\n"
"FKU0RA7itxzOH+F8bQ5BdYS49jDPCpyLBGHf7/0SCisGAQQBl1UBBQEBB0Dq9rwA\n"
"hAA2UFJShFsLFp7+g4uhWDfuDa3VjeIQRM+9QgMBCAf+BwMCMfCTl0LNqsn836t5\n"
"f2ZHBuMcNs4JWYmdLAIVaewEHq7zhOsX3iB+/yxwu9g2mXc4XUJ1iQzXLOYwgGov\n"
"8jIovrr01hDkSg4rvM9JKMWdd4h4BBgWCgAgFiEEw8h/CjkgsB+eRFDqK3nyHU3R\n"
"C/wFAmHf7/0CGwwACgkQK3nyHU3RC/xyfAEAqnMdSv6FTAwAWrYvJqJtSVoEhjMn\n"
"3c2qMsu34Bk86/MBAKHbLFmdyePvHaxKeO8CkQDoJzK8rYzw3RAmq/5JsQkL\n"
"=rOVf\n"
"-----END PGP PRIVATE KEY BLOCK-----\n";
class AddExistingSubkeyJobTest : public QGpgMETest
{
Q_OBJECT
private Q_SLOTS:
void initTestCase()
{
QGpgMETest::initTestCase();
// set up the test fixture for this test
qputenv("GNUPGHOME", mGnupgHomeTestFixture.path().toUtf8());
QVERIFY(importSecretKeys(testKeyData, 2));
}
void init()
{
if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < requiredVersion) {
QSKIP("gpg does not yet support adding an existing subkey to another key via the command API");
}
// set up a copy of the test fixture for each test function
mGnupgHomeTestCopy.reset(new QTemporaryDir{});
QVERIFY(copyKeyrings(mGnupgHomeTestFixture.path(), mGnupgHomeTestCopy->path()));
qputenv("GNUPGHOME", mGnupgHomeTestCopy->path().toUtf8());
}
void testAddExistingSubkeyAsync()
{
// Get the key the subkey should be added to
auto key = getTestKey("target-key@example.net");
QVERIFY(!key.isNull());
// Get the key with the subkey to add
auto sourceKey = getTestKey("source-key@example.net", 3);
QVERIFY(!sourceKey.isNull());
auto job = std::unique_ptr<AddExistingSubkeyJob>{openpgp()->addExistingSubkeyJob()};
hookUpPassphraseProvider(job.get());
Error result;
connect(job.get(), &AddExistingSubkeyJob::result,
job.get(), [this, &result](const Error &result_) {
result = result_;
Q_EMIT asyncDone();
});
QVERIFY(!job->start(key, sourceKey.subkey(1)));
job.release(); // after the job has been started it's on its own
QSignalSpy spy (this, SIGNAL(asyncDone()));
QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT));
QVERIFY(result.code() == GPG_ERR_NO_ERROR);
key.update();
QCOMPARE(key.numSubkeys(), 3u);
}
void testAddExistingSubkeySync()
{
// Get the key the subkey should be added to
auto key = getTestKey("target-key@example.net");
QVERIFY(!key.isNull());
// Get the key with the subkey to add
auto sourceKey = getTestKey("source-key@example.net", 3);
QVERIFY(!sourceKey.isNull());
auto sourceSubkey = sourceKey.subkey(1);
QVERIFY(sourceSubkey.expirationTime() == 0);
auto job = std::unique_ptr<AddExistingSubkeyJob>{openpgp()->addExistingSubkeyJob()};
hookUpPassphraseProvider(job.get());
const auto result = job->exec(key, sourceSubkey);
QVERIFY(result.code() == GPG_ERR_NO_ERROR);
key.update();
QCOMPARE(key.numSubkeys(), 3u);
QCOMPARE(key.subkey(2).expirationTime(), 0);
}
void testAddExistingSubkeyWithExpiration()
{
// Get the key the subkey should be added to
auto key = getTestKey("target-key@example.net");
QVERIFY(!key.isNull());
// Get the key with the subkey to add
auto sourceKey = getTestKey("source-key@example.net", 3);
QVERIFY(!sourceKey.isNull());
auto sourceSubkey = sourceKey.subkey(2);
QVERIFY(sourceSubkey.expirationTime() != 0);
auto job = std::unique_ptr<AddExistingSubkeyJob>{openpgp()->addExistingSubkeyJob()};
hookUpPassphraseProvider(job.get());
const auto result = job->exec(key, sourceSubkey);
QVERIFY(result.code() == GPG_ERR_NO_ERROR);
key.update();
QCOMPARE(key.numSubkeys(), 3u);
// allow 1 second different expiration because gpg calculates with
// expiration as difference to current time and takes current time
// several times
const auto allowedDeltaTSeconds = 1;
const auto expectedExpirationRange = std::make_pair(
sourceSubkey.expirationTime() - allowedDeltaTSeconds,
sourceSubkey.expirationTime() + allowedDeltaTSeconds);
const auto actualExpiration = key.subkey(2).expirationTime();
QVERIFY2(actualExpiration >= expectedExpirationRange.first,
("actual: " + std::to_string(actualExpiration) +
"; expected: " + std::to_string(expectedExpirationRange.first)).c_str());
QVERIFY2(actualExpiration <= expectedExpirationRange.second,
("actual: " + std::to_string(actualExpiration) +
"; expected: " + std::to_string(expectedExpirationRange.second)).c_str());
}
private:
Key getTestKey(const char *pattern, unsigned int expectedSubkeys = 2)
{
auto ctx = Context::create(OpenPGP);
VERIFY_OR_OBJECT(ctx);
Error err;
auto key = ctx->key(pattern, err, /*secret=*/true);
VERIFY_OR_OBJECT(!err);
VERIFY_OR_OBJECT(!key.isNull());
COMPARE_OR_OBJECT(key.numSubkeys(), expectedSubkeys);
for (unsigned int i = 0; i < key.numSubkeys(); ++i) {
VERIFY_OR_OBJECT(!key.subkey(i).isNull());
}
return key;
}
private:
QTemporaryDir mGnupgHomeTestFixture;
std::unique_ptr<QTemporaryDir> mGnupgHomeTestCopy;
};
QTEST_MAIN(AddExistingSubkeyJobTest)
#include "t-addexistingsubkey.moc"

View File

@ -36,7 +36,9 @@
#include "t-support.h"
#include "importjob.h"
#include "job.h"
#include "protocol.h"
#include <QTest>
@ -44,9 +46,11 @@
#include <QCoreApplication>
#include <QObject>
#include <QDir>
#include <QSignalSpy>
#include "context.h"
#include "engineinfo.h"
#include "importresult.h"
using namespace GpgME;
using namespace QGpgME;
@ -97,6 +101,30 @@ bool QGpgMETest::copyKeyrings(const QString &src, const QString &dest)
return true;
}
bool QGpgMETest::importSecretKeys(const char *keyData, int expectedKeys)
{
auto job = std::unique_ptr<ImportJob>{openpgp()->importJob()};
VERIFY_OR_FALSE(job);
hookUpPassphraseProvider(job.get());
ImportResult result;
connect(job.get(), &ImportJob::result,
this, [this, &result](const ImportResult &result_) {
result = result_;
Q_EMIT asyncDone();
});
VERIFY_OR_FALSE(!job->start(keyData));
job.release(); // after the job has been started it's on its own
QSignalSpy spy (this, SIGNAL(asyncDone()));
VERIFY_OR_FALSE(spy.wait(QSIGNALSPY_TIMEOUT));
VERIFY_OR_FALSE(!result.error());
VERIFY_OR_FALSE(!result.imports().empty());
COMPARE_OR_FALSE(result.numSecretKeysImported(), expectedKeys);
return true;
}
void QGpgMETest::hookUpPassphraseProvider(GpgME::Context *context)
{
context->setPassphraseProvider(&mPassphraseProvider);

View File

@ -48,6 +48,32 @@ namespace QGpgME
class Job;
}
/// generic variant of QVERIFY returning \a returnValue on failure
#define VERIFY_OR_RETURN_VALUE(statement, returnValue) \
do {\
if (!QTest::qVerify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__))\
return returnValue;\
} while (false)
/// generic variant of QCOMPARE returning \a returnValue on failure
#define COMPARE_OR_RETURN_VALUE(actual, expected, returnValue) \
do {\
if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\
return returnValue;\
} while (false)
/// variant of QVERIFY returning a default constructed object on failure
#define VERIFY_OR_OBJECT(statement) VERIFY_OR_RETURN_VALUE(statement, {})
/// variant of QCOMPARE returning a default constructed object on failure
#define COMPARE_OR_OBJECT(actual, expected) COMPARE_OR_RETURN_VALUE(actual, expected, {})
/// variant of QVERIFY returning \c false on failure
#define VERIFY_OR_FALSE(statement) VERIFY_OR_RETURN_VALUE(statement, false)
/// variant of QCOMPARE returning \c false on failure
#define COMPARE_OR_FALSE(actual, expected) COMPARE_OR_RETURN_VALUE(actual, expected, false)
namespace QTest
{
template <>
@ -88,6 +114,8 @@ protected:
bool copyKeyrings(const QString &from, const QString& to);
bool importSecretKeys(const char *keyData, int expectedKeys = 1);
void hookUpPassphraseProvider(GpgME::Context *context);
void hookUpPassphraseProvider(QGpgME::Job *job);