diff options
| author | Ingo Klöcker <[email protected]> | 2022-01-13 11:51:38 +0000 | 
|---|---|---|
| committer | Ingo Klöcker <[email protected]> | 2022-01-13 14:18:12 +0000 | 
| commit | 4d913a8aa5dad1327bed5987dada89e9d7c5d292 (patch) | |
| tree | b7c41c831add344aaffa7dc9763da2b08f22e7ff | |
| parent | qt,tests: Add helper to hook up the test passphrase provider (diff) | |
| download | gpgme-4d913a8aa5dad1327bed5987dada89e9d7c5d292.tar.gz gpgme-4d913a8aa5dad1327bed5987dada89e9d7c5d292.zip | |
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
| -rw-r--r-- | NEWS | 4 | ||||
| -rw-r--r-- | lang/qt/src/Makefile.am | 6 | ||||
| -rw-r--r-- | lang/qt/src/addexistingsubkeyjob.h | 79 | ||||
| -rw-r--r-- | lang/qt/src/job.cpp | 3 | ||||
| -rw-r--r-- | lang/qt/src/protocol.h | 2 | ||||
| -rw-r--r-- | lang/qt/src/protocol_p.h | 14 | ||||
| -rw-r--r-- | lang/qt/src/qgpgmeaddexistingsubkeyjob.cpp | 96 | ||||
| -rw-r--r-- | lang/qt/src/qgpgmeaddexistingsubkeyjob.h | 68 | ||||
| -rw-r--r-- | lang/qt/tests/Makefile.am | 22 | ||||
| -rw-r--r-- | lang/qt/tests/t-addexistingsubkey.cpp | 260 | ||||
| -rw-r--r-- | lang/qt/tests/t-support.cpp | 28 | ||||
| -rw-r--r-- | lang/qt/tests/t-support.h | 28 | 
12 files changed, 601 insertions, 9 deletions
| @@ -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)  ------------------------------------------------- diff --git a/lang/qt/src/Makefile.am b/lang/qt/src/Makefile.am index 792049af..c4f0e35f 100644 --- a/lang/qt/src/Makefile.am +++ b/lang/qt/src/Makefile.am @@ -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 \ diff --git a/lang/qt/src/addexistingsubkeyjob.h b/lang/qt/src/addexistingsubkeyjob.h new file mode 100644 index 00000000..5465778c --- /dev/null +++ b/lang/qt/src/addexistingsubkeyjob.h @@ -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 <[email protected]> + +    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__ diff --git a/lang/qt/src/job.cpp b/lang/qt/src/job.cpp index 79951481..14fe1426 100644 --- a/lang/qt/src/job.cpp +++ b/lang/qt/src/job.cpp @@ -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" diff --git a/lang/qt/src/protocol.h b/lang/qt/src/protocol.h index e3caac28..3ffd99b3 100644 --- a/lang/qt/src/protocol.h +++ b/lang/qt/src/protocol.h @@ -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; diff --git a/lang/qt/src/protocol_p.h b/lang/qt/src/protocol_p.h index 08100a48..a9cfd824 100644 --- a/lang/qt/src/protocol_p.h +++ b/lang/qt/src/protocol_p.h @@ -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) { diff --git a/lang/qt/src/qgpgmeaddexistingsubkeyjob.cpp b/lang/qt/src/qgpgmeaddexistingsubkeyjob.cpp new file mode 100644 index 00000000..32e2c292 --- /dev/null +++ b/lang/qt/src/qgpgmeaddexistingsubkeyjob.cpp @@ -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 <[email protected]> + +    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" diff --git a/lang/qt/src/qgpgmeaddexistingsubkeyjob.h b/lang/qt/src/qgpgmeaddexistingsubkeyjob.h new file mode 100644 index 00000000..15727552 --- /dev/null +++ b/lang/qt/src/qgpgmeaddexistingsubkeyjob.h @@ -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 <[email protected]> + +    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__ diff --git a/lang/qt/tests/Makefile.am b/lang/qt/tests/Makefile.am index 57ae59af..18dd989a 100644 --- a/lang/qt/tests/Makefile.am +++ b/lang/qt/tests/Makefile.am @@ -27,16 +27,19 @@ 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 \ -            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 +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  AM_LDFLAGS = -no-install @@ -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,10 +78,12 @@ nodist_t_keylist_SOURCES = $(moc_files)  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-wkdlookup t-import run-importjob \ -    run-exportjob +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  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/t-addexistingsubkey.cpp b/lang/qt/tests/t-addexistingsubkey.cpp new file mode 100644 index 00000000..589c90bf --- /dev/null +++ b/lang/qt/tests/t-addexistingsubkey.cpp @@ -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 <[email protected]> + +    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] [email protected] +    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] [email protected] +    ssb   cv25519 2022-01-13 [E] + * generated with +export GNUPGHOME=$(mktemp -d) +gpg -K +gpg --batch --pinentry-mode loopback --passphrase abc --quick-gen-key [email protected] default default never +fpr=$(gpg -k --with-colons [email protected] | 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 [email protected] default default never +gpg -K +gpg --export-secret-subkeys --armor --batch --pinentry-mode loopback --passphrase abc --comment [email protected] [email protected] | sed 's/\(.*\)/    "\1\\n"/' +gpg --export-secret-keys --armor --batch --pinentry-mode loopback --passphrase abc --comment [email protected] [email protected] | sed 's/\(.*\)/    "\1\\n"/' +#rm -rf ${GNUPGHOME} +unset GNUPGHOME +*/ +static const char *testKeyData = +    "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +    "Comment: [email protected]\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: [email protected]\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("[email protected]"); +        QVERIFY(!key.isNull()); + +        // Get the key with the subkey to add +        auto sourceKey = getTestKey("[email protected]", 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("[email protected]"); +        QVERIFY(!key.isNull()); + +        // Get the key with the subkey to add +        auto sourceKey = getTestKey("[email protected]", 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("[email protected]"); +        QVERIFY(!key.isNull()); + +        // Get the key with the subkey to add +        auto sourceKey = getTestKey("[email protected]", 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" diff --git a/lang/qt/tests/t-support.cpp b/lang/qt/tests/t-support.cpp index e827b517..6db082fd 100644 --- a/lang/qt/tests/t-support.cpp +++ b/lang/qt/tests/t-support.cpp @@ -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); diff --git a/lang/qt/tests/t-support.h b/lang/qt/tests/t-support.h index 0d5757c8..ecafe2f4 100644 --- a/lang/qt/tests/t-support.h +++ b/lang/qt/tests/t-support.h @@ -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); | 
