diff options
author | JinWang An <jinwang.an@samsung.com> | 2021-12-01 16:54:36 +0900 |
---|---|---|
committer | JinWang An <jinwang.an@samsung.com> | 2021-12-01 16:54:36 +0900 |
commit | 214479142a766516e8770c3e1a3b0b0cc37c239e (patch) | |
tree | 43ff2d595b2e19d2f3e35ce6cf74a9e4a63ab3e7 /lang | |
parent | 3a4efa5aa27f73c93a1b020b8b30f07f0b4e46c7 (diff) | |
download | gpgme-upstream/1.9.0.tar.gz gpgme-upstream/1.9.0.tar.bz2 gpgme-upstream/1.9.0.zip |
Imported Upstream version 1.9.0upstream/1.9.0
Diffstat (limited to 'lang')
87 files changed, 2898 insertions, 567 deletions
diff --git a/lang/Makefile.in b/lang/Makefile.in index 647734a..5ebbf14 100644 --- a/lang/Makefile.in +++ b/lang/Makefile.in @@ -109,8 +109,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs diff --git a/lang/cl/Makefile.in b/lang/cl/Makefile.in index 31d9325..4737b80 100644 --- a/lang/cl/Makefile.in +++ b/lang/cl/Makefile.in @@ -112,8 +112,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs diff --git a/lang/cl/gpgme.asd b/lang/cl/gpgme.asd index 5410fad..50046a6 100644 --- a/lang/cl/gpgme.asd +++ b/lang/cl/gpgme.asd @@ -27,7 +27,7 @@ (defsystem gpgme :description "GnuPG Made Easy." :author "g10 Code GmbH" - :version "1.8.0" + :version "1.9.0" :licence "GPL" :depends-on ("cffi" "gpg-error") :components ((:file "gpgme-package") diff --git a/lang/cpp/Makefile.in b/lang/cpp/Makefile.in index 30aef31..f49d1d0 100644 --- a/lang/cpp/Makefile.in +++ b/lang/cpp/Makefile.in @@ -110,8 +110,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs diff --git a/lang/cpp/src/GpgmeppConfig.cmake.in.in b/lang/cpp/src/GpgmeppConfig.cmake.in.in index 928d19f..7f42f31 100644 --- a/lang/cpp/src/GpgmeppConfig.cmake.in.in +++ b/lang/cpp/src/GpgmeppConfig.cmake.in.in @@ -63,8 +63,8 @@ add_library(Gpgmepp SHARED IMPORTED) set_target_properties(Gpgmepp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "@resolved_includedir@/gpgme++;@resolved_includedir@" - INTERFACE_LINK_LIBRARIES "pthread;@resolved_libdir@/libgpgme@libsuffix@;@LIBASSUAN_LIBS@" - IMPORTED_LOCATION "@resolved_libdir@/libgpgmepp.so" + INTERFACE_LINK_LIBRARIES "pthread;@resolved_libdir@/libgpgme.so;@LIBASSUAN_LIBS@" + IMPORTED_LOCATION "@resolved_libdir@/libgpgmepp@libsuffix@" ) if(CMAKE_VERSION VERSION_LESS 2.8.12) diff --git a/lang/cpp/src/Makefile.am b/lang/cpp/src/Makefile.am index 92ed784..4028b3d 100644 --- a/lang/cpp/src/Makefile.am +++ b/lang/cpp/src/Makefile.am @@ -31,7 +31,8 @@ main_sources = \ signingresult.cpp encryptionresult.cpp \ engineinfo.cpp gpgsetexpirytimeeditinteractor.cpp \ gpgsetownertrusteditinteractor.cpp gpgsignkeyeditinteractor.cpp \ - gpgadduserideditinteractor.cpp defaultassuantransaction.cpp \ + gpgadduserideditinteractor.cpp gpggencardkeyinteractor.cpp \ + defaultassuantransaction.cpp \ scdgetinfoassuantransaction.cpp gpgagentgetinfoassuantransaction.cpp \ vfsmountresult.cpp configuration.cpp tofuinfo.cpp swdbresult.cpp @@ -42,6 +43,7 @@ gpgmepp_headers = \ gpgadduserideditinteractor.h gpgagentgetinfoassuantransaction.h \ gpgmefw.h gpgsetexpirytimeeditinteractor.h \ gpgsetownertrusteditinteractor.h gpgsignkeyeditinteractor.h \ + gpggencardkeyinteractor.h \ importresult.h keygenerationresult.h key.h keylistresult.h \ notation.h result.h scdgetinfoassuantransaction.h signingresult.h \ trustitem.h verificationresult.h vfsmountresult.h gpgmepp_export.h \ @@ -69,6 +71,12 @@ libgpgmepp_la_LIBADD = ../../../src/libgpgme.la @LIBASSUAN_LIBS@ libgpgmepp_la_LDFLAGS = -no-undefined -version-info \ @LIBGPGMEPP_LT_CURRENT@:@LIBGPGMEPP_LT_REVISION@:@LIBGPGMEPP_LT_AGE@ +if HAVE_MACOS_SYSTEM +libsuffix=.dylib +else +libsuffix=.so +endif + if HAVE_W32_SYSTEM GpgmeppConfig.cmake: GpgmeppConfig-w32.cmake.in sed -e 's|[@]resolved_bindir@|$(bindir)|g' < "$<" | \ @@ -77,6 +85,7 @@ GpgmeppConfig.cmake: GpgmeppConfig-w32.cmake.in else GpgmeppConfig.cmake: GpgmeppConfig.cmake.in sed -e 's|[@]resolved_libdir@|$(libdir)|g' < "$<" | \ + sed -e 's|[@]libsuffix@|$(libsuffix)|g' | \ sed -e 's|[@]resolved_includedir@|$(includedir)|g' > $@ endif install-cmake-files: GpgmeppConfig.cmake GpgmeppConfigVersion.cmake diff --git a/lang/cpp/src/Makefile.in b/lang/cpp/src/Makefile.in index 45f6219..27bf982 100644 --- a/lang/cpp/src/Makefile.in +++ b/lang/cpp/src/Makefile.in @@ -117,8 +117,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs @@ -166,8 +166,8 @@ am__objects_1 = exception.lo context.lo key.lo trustitem.lo data.lo \ encryptionresult.lo engineinfo.lo \ gpgsetexpirytimeeditinteractor.lo \ gpgsetownertrusteditinteractor.lo gpgsignkeyeditinteractor.lo \ - gpgadduserideditinteractor.lo defaultassuantransaction.lo \ - scdgetinfoassuantransaction.lo \ + gpgadduserideditinteractor.lo gpggencardkeyinteractor.lo \ + defaultassuantransaction.lo scdgetinfoassuantransaction.lo \ gpgagentgetinfoassuantransaction.lo vfsmountresult.lo \ configuration.lo tofuinfo.lo swdbresult.lo am__objects_2 = @@ -470,7 +470,8 @@ main_sources = \ signingresult.cpp encryptionresult.cpp \ engineinfo.cpp gpgsetexpirytimeeditinteractor.cpp \ gpgsetownertrusteditinteractor.cpp gpgsignkeyeditinteractor.cpp \ - gpgadduserideditinteractor.cpp defaultassuantransaction.cpp \ + gpgadduserideditinteractor.cpp gpggencardkeyinteractor.cpp \ + defaultassuantransaction.cpp \ scdgetinfoassuantransaction.cpp gpgagentgetinfoassuantransaction.cpp \ vfsmountresult.cpp configuration.cpp tofuinfo.cpp swdbresult.cpp @@ -481,6 +482,7 @@ gpgmepp_headers = \ gpgadduserideditinteractor.h gpgagentgetinfoassuantransaction.h \ gpgmefw.h gpgsetexpirytimeeditinteractor.h \ gpgsetownertrusteditinteractor.h gpgsignkeyeditinteractor.h \ + gpggencardkeyinteractor.h \ importresult.h keygenerationresult.h key.h keylistresult.h \ notation.h result.h scdgetinfoassuantransaction.h signingresult.h \ trustitem.h verificationresult.h vfsmountresult.h gpgmepp_export.h \ @@ -507,6 +509,8 @@ libgpgmepp_la_LIBADD = ../../../src/libgpgme.la @LIBASSUAN_LIBS@ libgpgmepp_la_LDFLAGS = -no-undefined -version-info \ @LIBGPGMEPP_LT_CURRENT@:@LIBGPGMEPP_LT_REVISION@:@LIBGPGMEPP_LT_AGE@ +@HAVE_MACOS_SYSTEM_FALSE@libsuffix = .so +@HAVE_MACOS_SYSTEM_TRUE@libsuffix = .dylib CLEANFILES = GpgmeppConfig.cmake GpgmeppConfigVersion.cmake \ gpgmepp_version.h GpgmeppConfig.cmake.in @@ -611,6 +615,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exception.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgadduserideditinteractor.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgagentgetinfoassuantransaction.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpggencardkeyinteractor.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgsetexpirytimeeditinteractor.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgsetownertrusteditinteractor.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgsignkeyeditinteractor.Plo@am__quote@ @@ -943,6 +948,7 @@ uninstall-am: uninstall-gpgmeppincludeHEADERS uninstall-libLTLIBRARIES \ @HAVE_W32_SYSTEM_TRUE@ sed -e 's|[@]resolved_includedir@|$(includedir)|g' > $@ @HAVE_W32_SYSTEM_FALSE@GpgmeppConfig.cmake: GpgmeppConfig.cmake.in @HAVE_W32_SYSTEM_FALSE@ sed -e 's|[@]resolved_libdir@|$(libdir)|g' < "$<" | \ +@HAVE_W32_SYSTEM_FALSE@ sed -e 's|[@]libsuffix@|$(libsuffix)|g' | \ @HAVE_W32_SYSTEM_FALSE@ sed -e 's|[@]resolved_includedir@|$(includedir)|g' > $@ install-cmake-files: GpgmeppConfig.cmake GpgmeppConfigVersion.cmake -$(INSTALL) -d $(DESTDIR)$(libdir)/cmake/Gpgmepp diff --git a/lang/cpp/src/context.cpp b/lang/cpp/src/context.cpp index ada7bea..77962d8 100644 --- a/lang/cpp/src/context.cpp +++ b/lang/cpp/src/context.cpp @@ -280,6 +280,11 @@ std::unique_ptr<Context> Context::createForEngine(Engine eng, Error *error) return std::unique_ptr<Context>(new Context(ctx)); } +void Context::setDecryptionFlags(DecryptionFlags flags) +{ + d->decryptFlags = flags; +} + // // // Context::Private @@ -294,7 +299,8 @@ Context::Private::Private(gpgme_ctx_t c) lastAssuanInquireData(Data::null), lastAssuanTransaction(), lastEditInteractor(), - lastCardEditInteractor() + lastCardEditInteractor(), + decryptFlags(DecryptNone) { } @@ -904,21 +910,32 @@ std::unique_ptr<AssuanTransaction> Context::takeLastAssuanTransaction() return std::move(d->lastAssuanTransaction); } -DecryptionResult Context::decrypt(const Data &cipherText, Data &plainText) +DecryptionResult Context::decrypt(const Data &cipherText, Data &plainText, const DecryptionFlags flags) { d->lastop = Private::Decrypt; const Data::Private *const cdp = cipherText.impl(); Data::Private *const pdp = plainText.impl(); - d->lasterr = gpgme_op_decrypt(d->ctx, cdp ? cdp->data : 0, pdp ? pdp->data : 0); + d->lasterr = gpgme_op_decrypt_ext(d->ctx, static_cast<gpgme_decrypt_flags_t> (d->decryptFlags | flags), cdp ? cdp->data : 0, pdp ? pdp->data : 0); return DecryptionResult(d->ctx, Error(d->lasterr)); } -Error Context::startDecryption(const Data &cipherText, Data &plainText) +DecryptionResult Context::decrypt(const Data &cipherText, Data &plainText) +{ + return decrypt(cipherText, plainText, DecryptNone); +} + +Error Context::startDecryption(const Data &cipherText, Data &plainText, const DecryptionFlags flags) { d->lastop = Private::Decrypt; const Data::Private *const cdp = cipherText.impl(); Data::Private *const pdp = plainText.impl(); - return Error(d->lasterr = gpgme_op_decrypt_start(d->ctx, cdp ? cdp->data : 0, pdp ? pdp->data : 0)); + return Error(d->lasterr = gpgme_op_decrypt_ext_start(d->ctx, static_cast<gpgme_decrypt_flags_t> (d->decryptFlags | flags), + cdp ? cdp->data : 0, pdp ? pdp->data : 0)); +} + +Error Context::startDecryption(const Data &cipherText, Data &plainText) +{ + return startDecryption(cipherText, plainText, DecryptNone); } DecryptionResult Context::decryptionResult() const @@ -973,22 +990,33 @@ VerificationResult Context::verificationResult() const } } -std::pair<DecryptionResult, VerificationResult> Context::decryptAndVerify(const Data &cipherText, Data &plainText) +std::pair<DecryptionResult, VerificationResult> Context::decryptAndVerify(const Data &cipherText, Data &plainText, DecryptionFlags flags) { d->lastop = Private::DecryptAndVerify; const Data::Private *const cdp = cipherText.impl(); Data::Private *const pdp = plainText.impl(); - d->lasterr = gpgme_op_decrypt_verify(d->ctx, cdp ? cdp->data : 0, pdp ? pdp->data : 0); + d->lasterr = gpgme_op_decrypt_ext(d->ctx, static_cast<gpgme_decrypt_flags_t> (d->decryptFlags | flags | DecryptVerify), + cdp ? cdp->data : 0, pdp ? pdp->data : 0); return std::make_pair(DecryptionResult(d->ctx, Error(d->lasterr)), VerificationResult(d->ctx, Error(d->lasterr))); } -Error Context::startCombinedDecryptionAndVerification(const Data &cipherText, Data &plainText) +std::pair<DecryptionResult, VerificationResult> Context::decryptAndVerify(const Data &cipherText, Data &plainText) +{ + return decryptAndVerify(cipherText, plainText, DecryptNone); +} + +Error Context::startCombinedDecryptionAndVerification(const Data &cipherText, Data &plainText, DecryptionFlags flags) { d->lastop = Private::DecryptAndVerify; const Data::Private *const cdp = cipherText.impl(); Data::Private *const pdp = plainText.impl(); - return Error(d->lasterr = gpgme_op_decrypt_verify_start(d->ctx, cdp ? cdp->data : 0, pdp ? pdp->data : 0)); + return Error(d->lasterr = gpgme_op_decrypt_ext_start(d->ctx, static_cast<gpgme_decrypt_flags_t> (d->decryptFlags | flags | DecryptVerify), cdp ? cdp->data : 0, pdp ? pdp->data : 0)); +} + +Error Context::startCombinedDecryptionAndVerification(const Data &cipherText, Data &plainText) +{ + return startCombinedDecryptionAndVerification(cipherText, plainText, DecryptNone); } unsigned int to_auditlog_flags(unsigned int flags) @@ -1376,6 +1404,30 @@ Error Context::setTofuPolicyStart(const Key &k, unsigned int policy) k.impl(), to_tofu_policy_t(policy))); } +Error Context::addUid(const Key &k, const char *userid) +{ + return Error(d->lasterr = gpgme_op_adduid(d->ctx, + k.impl(), userid, 0)); +} + +Error Context::startAddUid(const Key &k, const char *userid) +{ + return Error(d->lasterr = gpgme_op_adduid_start(d->ctx, + k.impl(), userid, 0)); +} + +Error Context::revUid(const Key &k, const char *userid) +{ + return Error(d->lasterr = gpgme_op_revuid(d->ctx, + k.impl(), userid, 0)); +} + +Error Context::startRevUid(const Key &k, const char *userid) +{ + return Error(d->lasterr = gpgme_op_revuid_start(d->ctx, + k.impl(), userid, 0)); +} + // Engine Spawn stuff Error Context::spawn(const char *file, const char *argv[], Data &input, Data &output, Data &err, diff --git a/lang/cpp/src/context.h b/lang/cpp/src/context.h index 2c205b0..bec4e39 100644 --- a/lang/cpp/src/context.h +++ b/lang/cpp/src/context.h @@ -214,6 +214,12 @@ public: GpgME::Error edit(const Key &key, std::unique_ptr<EditInteractor> function, Data &out); GpgME::Error startEditing(const Key &key, std::unique_ptr<EditInteractor> function, Data &out); + Error addUid(const Key &key, const char *userid); + Error startAddUid(const Key &key, const char *userid); + + Error revUid(const Key &key, const char *userid); + Error startRevUid(const Key &key, const char *userid); + // using TofuInfo::Policy Error setTofuPolicy(const Key &k, unsigned int policy); Error setTofuPolicyStart(const Key &k, unsigned int policy); @@ -255,14 +261,28 @@ public: // // Crypto Operations // - // + + enum DecryptionFlags { + // Keep in line with core's flags + DecryptNone = 0, + DecryptVerify = 1, + DecryptUnwrap = 128, + DecryptMaxValue = 0x80000000 + }; // // Decryption // + // Alternative way to set decryption flags as they were added only in + // 1.9.0 and so other API can still be used but with 1.9.0 additionally + // flags can be set. + void setDecryptionFlags (const DecryptionFlags flags); + DecryptionResult decrypt(const Data &cipherText, Data &plainText); GpgME::Error startDecryption(const Data &cipherText, Data &plainText); + DecryptionResult decrypt(const Data &cipherText, Data &plainText, const DecryptionFlags flags); + GpgME::Error startDecryption(const Data &cipherText, Data &plainText, const DecryptionFlags flags); DecryptionResult decryptionResult() const; // @@ -280,7 +300,9 @@ public: // std::pair<DecryptionResult, VerificationResult> decryptAndVerify(const Data &cipherText, Data &plainText); + std::pair<DecryptionResult, VerificationResult> decryptAndVerify(const Data &cipherText, Data &plainText, const DecryptionFlags flags); GpgME::Error startCombinedDecryptionAndVerification(const Data &cipherText, Data &plainText); + GpgME::Error startCombinedDecryptionAndVerification(const Data &cipherText, Data &plainText, const DecryptionFlags flags); // use verificationResult() and decryptionResult() to retrieve the result objects... // @@ -319,7 +341,9 @@ public: Prepare = 4, ExpectSign = 8, NoCompress = 16, - Symmetric = 32 + Symmetric = 32, + ThrowKeyIds = 64, + EncryptWrap = 128 }; EncryptionResult encrypt(const std::vector<Key> &recipients, const Data &plainText, Data &cipherText, EncryptionFlags flags); GpgME::Error encryptSymmetrically(const Data &plainText, Data &cipherText); diff --git a/lang/cpp/src/context_p.h b/lang/cpp/src/context_p.h index be34783..d53da0a 100644 --- a/lang/cpp/src/context_p.h +++ b/lang/cpp/src/context_p.h @@ -77,6 +77,7 @@ public: Data lastAssuanInquireData; std::unique_ptr<AssuanTransaction> lastAssuanTransaction; std::unique_ptr<EditInteractor> lastEditInteractor, lastCardEditInteractor; + DecryptionFlags decryptFlags; }; } // namespace GpgME diff --git a/lang/cpp/src/data.cpp b/lang/cpp/src/data.cpp index 2cb4fa8..32ca561 100644 --- a/lang/cpp/src/data.cpp +++ b/lang/cpp/src/data.cpp @@ -25,6 +25,7 @@ #endif #include "data_p.h" +#include "context_p.h" #include <error.h> #include <interfaces/dataprovider.h> @@ -230,3 +231,26 @@ off_t GpgME::Data::seek(off_t offset, int whence) { return gpgme_data_seek(d->data, offset, whence); } + +std::vector<GpgME::Key> GpgME::Data::toKeys(Protocol proto) const +{ + std::vector<GpgME::Key> ret; + if (isNull()) { + return ret; + } + auto ctx = GpgME::Context::createForProtocol(proto); + if (!ctx) { + return ret; + } + + if (gpgme_op_keylist_from_data_start (ctx->impl()->ctx, d->data, 0)) { + return ret; + } + + gpgme_key_t key; + while (!gpgme_op_keylist_next (ctx->impl()->ctx, &key)) { + ret.push_back(GpgME::Key(key, false)); + } + delete ctx; + return ret; +} diff --git a/lang/cpp/src/data.h b/lang/cpp/src/data.h index 50bdf62..cc7906f 100644 --- a/lang/cpp/src/data.h +++ b/lang/cpp/src/data.h @@ -24,6 +24,7 @@ #define __GPGMEPP_DATA_H__ #include "global.h" +#include "key.h" #include <sys/types.h> // for size_t, off_t #include <cstdio> // FILE @@ -109,6 +110,10 @@ public: ssize_t write(const void *buffer, size_t length); off_t seek(off_t offset, int whence); + /** Try to parse the data to a key object using the + * Protocol proto. Returns an empty list on error.*/ + std::vector<Key> toKeys(const Protocol proto = Protocol::OpenPGP) const; + class Private; Private *impl() { diff --git a/lang/cpp/src/editinteractor.cpp b/lang/cpp/src/editinteractor.cpp index 31591fa..b652bda 100644 --- a/lang/cpp/src/editinteractor.cpp +++ b/lang/cpp/src/editinteractor.cpp @@ -212,6 +212,8 @@ bool EditInteractor::needsNoResponse(unsigned int status) const case GPGME_STATUS_KEY_CREATED: case GPGME_STATUS_NEED_PASSPHRASE_SYM: case GPGME_STATUS_SC_OP_FAILURE: + case GPGME_STATUS_CARDCTRL: + case GPGME_STATUS_BACKUP_KEY_CREATED: return false; default: return true; diff --git a/lang/cpp/src/gpggencardkeyinteractor.cpp b/lang/cpp/src/gpggencardkeyinteractor.cpp new file mode 100644 index 0000000..90329e2 --- /dev/null +++ b/lang/cpp/src/gpggencardkeyinteractor.cpp @@ -0,0 +1,332 @@ +/* + gpggencardkeyinteractor.cpp - Edit Interactor to generate a key on a card + Copyright (C) 2017 Intevation GmbH + + This file is part of GPGME++. + + GPGME++ is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + GPGME++ 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 Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with GPGME++; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "gpggencardkeyinteractor.h" + +#include "error.h" + +#include <gpgme.h> + +using namespace GpgME; + +class GpgGenCardKeyInteractor::Private +{ +public: + Private() : keysize(2048), backup(false) + { + + } + std::string name, email, backupFileName, expiry, serial; + int keysize; + bool backup; +}; + +GpgGenCardKeyInteractor::~GpgGenCardKeyInteractor() {} + +GpgGenCardKeyInteractor::GpgGenCardKeyInteractor(const std::string &serial): + d(new Private) +{ + d->serial = serial; +} + +void GpgGenCardKeyInteractor::setNameUtf8(const std::string &name) +{ + d->name = name; +} + +void GpgGenCardKeyInteractor::setEmailUtf8(const std::string &email) +{ + d->email = email; +} + +void GpgGenCardKeyInteractor::setDoBackup(bool value) +{ + d->backup = value; +} + +void GpgGenCardKeyInteractor::setKeySize(int value) +{ + d->keysize = value; +} + +void GpgGenCardKeyInteractor::setExpiry(const std::string &timeStr) +{ + d->expiry = timeStr; +} + +std::string GpgGenCardKeyInteractor::backupFileName() const +{ + return d->backupFileName; +} + +namespace GpgGenCardKeyInteractor_Private +{ +enum { + START = EditInteractor::StartState, + DO_ADMIN, + EXPIRE, + + GOT_SERIAL, + COMMAND, + NAME, + EMAIL, + COMMENT, + BACKUP, + REPLACE, + SIZE, + SIZE2, + SIZE3, + BACKUP_KEY_CREATED, + KEY_CREATED, + QUIT, + SAVE, + + ERROR = EditInteractor::ErrorState +}; +} + +const char *GpgGenCardKeyInteractor::action(Error &err) const +{ + + using namespace GpgGenCardKeyInteractor_Private; + + switch (state()) { + case DO_ADMIN: + return "admin"; + case COMMAND: + return "generate"; + case NAME: + return d->name.c_str(); + case EMAIL: + return d->email.c_str(); + case EXPIRE: + return d->expiry.c_str(); + case BACKUP: + return d->backup ? "Y" : "N"; + case REPLACE: + return "Y"; + case SIZE: + case SIZE2: + case SIZE3: + return std::to_string(d->keysize).c_str(); + case COMMENT: + return ""; + case SAVE: + return "Y"; + case QUIT: + return "quit"; + case KEY_CREATED: + case START: + case GOT_SERIAL: + case BACKUP_KEY_CREATED: + case ERROR: + return 0; + default: + err = Error::fromCode(GPG_ERR_GENERAL); + return 0; + } +} + +unsigned int GpgGenCardKeyInteractor::nextState(unsigned int status, const char *args, Error &err) const +{ + + static const Error GENERAL_ERROR = Error::fromCode(GPG_ERR_GENERAL); + static const Error INV_NAME_ERROR = Error::fromCode(GPG_ERR_INV_NAME); + static const Error INV_EMAIL_ERROR = Error::fromCode(GPG_ERR_INV_USER_ID); + static const Error INV_COMMENT_ERROR = Error::fromCode(GPG_ERR_INV_USER_ID); + + if (needsNoResponse(status)) { + return state(); + } + + using namespace GpgGenCardKeyInteractor_Private; + + switch (state()) { + case START: + if (status == GPGME_STATUS_CARDCTRL && + !d->serial.empty()) { + const std::string sArgs = args; + if (sArgs.find(d->serial) == std::string::npos) { + // Wrong smartcard + err = Error::fromCode(GPG_ERR_WRONG_CARD); + return ERROR; + } else { + printf("EditInteractor: Confirmed S/N: %s %s\n", + d->serial.c_str(), sArgs.c_str()); + } + return GOT_SERIAL; + } else if (d->serial.empty()) { + return GOT_SERIAL; + } + err = GENERAL_ERROR; + return ERROR; + case GOT_SERIAL: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.prompt") == 0) { + return DO_ADMIN; + } + err = GENERAL_ERROR; + return ERROR; + case DO_ADMIN: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.prompt") == 0) { + return COMMAND; + } + err = GENERAL_ERROR; + return ERROR; + case COMMAND: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.genkeys.backup_enc") == 0) { + return BACKUP; + } + err = GENERAL_ERROR; + return ERROR; + case BACKUP: + if (status == GPGME_STATUS_GET_BOOL && + strcmp(args, "cardedit.genkeys.replace_keys") == 0) { + return REPLACE; + } + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.genkeys.size") == 0) { + return SIZE; + } + err = GENERAL_ERROR; + return ERROR; + case REPLACE: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.genkeys.size") == 0) { + printf("Moving to SIZE\n"); + return SIZE; + } + err = GENERAL_ERROR; + return ERROR; + case SIZE: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.genkeys.size") == 0) { + return SIZE2; + } + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.valid") == 0) { + return EXPIRE; + } + err = GENERAL_ERROR; + return ERROR; + case SIZE2: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.genkeys.size") == 0) { + return SIZE3; + } + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.valid") == 0) { + return EXPIRE; + } + err = GENERAL_ERROR; + return ERROR; + case SIZE3: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.valid") == 0) { + return EXPIRE; + } + err = GENERAL_ERROR; + return ERROR; + case EXPIRE: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.name") == 0) { + return NAME; + } + err = GENERAL_ERROR; + return ERROR; + case NAME: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.email") == 0) { + return EMAIL; + } + err = GENERAL_ERROR; + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.name") == 0) { + err = INV_NAME_ERROR; + } + return ERROR; + case EMAIL: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.comment") == 0) { + return COMMENT; + } + err = GENERAL_ERROR; + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.email") == 0) { + err = INV_EMAIL_ERROR; + } + return ERROR; + case COMMENT: + if (status == GPGME_STATUS_BACKUP_KEY_CREATED) { + std::string sArgs = args; + const auto pos = sArgs.rfind(" "); + if (pos != std::string::npos) { + d->backupFileName = sArgs.substr(pos + 1); + return BACKUP_KEY_CREATED; + } + } + if (status == GPGME_STATUS_KEY_CREATED) { + return KEY_CREATED; + } + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keyedit.prompt") == 0) { + return QUIT; + } + err = GENERAL_ERROR; + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.comment") == 0) { + err = INV_COMMENT_ERROR; + } + return ERROR; + case BACKUP_KEY_CREATED: + if (status == GPGME_STATUS_KEY_CREATED) { + return KEY_CREATED; + } + err = GENERAL_ERROR; + return ERROR; + case KEY_CREATED: + return QUIT; + case QUIT: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "cardedit.prompt") == 0) { + return QUIT; + } + err = GENERAL_ERROR; + return ERROR; + case ERROR: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keyedit.prompt") == 0) { + return QUIT; + } + err = lastError(); + return ERROR; + default: + err = GENERAL_ERROR; + return ERROR; + } +} diff --git a/lang/cpp/src/gpggencardkeyinteractor.h b/lang/cpp/src/gpggencardkeyinteractor.h new file mode 100644 index 0000000..c6b17d1 --- /dev/null +++ b/lang/cpp/src/gpggencardkeyinteractor.h @@ -0,0 +1,71 @@ +/* + gpggencardkeyinteractor.h - Edit Interactor to generate a key on a card + Copyright (C) 2017 Intevation GmbH + + This file is part of GPGME++. + + GPGME++ is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + GPGME++ 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 Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with GPGME++; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __GPGMEPP_GPGGENCARDKEYEDITINTERACTOR_H__ +#define __GPGMEPP_GPGGENCARDKEYEDITINTERACTOR_H__ + +#include <editinteractor.h> + +#include <string> +#include <memory> + +namespace GpgME +{ + +class GPGMEPP_EXPORT GpgGenCardKeyInteractor: public EditInteractor +{ +public: + /** Edit interactor to generate a key on a smartcard. + * + * The \a serialnumber argument is intended to safeguard + * against accidentally working on the wrong smartcard. + * + * The edit interactor will fail if the card did not match. + * + * @param serialnumber: Serialnumber of the intended card. + **/ + explicit GpgGenCardKeyInteractor(const std::string &serialnumber); + ~GpgGenCardKeyInteractor(); + + /** Set the key sizes for the subkeys (default 2048) */ + void setKeySize(int size); + + void setNameUtf8(const std::string &name); + void setEmailUtf8(const std::string &email); + + void setDoBackup(bool value); + void setExpiry(const std::string &timeString); + + std::string backupFileName() const; + +private: + /* reimp */ const char *action(Error &err) const; + /* reimp */ unsigned int nextState(unsigned int statusCode, const char *args, Error &err) const; + +private: + class Private; + std::shared_ptr<Private> d; +}; + +} // namespace GpgME + +#endif // __GPGMEPP_GPGGENCARDKEYEDITINTERACTOR_H__ diff --git a/lang/cpp/src/key.cpp b/lang/cpp/src/key.cpp index 235a3c8..31e59e1 100644 --- a/lang/cpp/src/key.cpp +++ b/lang/cpp/src/key.cpp @@ -234,6 +234,11 @@ bool Key::isQualified() const return key && key->is_qualified; } +bool Key::isDeVs() const +{ + return key && key->subkeys && key->subkeys->is_de_vs; +} + const char *Key::issuerSerial() const { return key ? key->issuer_serial : 0 ; @@ -341,7 +346,12 @@ void Key::update() KeyListMode::Validate | KeyListMode::WithTofu); Error err; - auto newKey = ctx->key(primaryFingerprint(), err, hasSecret()); + auto newKey = ctx->key(primaryFingerprint(), err, true); + // Not secret so we get the information from the pubring. + if (newKey.isNull()) + { + newKey = ctx->key(primaryFingerprint(), err, false); + } delete ctx; if (err) { return; @@ -464,6 +474,11 @@ bool Subkey::isQualified() const return subkey && subkey->is_qualified; } +bool Subkey::isDeVs() const +{ + return subkey && subkey->is_de_vs; +} + bool Subkey::isCardKey() const { return subkey && subkey->is_cardkey; @@ -471,7 +486,12 @@ bool Subkey::isCardKey() const const char *Subkey::cardSerialNumber() const { - return subkey ? subkey->card_number : 0 ; + return subkey ? subkey->card_number : nullptr; +} + +const char *Subkey::keyGrip() const +{ + return subkey ? subkey->keygrip : nullptr; } bool Subkey::isSecret() const @@ -894,7 +914,39 @@ std::string UserID::addrSpecFromString(const char *userid) std::string UserID::addrSpec() const { - return addrSpecFromString(email()); + if (!uid || !uid->address) { + return std::string(); + } + + return uid->address; +} + +Error UserID::revoke() +{ + if (isNull()) { + return Error::fromCode(GPG_ERR_GENERAL); + } + auto ctx = Context::createForProtocol(parent().protocol()); + if (!ctx) { + return Error::fromCode(GPG_ERR_INV_ENGINE); + } + Error ret = ctx->revUid(key, id()); + delete ctx; + return ret; +} + +Error Key::addUid(const char *uid) +{ + if (isNull()) { + return Error::fromCode(GPG_ERR_GENERAL); + } + auto ctx = Context::createForProtocol(protocol()); + if (!ctx) { + return Error::fromCode(GPG_ERR_INV_ENGINE); + } + Error ret = ctx->addUid(key, uid); + delete ctx; + return ret; } std::ostream &operator<<(std::ostream &os, const UserID &uid) @@ -903,6 +955,7 @@ std::ostream &operator<<(std::ostream &os, const UserID &uid) if (!uid.isNull()) { os << "\n name: " << protect(uid.name()) << "\n email: " << protect(uid.email()) + << "\n mbox: " << uid.addrSpec() << "\n comment: " << protect(uid.comment()) << "\n validity: " << uid.validityAsString() << "\n revoked: " << uid.isRevoked() diff --git a/lang/cpp/src/key.h b/lang/cpp/src/key.h index 3f596a8..829bd26 100644 --- a/lang/cpp/src/key.h +++ b/lang/cpp/src/key.h @@ -112,6 +112,7 @@ public: bool canCertify() const; bool canAuthenticate() const; bool isQualified() const; + bool isDeVs() const; bool hasSecret() const; GPGMEPP_DEPRECATED bool isSecret() const @@ -152,6 +153,17 @@ public: * how long the keylisting takes.*/ void update(); + /** + * @brief Add a user id to this key. + * + * Needs gnupg 2.1.13 and the key needs to be updated + * afterwards to see the new uid. + * + * @param uid should be fully formated and UTF-8 encoded. + * + * @returns a possible error. + **/ + Error addUid(const char *uid); private: gpgme_key_t impl() const { @@ -208,6 +220,7 @@ public: bool canCertify() const; bool canAuthenticate() const; bool isQualified() const; + bool isDeVs() const; bool isCardKey() const; bool isSecret() const; @@ -259,6 +272,8 @@ public: const char *cardSerialNumber() const; + const char *keyGrip() const; + private: shared_gpgme_key_t key; gpgme_sub_key_t subkey; @@ -335,6 +350,13 @@ public: * @returns a normalized mail address for this userid * or an empty string. */ std::string addrSpec() const; + + /*! Revoke the user id. + * + * Key needs update afterwards. + * + * @returns an error on error.*/ + Error revoke(); private: shared_gpgme_key_t key; gpgme_user_id_t uid; diff --git a/lang/cpp/src/verificationresult.cpp b/lang/cpp/src/verificationresult.cpp index 23c458e..42e483c 100644 --- a/lang/cpp/src/verificationresult.cpp +++ b/lang/cpp/src/verificationresult.cpp @@ -413,7 +413,8 @@ GpgME::Key GpgME::Signature::key(bool search, bool update) const } } if (update) { - ret.update(); + d->keys[idx].update(); + ret = d->keys[idx]; } return ret; } diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am index e32fd12..d91ead9 100644 --- a/lang/python/Makefile.am +++ b/lang/python/Makefile.am @@ -46,42 +46,47 @@ COPY_FILES_GPG = \ # For VPATH builds we need to copy some files because Python's # distutils are not VPATH-aware. -copystamp: $(COPY_FILES) $(COPY_FILES_GPG) data.h config.h - if test "$(srcdir)" != "$(builddir)" ; then \ - cp -R $(COPY_FILES) . ; \ - cp -R $(COPY_FILES_GPG) gpg ; \ - fi +copystamp: $(COPY_FILES) $(COPY_FILES_GPG) + set -e ; for VERSION in $(PYTHON_VERSIONS); do \ + $(MKDIR_P) python$${VERSION}-gpg/gpg ; \ + cp -R $(COPY_FILES) python$${VERSION}-gpg ; \ + cp setup.py python$${VERSION}-gpg ; \ + cp gpg/version.py python$${VERSION}-gpg/gpg ; \ + ln -sf "$(abs_top_srcdir)/src/data.h" python$${VERSION}-gpg ; \ + ln -sf "$(abs_top_builddir)/config.h" python$${VERSION}-gpg ; \ + cp -R $(COPY_FILES_GPG) python$${VERSION}-gpg/gpg ; \ + done touch $@ -data.h: - ln -s "$(top_srcdir)/src/data.h" . - -config.h: - ln -s "$(top_builddir)/config.h" . - all-local: copystamp - for PYTHON in $(PYTHONS); do \ + set -e ; set $(PYTHONS); for VERSION in $(PYTHON_VERSIONS); do \ + PYTHON="$$1" ; shift ; \ + cd python$${VERSION}-gpg && \ CFLAGS="$(CFLAGS)" \ $$PYTHON setup.py build --verbose ; \ + cd .. ; \ done -dist/gpg-$(VERSION).tar.gz dist/gpg-$(VERSION).tar.gz.asc: copystamp +python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \ +python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc: copystamp + cd python$(PYTHON_VERSION)-gpg && \ CFLAGS="$(CFLAGS)" \ $(PYTHON) setup.py sdist --verbose - gpg2 --detach-sign --armor dist/gpg-$(VERSION).tar.gz + gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz .PHONY: prepare prepare: copystamp .PHONY: sdist -sdist: dist/gpg-$(VERSION).tar.gz dist/gpg-$(VERSION).tar.gz.asc +sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \ + python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc .PHONY: upload -upload: dist/gpg-$(VERSION).tar.gz dist/gpg-$(VERSION).tar.gz.asc +upload: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \ + python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc twine upload $^ -CLEANFILES = gpgme.h errors.i gpgme_wrap.c gpg/gpgme.py \ - data.h config.h copystamp +CLEANFILES = copystamp # Remove the rest. # @@ -89,23 +94,23 @@ CLEANFILES = gpgme.h errors.i gpgme_wrap.c gpg/gpgme.py \ # permissions. clean-local: rm -rf -- build - if test "$(srcdir)" != "$(builddir)" ; then \ - find . -type d ! -perm -200 -exec chmod u+w {} ';' ; \ - for F in $(COPY_FILES); do rm -rf -- `basename $$F` ; done ; \ - for F in $(COPY_FILES_GPG); do \ - rm -rf -- gpg/`basename $$F` ; \ - done ; \ - fi + for VERSION in $(PYTHON_VERSIONS); do \ + find python$${VERSION}-gpg -type d ! -perm -200 -exec chmod u+w {} ';' ; \ + rm -rf -- python$${VERSION}-gpg ; \ + done install-exec-local: rm -f install_files.txt - for PYTHON in $(PYTHONS); do \ + set -e ; set $(PYTHONS); for VERSION in $(PYTHON_VERSIONS); do \ + PYTHON="$$1" ; shift ; \ + cd python$${VERSION}-gpg ; \ $$PYTHON setup.py install \ --prefix $(DESTDIR)$(prefix) \ --record files.txt \ --verbose ; \ - cat files.txt >> install_files.txt ; \ + cat files.txt >> ../install_files.txt ; \ rm files.txt ; \ + cd .. ; \ done $(MKDIR_P) $(DESTDIR)$(pythondir)/gpg mv install_files.txt $(DESTDIR)$(pythondir)/gpg diff --git a/lang/python/Makefile.in b/lang/python/Makefile.in index 4168708..6c96f84 100644 --- a/lang/python/Makefile.in +++ b/lang/python/Makefile.in @@ -109,8 +109,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs @@ -420,9 +420,7 @@ COPY_FILES_GPG = \ $(srcdir)/gpg/results.py \ $(srcdir)/gpg/util.py -CLEANFILES = gpgme.h errors.i gpgme_wrap.c gpg/gpgme.py \ - data.h config.h copystamp - +CLEANFILES = copystamp all: all-recursive .SUFFIXES: @@ -740,38 +738,44 @@ uninstall-am: uninstall-local # For VPATH builds we need to copy some files because Python's # distutils are not VPATH-aware. -copystamp: $(COPY_FILES) $(COPY_FILES_GPG) data.h config.h - if test "$(srcdir)" != "$(builddir)" ; then \ - cp -R $(COPY_FILES) . ; \ - cp -R $(COPY_FILES_GPG) gpg ; \ - fi +copystamp: $(COPY_FILES) $(COPY_FILES_GPG) + set -e ; for VERSION in $(PYTHON_VERSIONS); do \ + $(MKDIR_P) python$${VERSION}-gpg/gpg ; \ + cp -R $(COPY_FILES) python$${VERSION}-gpg ; \ + cp setup.py python$${VERSION}-gpg ; \ + cp gpg/version.py python$${VERSION}-gpg/gpg ; \ + ln -sf "$(abs_top_srcdir)/src/data.h" python$${VERSION}-gpg ; \ + ln -sf "$(abs_top_builddir)/config.h" python$${VERSION}-gpg ; \ + cp -R $(COPY_FILES_GPG) python$${VERSION}-gpg/gpg ; \ + done touch $@ -data.h: - ln -s "$(top_srcdir)/src/data.h" . - -config.h: - ln -s "$(top_builddir)/config.h" . - all-local: copystamp - for PYTHON in $(PYTHONS); do \ + set -e ; set $(PYTHONS); for VERSION in $(PYTHON_VERSIONS); do \ + PYTHON="$$1" ; shift ; \ + cd python$${VERSION}-gpg && \ CFLAGS="$(CFLAGS)" \ $$PYTHON setup.py build --verbose ; \ + cd .. ; \ done -dist/gpg-$(VERSION).tar.gz dist/gpg-$(VERSION).tar.gz.asc: copystamp +python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \ +python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc: copystamp + cd python$(PYTHON_VERSION)-gpg && \ CFLAGS="$(CFLAGS)" \ $(PYTHON) setup.py sdist --verbose - gpg2 --detach-sign --armor dist/gpg-$(VERSION).tar.gz + gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz .PHONY: prepare prepare: copystamp .PHONY: sdist -sdist: dist/gpg-$(VERSION).tar.gz dist/gpg-$(VERSION).tar.gz.asc +sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \ + python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc .PHONY: upload -upload: dist/gpg-$(VERSION).tar.gz dist/gpg-$(VERSION).tar.gz.asc +upload: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \ + python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc twine upload $^ # Remove the rest. @@ -780,23 +784,23 @@ upload: dist/gpg-$(VERSION).tar.gz dist/gpg-$(VERSION).tar.gz.asc # permissions. clean-local: rm -rf -- build - if test "$(srcdir)" != "$(builddir)" ; then \ - find . -type d ! -perm -200 -exec chmod u+w {} ';' ; \ - for F in $(COPY_FILES); do rm -rf -- `basename $$F` ; done ; \ - for F in $(COPY_FILES_GPG); do \ - rm -rf -- gpg/`basename $$F` ; \ - done ; \ - fi + for VERSION in $(PYTHON_VERSIONS); do \ + find python$${VERSION}-gpg -type d ! -perm -200 -exec chmod u+w {} ';' ; \ + rm -rf -- python$${VERSION}-gpg ; \ + done install-exec-local: rm -f install_files.txt - for PYTHON in $(PYTHONS); do \ + set -e ; set $(PYTHONS); for VERSION in $(PYTHON_VERSIONS); do \ + PYTHON="$$1" ; shift ; \ + cd python$${VERSION}-gpg ; \ $$PYTHON setup.py install \ --prefix $(DESTDIR)$(prefix) \ --record files.txt \ --verbose ; \ - cat files.txt >> install_files.txt ; \ + cat files.txt >> ../install_files.txt ; \ rm files.txt ; \ + cd .. ; \ done $(MKDIR_P) $(DESTDIR)$(pythondir)/gpg mv install_files.txt $(DESTDIR)$(pythondir)/gpg diff --git a/lang/python/gpg/constants/__init__.py b/lang/python/gpg/constants/__init__.py index 4fb3d6f..484ffd2 100644 --- a/lang/python/gpg/constants/__init__.py +++ b/lang/python/gpg/constants/__init__.py @@ -25,16 +25,16 @@ util.process_constants('GPGME_', globals()) del util # For convenience, we import the modules here. -from . import data, event, keylist, md, pk -from . import protocol, sig, sigsum, status, validity +from . import data, keylist, sig, tofu # The subdirs. +from . import create, event, keysign, md, pk, protocol, sigsum, status, validity # A complication arises because 'import' is a reserved keyword. # Import it as 'Import' instead. globals()['Import'] = getattr(__import__('', globals(), locals(), [str('import')], 1), "import") -__all__ = ['data', 'event', 'import', 'keylist', 'md', 'pk', - 'protocol', 'sig', 'sigsum', 'status', 'validity'] +__all__ = ['data', 'event', 'import', 'keysign', 'keylist', 'md', 'pk', + 'protocol', 'sig', 'sigsum', 'status', 'tofu', 'validity', 'create'] # GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact. We # implement gpg.Context.op_edit using gpgme_op_interact, so the diff --git a/lang/python/gpg/constants/create.py b/lang/python/gpg/constants/create.py new file mode 100644 index 0000000..132e96d --- /dev/null +++ b/lang/python/gpg/constants/create.py @@ -0,0 +1,25 @@ +# Flags for key creation +# +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +from gpg import util +util.process_constants('GPGME_CREATE_', globals()) +del util diff --git a/lang/python/gpg/constants/keysign.py b/lang/python/gpg/constants/keysign.py new file mode 100644 index 0000000..fccdbc4 --- /dev/null +++ b/lang/python/gpg/constants/keysign.py @@ -0,0 +1,25 @@ +# Flags for key signing +# +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +from gpg import util +util.process_constants('GPGME_KEYSIGN_', globals()) +del util diff --git a/lang/python/gpg/constants/tofu/__init__.py b/lang/python/gpg/constants/tofu/__init__.py new file mode 100644 index 0000000..819a58b --- /dev/null +++ b/lang/python/gpg/constants/tofu/__init__.py @@ -0,0 +1,24 @@ +# TOFU +# +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +from . import policy +__all__ = ['policy'] diff --git a/lang/python/gpg/constants/tofu/policy.py b/lang/python/gpg/constants/tofu/policy.py new file mode 100644 index 0000000..5a61f06 --- /dev/null +++ b/lang/python/gpg/constants/tofu/policy.py @@ -0,0 +1,25 @@ +# TOFU policies +# +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +from gpg import util +util.process_constants('GPGME_TOFU_POLICY_', globals()) +del util diff --git a/lang/python/gpg/core.py b/lang/python/gpg/core.py index 748bcbb..632f4ca 100644 --- a/lang/python/gpg/core.py +++ b/lang/python/gpg/core.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016 g10 Code GmbH +# Copyright (C) 2016-2017 g10 Code GmbH # Copyright (C) 2004,2008 Igor Belyi <belyi@users.sourceforge.net> # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org> # @@ -176,7 +176,7 @@ class Context(GpgmeWrapper): def __init__(self, armor=False, textmode=False, offline=False, signers=[], pinentry_mode=constants.PINENTRY_MODE_DEFAULT, protocol=constants.PROTOCOL_OpenPGP, - wrapped=None): + wrapped=None, home_dir=None): """Construct a context object Keyword arguments: @@ -186,6 +186,7 @@ class Context(GpgmeWrapper): signers -- list of keys used for signing (default []) pinentry_mode -- pinentry mode (default PINENTRY_MODE_DEFAULT) protocol -- protocol to use (default PROTOCOL_OpenPGP) + home_dir -- state directory (default is the engine default) """ if wrapped: @@ -203,6 +204,15 @@ class Context(GpgmeWrapper): self.signers = signers self.pinentry_mode = pinentry_mode self.protocol = protocol + self.home_dir = home_dir + + def __repr__(self): + return ( + "Context(armor={0.armor}, " + "textmode={0.textmode}, offline={0.offline}, " + "signers={0.signers}, pinentry_mode={0.pinentry_mode}, " + "protocol={0.protocol}, home_dir={0.home_dir}" + ")").format(self) def encrypt(self, plaintext, recipients=[], sign=True, sink=None, passphrase=None, always_trust=False, add_encrypt_to=False, @@ -473,12 +483,17 @@ class Context(GpgmeWrapper): plainbytes = data.read() return plainbytes, result - def keylist(self, pattern=None, secret=False): + def keylist(self, pattern=None, secret=False, + mode=constants.keylist.mode.LOCAL, + source=None): """List keys Keyword arguments: pattern -- return keys matching pattern (default: all keys) - secret -- return only secret keys + secret -- return only secret keys (default: False) + mode -- keylist mode (default: list local keys) + source -- read keys from source instead from the keyring + (all other options are ignored in this case) Returns: -- an iterator returning key objects @@ -486,7 +501,249 @@ class Context(GpgmeWrapper): Raises: GPGMEError -- as signaled by the underlying library """ - return self.op_keylist_all(pattern, secret) + if not source: + self.set_keylist_mode(mode) + self.op_keylist_start(pattern, secret) + else: + # Automatic wrapping of SOURCE is not possible here, + # because the object must not be deallocated until the + # iteration over the results ends. + if not isinstance(source, Data): + source = Data(file=source) + self.op_keylist_from_data_start(source, 0) + + key = self.op_keylist_next() + while key: + yield key + key = self.op_keylist_next() + self.op_keylist_end() + + def create_key(self, userid, algorithm=None, expires_in=0, expires=True, + sign=False, encrypt=False, certify=False, authenticate=False, + passphrase=None, force=False): + """Create a primary key + + Create a primary key for the user id USERID. + + ALGORITHM may be used to specify the public key encryption + algorithm for the new key. By default, a reasonable default + is chosen. You may use "future-default" to select an + algorithm that will be the default in a future implementation + of the engine. ALGORITHM may be a string like "rsa", or + "rsa2048" to explicitly request an algorithm and a key size. + + EXPIRES_IN specifies the expiration time of the key in number + of seconds since the keys creation. By default, a reasonable + expiration time is chosen. If you want to create a key that + does not expire, use the keyword argument EXPIRES. + + SIGN, ENCRYPT, CERTIFY, and AUTHENTICATE can be used to + request the capabilities of the new key. If you don't request + any, a reasonable set of capabilities is selected, and in case + of OpenPGP, a subkey with a reasonable set of capabilities is + created. + + If PASSPHRASE is None (the default), then the key will not be + protected with a passphrase. If PASSPHRASE is a string, it + will be used to protect the key. If PASSPHRASE is True, the + passphrase must be supplied using a passphrase callback or + out-of-band with a pinentry. + + Keyword arguments: + algorithm -- public key algorithm, see above (default: reasonable) + expires_in -- expiration time in seconds (default: reasonable) + expires -- whether or not the key should expire (default: True) + sign -- request the signing capability (see above) + encrypt -- request the encryption capability (see above) + certify -- request the certification capability (see above) + authenticate -- request the authentication capability (see above) + passphrase -- protect the key with a passphrase (default: no passphrase) + force -- force key creation even if a key with the same userid exists + (default: False) + + Returns: + -- an object describing the result of the key creation + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + if util.is_a_string(passphrase): + old_pinentry_mode = self.pinentry_mode + old_passphrase_cb = getattr(self, '_passphrase_cb', None) + self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK + def passphrase_cb(hint, desc, prev_bad, hook=None): + return passphrase + self.set_passphrase_cb(passphrase_cb) + + try: + self.op_createkey(userid, algorithm, + 0, # reserved + expires_in, + None, # extrakey + ((constants.create.SIGN if sign else 0) + | (constants.create.ENCR if encrypt else 0) + | (constants.create.CERT if certify else 0) + | (constants.create.AUTH if authenticate else 0) + | (constants.create.NOPASSWD if passphrase == None else 0) + | (0 if expires else constants.create.NOEXPIRE) + | (constants.create.FORCE if force else 0))) + finally: + if util.is_a_string(passphrase): + self.pinentry_mode = old_pinentry_mode + if old_passphrase_cb: + self.set_passphrase_cb(*old_passphrase_cb[1:]) + + return self.op_genkey_result() + + def create_subkey(self, key, algorithm=None, expires_in=0, expires=True, + sign=False, encrypt=False, authenticate=False, passphrase=None): + """Create a subkey + + Create a subkey for the given KEY. As subkeys are a concept + of OpenPGP, calling this is only valid for the OpenPGP + protocol. + + ALGORITHM may be used to specify the public key encryption + algorithm for the new subkey. By default, a reasonable + default is chosen. You may use "future-default" to select an + algorithm that will be the default in a future implementation + of the engine. ALGORITHM may be a string like "rsa", or + "rsa2048" to explicitly request an algorithm and a key size. + + EXPIRES_IN specifies the expiration time of the subkey in + number of seconds since the subkeys creation. By default, a + reasonable expiration time is chosen. If you want to create a + subkey that does not expire, use the keyword argument EXPIRES. + + SIGN, ENCRYPT, and AUTHENTICATE can be used to request the + capabilities of the new subkey. If you don't request any, an + encryption subkey is generated. + + If PASSPHRASE is None (the default), then the subkey will not + be protected with a passphrase. If PASSPHRASE is a string, it + will be used to protect the subkey. If PASSPHRASE is True, + the passphrase must be supplied using a passphrase callback or + out-of-band with a pinentry. + + Keyword arguments: + algorithm -- public key algorithm, see above (default: reasonable) + expires_in -- expiration time in seconds (default: reasonable) + expires -- whether or not the subkey should expire (default: True) + sign -- request the signing capability (see above) + encrypt -- request the encryption capability (see above) + authenticate -- request the authentication capability (see above) + passphrase -- protect the subkey with a passphrase (default: no passphrase) + + Returns: + -- an object describing the result of the subkey creation + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + if util.is_a_string(passphrase): + old_pinentry_mode = self.pinentry_mode + old_passphrase_cb = getattr(self, '_passphrase_cb', None) + self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK + def passphrase_cb(hint, desc, prev_bad, hook=None): + return passphrase + self.set_passphrase_cb(passphrase_cb) + + try: + self.op_createsubkey(key, algorithm, + 0, # reserved + expires_in, + ((constants.create.SIGN if sign else 0) + | (constants.create.ENCR if encrypt else 0) + | (constants.create.AUTH if authenticate else 0) + | (constants.create.NOPASSWD + if passphrase == None else 0) + | (0 if expires else constants.create.NOEXPIRE))) + finally: + if util.is_a_string(passphrase): + self.pinentry_mode = old_pinentry_mode + if old_passphrase_cb: + self.set_passphrase_cb(*old_passphrase_cb[1:]) + + return self.op_genkey_result() + + def key_add_uid(self, key, uid): + """Add a UID + + Add the uid UID to the given KEY. Calling this function is + only valid for the OpenPGP protocol. + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + self.op_adduid(key, uid, 0) + + def key_revoke_uid(self, key, uid): + """Revoke a UID + + Revoke the uid UID from the given KEY. Calling this function + is only valid for the OpenPGP protocol. + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + self.op_revuid(key, uid, 0) + + def key_sign(self, key, uids=None, expires_in=False, local=False): + """Sign a key + + Sign a key with the current set of signing keys. Calling this + function is only valid for the OpenPGP protocol. + + If UIDS is None (the default), then all UIDs are signed. If + it is a string, then only the matching UID is signed. If it + is a list of strings, then all matching UIDs are signed. Note + that a case-sensitive exact string comparison is done. + + EXPIRES_IN specifies the expiration time of the signature in + seconds. If EXPIRES_IN is False, the signature does not + expire. + + Keyword arguments: + uids -- user ids to sign, see above (default: sign all) + expires_in -- validity period of the signature in seconds + (default: do not expire) + local -- create a local, non-exportable signature + (default: False) + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + flags = 0 + if uids == None or util.is_a_string(uids): + pass#through unchanged + else: + flags |= constants.keysign.LFSEP + uids = "\n".join(uids) + + if not expires_in: + flags |= constants.keysign.NOEXPIRE + + if local: + flags |= constants.keysign.LOCAL + + self.op_keysign(key, uids, expires_in, flags) + + def key_tofu_policy(self, key, policy): + """Set a keys' TOFU policy + + Set the TOFU policy associated with KEY to POLICY. Calling + this function is only valid for the OpenPGP protocol. + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + self.op_tofu_policy(key, policy) def assuan_transact(self, command, data_cb=None, inquire_cb=None, status_cb=None): @@ -512,7 +769,7 @@ class Context(GpgmeWrapper): """ - if isinstance(command, (str, bytes)): + if util.is_a_string(command) or isinstance(command, bytes): cmd = command else: cmd = " ".join(util.percent_escape(f) for f in command) @@ -602,27 +859,40 @@ class Context(GpgmeWrapper): errorcheck(gpgme.gpgme_engine_check_version(value)) self.set_protocol(value) + @property + def home_dir(self): + """Engine's home directory""" + return self.engine_info.home_dir + @home_dir.setter + def home_dir(self, value): + self.set_engine_info(self.protocol, home_dir=value) + _ctype = 'gpgme_ctx_t' _cprefix = 'gpgme_' def _errorcheck(self, name): """This function should list all functions returning gpgme_error_t""" + # The list of functions is created using: + # + # $ grep '^gpgme_error_t ' obj/lang/python/python3.5-gpg/gpgme.h \ + # | grep -v _op_ | awk "/\(gpgme_ctx/ { printf (\"'%s',\\n\", \$2) } " return ((name.startswith('gpgme_op_') and not name.endswith('_result')) or name in { + 'gpgme_new', 'gpgme_set_ctx_flag', 'gpgme_set_protocol', 'gpgme_set_sub_protocol', 'gpgme_set_keylist_mode', 'gpgme_set_pinentry_mode', 'gpgme_set_locale', - 'gpgme_set_engine_info', + 'gpgme_ctx_set_engine_info', 'gpgme_signers_add', - 'gpgme_get_sig_key', 'gpgme_sig_notation_add', + 'gpgme_set_sender', 'gpgme_cancel', 'gpgme_cancel_async', - 'gpgme_cancel_get_key', + 'gpgme_get_key', }) _boolean_properties = {'armor', 'textmode', 'offline'} @@ -829,8 +1099,7 @@ class Context(GpgmeWrapper): home_dir -- configuration directory (unchanged if None) """ - errorcheck(gpgme.gpgme_ctx_set_engine_info( - self.wrapped, proto, file_name, home_dir)) + self.ctx_set_engine_info(proto, file_name, home_dir) def wait(self, hang): """Wait for asynchronous call to finish. Wait forever if hang is True. @@ -884,11 +1153,19 @@ class Data(GpgmeWrapper): def _errorcheck(self, name): """This function should list all functions returning gpgme_error_t""" + # This list is compiled using + # + # $ grep -v '^gpgme_error_t ' obj/lang/python/python3.5-gpg/gpgme.h \ + # | awk "/\(gpgme_data_t/ { printf (\"'%s',\\n\", \$2) } " | sed "s/'\\*/'/" return name not in { + 'gpgme_data_read', + 'gpgme_data_write', + 'gpgme_data_seek', + 'gpgme_data_release', 'gpgme_data_release_and_get_mem', 'gpgme_data_get_encoding', - 'gpgme_data_seek', 'gpgme_data_get_file_name', + 'gpgme_data_identify', } def __init__(self, string=None, file=None, offset=None, @@ -1097,15 +1374,64 @@ class Data(GpgmeWrapper): chunks.append(result) return b''.join(chunks) +def pubkey_algo_string(subkey): + """Return short algorithm string + + Return a public key algorithm string (e.g. "rsa2048") for a given + SUBKEY. + + Returns: + algo - a string + + """ + return gpgme.gpgme_pubkey_algo_string(subkey) + def pubkey_algo_name(algo): + """Return name of public key algorithm + + Return the name of the public key algorithm for a given numeric + algorithm id ALGO (cf. RFC4880). + + Returns: + algo - a string + + """ return gpgme.gpgme_pubkey_algo_name(algo) def hash_algo_name(algo): + """Return name of hash algorithm + + Return the name of the hash algorithm for a given numeric + algorithm id ALGO (cf. RFC4880). + + Returns: + algo - a string + + """ return gpgme.gpgme_hash_algo_name(algo) def get_protocol_name(proto): + """Get protocol description + + Get the string describing protocol PROTO. + + Returns: + proto - a string + + """ return gpgme.gpgme_get_protocol_name(proto) +def addrspec_from_uid(uid): + """Return the address spec + + Return the addr-spec (cf. RFC2822 section 4.3) from a user id UID. + + Returns: + addr_spec - a string + + """ + return gpgme.gpgme_addrspec_from_uid(uid) + def check_version(version=None): return gpgme.gpgme_check_version(version) diff --git a/lang/python/gpg/gpgme.py b/lang/python/gpg/gpgme.py deleted file mode 100644 index 238359d..0000000 --- a/lang/python/gpg/gpgme.py +++ /dev/null @@ -1,126 +0,0 @@ -# This file was automatically generated by SWIG (http://www.swig.org). -# Version 3.0.7 -# -# Do not make changes to this file unless you know what you are doing--modify -# the SWIG interface file instead. - - - - - -from sys import version_info -if version_info >= (2, 6, 0): - def swig_import_helper(): - from os.path import dirname - import imp - fp = None - try: - fp, pathname, description = imp.find_module('_gpgme', [dirname(__file__)]) - except ImportError: - import _gpgme - return _gpgme - if fp is not None: - try: - _mod = imp.load_module('_gpgme', fp, pathname, description) - finally: - fp.close() - return _mod - _gpgme = swig_import_helper() - del swig_import_helper -else: - import _gpgme -del version_info -from _gpgme import * -try: - _swig_property = property -except NameError: - pass # Python < 2.2 doesn't have 'property'. - - -def _swig_setattr_nondynamic(self, class_type, name, value, static=1): - if (name == "thisown"): - return self.this.own(value) - if (name == "this"): - if type(value).__name__ == 'SwigPyObject': - self.__dict__[name] = value - return - method = class_type.__swig_setmethods__.get(name, None) - if method: - return method(self, value) - if (not static): - if _newclass: - object.__setattr__(self, name, value) - else: - self.__dict__[name] = value - else: - raise AttributeError("You cannot add attributes to %s" % self) - - -def _swig_setattr(self, class_type, name, value): - return _swig_setattr_nondynamic(self, class_type, name, value, 0) - - -def _swig_getattr_nondynamic(self, class_type, name, static=1): - if (name == "thisown"): - return self.this.own() - method = class_type.__swig_getmethods__.get(name, None) - if method: - return method(self) - if (not static): - return object.__getattr__(self, name) - else: - raise AttributeError(name) - -def _swig_getattr(self, class_type, name): - return _swig_getattr_nondynamic(self, class_type, name, 0) - - -def _swig_repr(self): - try: - strthis = "proxy of " + self.this.__repr__() - except: - strthis = "" - return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) - -try: - _object = object - _newclass = 1 -except AttributeError: - class _object: - pass - _newclass = 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# This file is compatible with both classic and new-style classes. - - diff --git a/lang/python/gpg/results.py b/lang/python/gpg/results.py index 3383896..46ebeec 100644 --- a/lang/python/gpg/results.py +++ b/lang/python/gpg/results.py @@ -64,10 +64,10 @@ class Result(object): setattr(self, key, getattr(fragile, key)) - def __str__(self): - return '<{} {}>'.format( + def __repr__(self): + return '{}({})'.format( self.__class__.__name__, - ', '.join('{}: {}'.format(k, getattr(self, k)) + ', '.join('{}={!r}'.format(k, getattr(self, k)) for k in dir(self) if not k.startswith('_'))) class InvalidKey(Result): diff --git a/lang/python/gpg/version.py b/lang/python/gpg/version.py index 9ec657d..ff4cd71 100644 --- a/lang/python/gpg/version.py +++ b/lang/python/gpg/version.py @@ -22,7 +22,7 @@ del absolute_import, print_function from . import gpgme productname = 'gpg' -versionstr = "1.8.0" +versionstr = "1.9.0" gpgme_versionstr = gpgme.GPGME_VERSION in_tree_build = bool(gpgme.cvar.gpg_in_tree_build) diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i index 783531f..610b3d9 100644 --- a/lang/python/gpgme.i +++ b/lang/python/gpgme.i @@ -111,8 +111,10 @@ } /* Release returned buffers as necessary. */ -%typemap(newfree) char * "free($1);"; +%typemap(newfree) char * "gpgme_free($1);"; %newobject gpgme_data_release_and_get_mem; +%newobject gpgme_pubkey_algo_string; +%newobject gpgme_addrspec_from_uid; %typemap(arginit) gpgme_key_t [] { $1 = NULL; @@ -135,7 +137,12 @@ /* Following code is from swig's python.swg. */ if ((SWIG_ConvertPtr(pypointer,(void **) &$1[i], $*1_descriptor,SWIG_POINTER_EXCEPTION | $disown )) == -1) { - Py_DECREF(pypointer); + Py_DECREF(pypointer); + PyErr_Format(PyExc_TypeError, + "arg %d: list must contain only gpgme_key_ts, got %s " + "at position %d", + $argnum, pypointer->ob_type->tp_name, i); + free($1); return NULL; } Py_DECREF(pypointer); @@ -287,7 +294,7 @@ gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext, gpgme_data_t keydata, gpgme_data_t pubkey, gpgme_data_t seckey, - gpgme_data_t out}; + gpgme_data_t out, gpgme_data_t data}; /* SWIG has problems interpreting ssize_t, off_t or gpgme_error_t in gpgme.h. */ @@ -424,69 +431,24 @@ /* Wrap the fragile result objects into robust Python ones. */ -%typemap(out) gpgme_encrypt_result_t { - PyObject *fragile; - fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, - %newpointer_flags); - $result = _gpg_wrap_result(fragile, "EncryptResult"); - Py_DECREF(fragile); -} - -%typemap(out) gpgme_decrypt_result_t { - PyObject *fragile; - fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, - %newpointer_flags); - $result = _gpg_wrap_result(fragile, "DecryptResult"); - Py_DECREF(fragile); -} - -%typemap(out) gpgme_sign_result_t { - PyObject *fragile; - fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, - %newpointer_flags); - $result = _gpg_wrap_result(fragile, "SignResult"); - Py_DECREF(fragile); -} - -%typemap(out) gpgme_verify_result_t { - PyObject *fragile; - fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, - %newpointer_flags); - $result = _gpg_wrap_result(fragile, "VerifyResult"); - Py_DECREF(fragile); -} - -%typemap(out) gpgme_import_result_t { - PyObject *fragile; - fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, - %newpointer_flags); - $result = _gpg_wrap_result(fragile, "ImportResult"); - Py_DECREF(fragile); -} - -%typemap(out) gpgme_genkey_result_t { +%define wrapresult(cls, name) +%typemap(out) cls { PyObject *fragile; fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, %newpointer_flags); - $result = _gpg_wrap_result(fragile, "GenkeyResult"); + $result = _gpg_wrap_result(fragile, name); Py_DECREF(fragile); } +%enddef -%typemap(out) gpgme_keylist_result_t { - PyObject *fragile; - fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, - %newpointer_flags); - $result = _gpg_wrap_result(fragile, "KeylistResult"); - Py_DECREF(fragile); -} - -%typemap(out) gpgme_vfs_mount_result_t { - PyObject *fragile; - fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, - %newpointer_flags); - $result = _gpg_wrap_result(fragile, "VFSMountResult"); - Py_DECREF(fragile); -} +wrapresult(gpgme_encrypt_result_t, "EncryptResult") +wrapresult(gpgme_decrypt_result_t, "DecryptResult") +wrapresult(gpgme_sign_result_t, "SignResult") +wrapresult(gpgme_verify_result_t, "VerifyResult") +wrapresult(gpgme_import_result_t, "ImportResult") +wrapresult(gpgme_genkey_result_t, "GenkeyResult") +wrapresult(gpgme_keylist_result_t, "KeylistResult") +wrapresult(gpgme_vfs_mount_result_t, "VFSMountResult") %typemap(out) gpgme_engine_info_t { int i; @@ -586,6 +548,15 @@ } } + +/* With SWIG, you can define default arguments for parameters. + * While it's legal in C++ it is not in C, so we cannot change the + * already existing gpgme.h. We need, however, to declare the function + * *before* SWIG loads it from gpgme.h. Hence, we define it here. */ +gpgme_error_t gpgme_op_keylist_start (gpgme_ctx_t ctx, + const char *pattern="", + int secret_only=0); + /* Include the unmodified <gpgme.h> for cc, and the cleaned-up local version for SWIG. We do, however, want to hide certain fields on some structs, which we provide prior to including the version for @@ -656,7 +627,17 @@ FILE *fdopen(int fildes, const char *mode); PyObject * _gpg_wrap_gpgme_data_t(gpgme_data_t data) { - return SWIG_Python_NewPointerObj(NULL, data, SWIGTYPE_p_gpgme_data, 0); + /* + * If SWIG is invoked without -builtin, the macro SWIG_NewPointerObj + * expects a variable named "self". + * + * XXX: It is not quite clear why passing NULL as self is okay, but + * it works with -builtin, and it seems to work just fine without + * it too. + */ + PyObject* self = NULL; + (void) self; + return SWIG_NewPointerObj(data, SWIGTYPE_p_gpgme_data, 0); } gpgme_ctx_t @@ -675,3 +656,38 @@ _gpg_unwrap_gpgme_ctx_t(PyObject *wrapped) /* ... but only the public definitions here. They will be exposed to the Python world, so let's be careful. */ %include "helpers.h" + + +%define genericrepr(cls) +%pythoncode %{ + def __repr__(self): + names = [name for name in dir(self) + if not name.startswith("_") and name != "this"] + props = ", ".join(("{}={!r}".format(name, getattr(self, name)) + for name in names) + ) + return "cls({})".format(props) +%} + +%enddef + +%extend _gpgme_key { + genericrepr(Key) +}; + + +%extend _gpgme_subkey { + genericrepr(SubKey) +}; + +%extend _gpgme_key_sig { + genericrepr(KeySig) +}; + +%extend _gpgme_user_id { + genericrepr(UID) +}; + +%extend _gpgme_tofu_info { + genericrepr(TofuInfo) +}; diff --git a/lang/python/helpers.c b/lang/python/helpers.c index 8f71a30..947819d 100644 --- a/lang/python/helpers.c +++ b/lang/python/helpers.c @@ -293,8 +293,10 @@ _gpg_obj2gpgme_data_t(PyObject *input, int argnum, gpgme_data_t *wrapper, return _gpg_obj2gpgme_t(data, "gpgme_data_t", argnum); return PyErr_Format(PyExc_TypeError, - "arg %d: expected gpg.Data, file, or an object " - "implementing the buffer protocol, got %s", + "arg %d: expected gpg.Data, file, " + "bytes (not string!), or an object " + "implementing the buffer protocol. Got: %s. " + "If you provided a string, try to encode() it.", argnum, data->ob_type->tp_name); } @@ -375,7 +377,21 @@ static gpgme_error_t pyPassphraseCb(void *hook, goto leave; } - PyTuple_SetItem(args, 1, PyBytes_FromString(passphrase_info)); + if (passphrase_info == NULL) + { + Py_INCREF(Py_None); + PyTuple_SetItem(args, 1, Py_None); + } + else + PyTuple_SetItem(args, 1, PyUnicode_DecodeUTF8(passphrase_info, + strlen (passphrase_info), + "strict")); + if (PyErr_Occurred()) { + Py_DECREF(args); + err_status = gpg_error(GPG_ERR_GENERAL); + goto leave; + } + PyTuple_SetItem(args, 2, PyBool_FromLong((long)prev_was_bad)); if (dataarg) { Py_INCREF(dataarg); /* Because GetItem doesn't give a ref but SetItem taketh away */ diff --git a/lang/python/setup.py.in b/lang/python/setup.py.in index 9669c28..bf4efa3 100755 --- a/lang/python/setup.py.in +++ b/lang/python/setup.py.in @@ -34,12 +34,12 @@ in_tree = False extra_swig_opts = [] extra_macros = dict() -if os.path.exists("../../src/gpgme-config"): +if os.path.exists("../../../src/gpgme-config"): # In-tree build. in_tree = True - gpgme_config = ["../../src/gpgme-config"] + gpgme_config_flags - gpgme_h = "../../src/gpgme.h" - library_dirs = ["../../src/.libs"] # XXX uses libtool internals + gpgme_config = ["../../../src/gpgme-config"] + gpgme_config_flags + gpgme_h = "../../../src/gpgme.h" + library_dirs = ["../../../src/.libs"] # XXX uses libtool internals extra_macros.update( HAVE_CONFIG_H=1, HAVE_DATA_H=1, @@ -152,9 +152,10 @@ class BuildExtFirstHack(build): self.run_command('build_ext') build.run(self) +py3 = [] if sys.version_info.major < 3 else ['-py3'] swige = Extension("gpg._gpgme", ["gpgme.i", "helpers.c"], - swig_opts = ['-py3', '-builtin', '-threads', - '-outdir', 'gpg'] + extra_swig_opts, + swig_opts = ['-threads', + '-outdir', 'gpg'] + py3 + extra_swig_opts, include_dirs = include_dirs, define_macros = define_macros, library_dirs = library_dirs, @@ -171,7 +172,8 @@ setup(name="gpg", url='https://www.gnupg.org', ext_modules=[swige], packages = ['gpg', 'gpg.constants', 'gpg.constants.data', - 'gpg.constants.keylist', 'gpg.constants.sig'], + 'gpg.constants.keylist', 'gpg.constants.sig', + 'gpg.constants.tofu'], license="LGPL2.1+ (the library), GPL2+ (tests and examples)", classifiers=[ 'Development Status :: 4 - Beta', diff --git a/lang/python/tests/Makefile.am b/lang/python/tests/Makefile.am index 39f532c..9c19a13 100644 --- a/lang/python/tests/Makefile.am +++ b/lang/python/tests/Makefile.am @@ -46,11 +46,16 @@ py_tests = t-wrapper.py \ t-trustlist.py \ t-edit.py \ t-keylist.py \ + t-keylist-from-data.py \ t-wait.py \ t-encrypt-large.py \ t-file-name.py \ t-idiomatic.py \ - t-protocol-assuan.py + t-protocol-assuan.py \ + t-quick-key-creation.py \ + t-quick-subkey-creation.py \ + t-quick-key-manipulation.py \ + t-quick-key-signing.py XTESTS = initial.py $(py_tests) final.py EXTRA_DIST = support.py $(XTESTS) encrypt-only.asc sign-only.asc \ @@ -73,7 +78,7 @@ xcheck: ./pubring-stamp CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \ gpg-agent.conf pubring.kbx~ gpg.conf pubring.gpg~ \ - random_seed .gpg-v21-migrated \ + random_seed .gpg-v21-migrated tofu.db \ pubring-stamp private-keys-v1.d/gpg-sample.stamp private_keys = \ @@ -107,8 +112,9 @@ clean-local: ./gpg.conf: # This is required for t-sig-notations. echo no-force-v3-sigs > ./gpg.conf + echo ignore-invalid-option agent-program >> ./gpg.conf + echo "agent-program `which $(GPG_AGENT)`|--debug-quick-random" >> ./gpg.conf ./gpg-agent.conf: # This is required for gpg2, which does not support command fd. echo pinentry-program $(abs_top_srcdir)/tests/gpg/pinentry >$@ - echo allow-loopback-pinentry >>$@ diff --git a/lang/python/tests/Makefile.in b/lang/python/tests/Makefile.in index ce60cd5..4940a8e 100644 --- a/lang/python/tests/Makefile.in +++ b/lang/python/tests/Makefile.in @@ -108,8 +108,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs @@ -358,11 +358,16 @@ py_tests = t-wrapper.py \ t-trustlist.py \ t-edit.py \ t-keylist.py \ + t-keylist-from-data.py \ t-wait.py \ t-encrypt-large.py \ t-file-name.py \ t-idiomatic.py \ - t-protocol-assuan.py + t-protocol-assuan.py \ + t-quick-key-creation.py \ + t-quick-subkey-creation.py \ + t-quick-key-manipulation.py \ + t-quick-key-signing.py XTESTS = initial.py $(py_tests) final.py EXTRA_DIST = support.py $(XTESTS) encrypt-only.asc sign-only.asc \ @@ -370,7 +375,7 @@ EXTRA_DIST = support.py $(XTESTS) encrypt-only.asc sign-only.asc \ CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \ gpg-agent.conf pubring.kbx~ gpg.conf pubring.gpg~ \ - random_seed .gpg-v21-migrated \ + random_seed .gpg-v21-migrated tofu.db \ pubring-stamp private-keys-v1.d/gpg-sample.stamp private_keys = \ @@ -612,11 +617,12 @@ clean-local: ./gpg.conf: # This is required for t-sig-notations. echo no-force-v3-sigs > ./gpg.conf + echo ignore-invalid-option agent-program >> ./gpg.conf + echo "agent-program `which $(GPG_AGENT)`|--debug-quick-random" >> ./gpg.conf ./gpg-agent.conf: # This is required for gpg2, which does not support command fd. echo pinentry-program $(abs_top_srcdir)/tests/gpg/pinentry >$@ - echo allow-loopback-pinentry >>$@ # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. diff --git a/lang/python/tests/initial.py b/lang/python/tests/initial.py index ebe7f8a..49e4f82 100755 --- a/lang/python/tests/initial.py +++ b/lang/python/tests/initial.py @@ -24,7 +24,8 @@ import os import subprocess import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) + +print("Using gpg module from {0!r}.".format(os.path.dirname(gpg.__file__))) subprocess.check_call([os.path.join(os.getenv('top_srcdir'), "tests", "start-stop-agent"), "--start"]) diff --git a/lang/python/tests/run-tests.py b/lang/python/tests/run-tests.py index 55d3f11..c4af526 100644 --- a/lang/python/tests/run-tests.py +++ b/lang/python/tests/run-tests.py @@ -39,6 +39,8 @@ parser.add_argument('tests', metavar='TEST', type=str, nargs='+', help='A test to run') parser.add_argument('-v', '--verbose', action="store_true", default=False, help='Be verbose.') +parser.add_argument('-q', '--quiet', action="store_true", default=False, + help='Be quiet.') parser.add_argument('--interpreters', metavar='PYTHON', type=str, default=[], action=SplitAndAccumulate, help='Use these interpreters to run the tests, ' + @@ -49,6 +51,8 @@ parser.add_argument('--srcdir', type=str, parser.add_argument('--builddir', type=str, default=os.environ.get("abs_builddir", ""), help='Location of the tests.') +parser.add_argument('--parallel', action="store_true", default=False, + help='Ignored. For compatibility with run-tests.scm.') args = parser.parse_args() if not args.interpreters: @@ -65,19 +69,29 @@ for interpreter in args.interpreters: version = subprocess.check_output( [interpreter, "-c", "import sys; print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"]).strip().decode() - builddirs = glob.glob(os.path.join(args.builddir, "..", "build", - "lib*"+version)) - assert len(builddirs) == 1, \ - "Expected one build directory, got {0}".format(builddirs) + pattern = os.path.join(args.builddir, "..", + "python{0}-gpg".format(version), + "build", + "lib*"+version) + builddirs = glob.glob(pattern) + if len(builddirs) == 0: + sys.exit("Build directory matching {0!r} not found.".format(pattern)) + elif len(builddirs) > 1: + sys.exit("Multiple build directories matching {0!r} found: {1}".format( + pattern, builddirs)) + env = dict(os.environ) env["PYTHONPATH"] = builddirs[0] - print("Running tests using {0} ({1})...".format(interpreter, version)) + if not args.quiet: + print("Running tests using {0} ({1})...".format(interpreter, version)) + for test in args.tests: status = subprocess.call( [interpreter, os.path.join(args.srcdir, test)], env=env, stdout=out, stderr=err) - print("{0}: {1}".format(status_to_str(status), test)) + if not args.quiet: + print("{0}: {1}".format(status_to_str(status), test)) results.append(status) def count(status): @@ -85,6 +99,8 @@ def count(status): def failed(): return len(list(filter(lambda x: x not in (0, 77, 99), results))) -print("{0} tests run, {1} succeeded, {2} failed, {3} skipped.".format( - len(results), count(0), failed(), count(77))) -sys.exit(len(results) - count(0)) +if not args.quiet: + print("{0} tests run, {1} succeeded, {2} failed, {3} skipped.".format( + len(results), count(0), failed(), count(77))) + sys.exit(len(results) - count(0)) +sys.exit(results[0]) diff --git a/lang/python/tests/support.py b/lang/python/tests/support.py index f991c6d..fabd818 100644 --- a/lang/python/tests/support.py +++ b/lang/python/tests/support.py @@ -18,10 +18,28 @@ from __future__ import absolute_import, print_function, unicode_literals del absolute_import, print_function, unicode_literals +import contextlib +import shutil import sys import os +import re +import tempfile +import time import gpg +def assert_gpg_version(version=(2, 1, 0)): + with gpg.Context() as c: + clean_version = re.match(r'\d+\.\d+\.\d+', c.engine_info.version).group(0) + if tuple(map(int, clean_version.split('.'))) < version: + print("GnuPG too old: have {0}, need {1}.".format( + c.engine_info.version, '.'.join(map(str, version)))) + sys.exit(77) + +# Skip the Python tests for GnuPG < 2.1.12. Prior versions do not +# understand the command line flags that we assume exist. C.f. issue +# 3008. +assert_gpg_version((2, 1, 12)) + # known keys alpha = "A0FF4590BB6122EDEF6E3C542D727CC768697734" bob = "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2" @@ -35,9 +53,6 @@ def make_filename(name): def in_srcdir(name): return os.path.join(os.environ['srcdir'], name) -def init_gpgme(proto): - gpg.core.engine_check_version(proto) - verbose = int(os.environ.get('verbose', 0)) > 1 def print_data(data): if verbose: @@ -48,7 +63,11 @@ def print_data(data): except: # Hope for the best. pass - sys.stdout.buffer.write(data) + + if hasattr(sys.stdout, "buffer"): + sys.stdout.buffer.write(data) + else: + sys.stdout.write(data) def mark_key_trusted(ctx, key): class Editor(object): @@ -68,3 +87,41 @@ def mark_key_trusted(ctx, key): return result with gpg.Data() as sink: ctx.op_edit(key, Editor().edit, sink, sink) + + +# Python3.2 and up has tempfile.TemporaryDirectory, but we cannot use +# that, because there shutil.rmtree is used without +# ignore_errors=True, and that races against gpg-agent deleting its +# sockets. +class TemporaryDirectory(object): + def __enter__(self): + self.path = tempfile.mkdtemp() + return self.path + def __exit__(self, *args): + shutil.rmtree(self.path, ignore_errors=True) + +@contextlib.contextmanager +def EphemeralContext(): + with TemporaryDirectory() as tmp: + home = os.environ['GNUPGHOME'] + shutil.copy(os.path.join(home, "gpg.conf"), tmp) + shutil.copy(os.path.join(home, "gpg-agent.conf"), tmp) + + with gpg.Context(home_dir=tmp) as ctx: + yield ctx + + # Ask the agent to quit. + agent_socket = os.path.join(tmp, "S.gpg-agent") + ctx.protocol = gpg.constants.protocol.ASSUAN + ctx.set_engine_info(ctx.protocol, file_name=agent_socket) + try: + ctx.assuan_transact(["KILLAGENT"]) + except gpg.errors.GPGMEError as e: + if e.getcode() == gpg.errors.ASS_CONNECT_FAILED: + pass # the agent was not running + else: + raise + + # Block until it is really gone. + while os.path.exists(agent_socket): + time.sleep(.01) diff --git a/lang/python/tests/t-callbacks.py b/lang/python/tests/t-callbacks.py index eed50bc..94cf11e 100755 --- a/lang/python/tests/t-callbacks.py +++ b/lang/python/tests/t-callbacks.py @@ -24,7 +24,7 @@ import os import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) +support.assert_gpg_version() c = gpg.Context() c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK) diff --git a/lang/python/tests/t-decrypt-verify.py b/lang/python/tests/t-decrypt-verify.py index 6243167..03bbc4b 100755 --- a/lang/python/tests/t-decrypt-verify.py +++ b/lang/python/tests/t-decrypt-verify.py @@ -34,7 +34,6 @@ def check_verify_result(result, summary, fpr, status): assert sig.validity == gpg.constants.validity.FULL assert gpg.errors.GPGMEError(sig.validity_reason).getcode() == gpg.errors.NO_ERROR -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() source = gpg.Data(file=support.make_filename("cipher-2.asc")) diff --git a/lang/python/tests/t-decrypt.py b/lang/python/tests/t-decrypt.py index 1af0562..05b6d8b 100755 --- a/lang/python/tests/t-decrypt.py +++ b/lang/python/tests/t-decrypt.py @@ -23,7 +23,6 @@ del absolute_import, print_function, unicode_literals import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() source = gpg.Data(file=support.make_filename("cipher-1.asc")) diff --git a/lang/python/tests/t-edit.py b/lang/python/tests/t-edit.py index bd70e7e..ffc3296 100755 --- a/lang/python/tests/t-edit.py +++ b/lang/python/tests/t-edit.py @@ -26,6 +26,8 @@ import os import gpg import support +support.assert_gpg_version() + class KeyEditor(object): def __init__(self): self.steps = ["fpr", "expire", "1", "primary", "quit"] @@ -51,8 +53,6 @@ class KeyEditor(object): return result -support.init_gpgme(gpg.constants.protocol.OpenPGP) - c = gpg.Context() c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK) c.set_passphrase_cb(lambda *args: "abc") diff --git a/lang/python/tests/t-encrypt-large.py b/lang/python/tests/t-encrypt-large.py index cdb4a32..5646085 100755 --- a/lang/python/tests/t-encrypt-large.py +++ b/lang/python/tests/t-encrypt-large.py @@ -30,7 +30,6 @@ if len(sys.argv) == 2: else: nbytes = 100000 -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() ntoread = nbytes diff --git a/lang/python/tests/t-encrypt-sign.py b/lang/python/tests/t-encrypt-sign.py index 094a2b0..f04783f 100755 --- a/lang/python/tests/t-encrypt-sign.py +++ b/lang/python/tests/t-encrypt-sign.py @@ -24,7 +24,6 @@ import sys import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_armor(True) diff --git a/lang/python/tests/t-encrypt-sym.py b/lang/python/tests/t-encrypt-sym.py index 07e6b62..8ee9cd6 100755 --- a/lang/python/tests/t-encrypt-sym.py +++ b/lang/python/tests/t-encrypt-sym.py @@ -24,7 +24,7 @@ import os import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) +support.assert_gpg_version() for passphrase in ("abc", b"abc"): c = gpg.Context() diff --git a/lang/python/tests/t-encrypt.py b/lang/python/tests/t-encrypt.py index 0c0ca35..921502a 100755 --- a/lang/python/tests/t-encrypt.py +++ b/lang/python/tests/t-encrypt.py @@ -23,7 +23,6 @@ del absolute_import, print_function, unicode_literals import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_armor(True) @@ -62,3 +61,18 @@ with gpg.Context(armor=True) as c: assert support.sign_only.endswith(e.recipients[0].fpr) else: assert False, "Expected an InvalidRecipients error, got none" + + + + try: + # People might be tempted to provide strings. + # We should raise something useful. + ciphertext, _, _ = c.encrypt("Hallo Leute\n", + recipients=keys, + sign=False, + always_trust=True) + except TypeError as e: + # This test is a bit fragile, because the message + # may very well change. So if the behaviour will change + # this test can easily be deleted. + assert "encode" in str(e) diff --git a/lang/python/tests/t-export.py b/lang/python/tests/t-export.py index 4927beb..b9d5204 100755 --- a/lang/python/tests/t-export.py +++ b/lang/python/tests/t-export.py @@ -23,7 +23,6 @@ del absolute_import, print_function, unicode_literals import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_armor(True) diff --git a/lang/python/tests/t-file-name.py b/lang/python/tests/t-file-name.py index d12afb8..aab5680 100755 --- a/lang/python/tests/t-file-name.py +++ b/lang/python/tests/t-file-name.py @@ -26,7 +26,6 @@ import support testname = "abcde12345" -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_armor(True) diff --git a/lang/python/tests/t-idiomatic.py b/lang/python/tests/t-idiomatic.py index 485f048..826bc23 100755 --- a/lang/python/tests/t-idiomatic.py +++ b/lang/python/tests/t-idiomatic.py @@ -27,8 +27,6 @@ import tempfile import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) - # Both Context and Data can be used as context manager: with gpg.Context() as c, gpg.Data() as d: c.get_engine_info() diff --git a/lang/python/tests/t-import.py b/lang/python/tests/t-import.py index 5b0576f..e2edf5a 100755 --- a/lang/python/tests/t-import.py +++ b/lang/python/tests/t-import.py @@ -67,7 +67,6 @@ def check_result(result, fpr, secret): assert len(result.imports) == 1 or fpr == result.imports[1].fpr assert result.imports[0].result == 0 -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.op_import(gpg.Data(file=support.make_filename("pubkey-1.asc"))) diff --git a/lang/python/tests/t-keylist-from-data.py b/lang/python/tests/t-keylist-from-data.py new file mode 100755 index 0000000..6a26267 --- /dev/null +++ b/lang/python/tests/t-keylist-from-data.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python + +# Copyright (C) 2016 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME 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. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +import os +import sys +import gpg +import support + +support.assert_gpg_version((2, 1, 14)) + +# Check expration of keys. This test assumes three subkeys of which +# 2 are expired; it is used with the "Whisky" test key. It has +# already been checked that these 3 subkeys are available. +def check_whisky(name, key): + sub1 = key.subkeys[2] + sub2 = key.subkeys[3] + + assert sub1.expired and sub2.expired, \ + "Subkey of `{}' not flagged as expired".format(name) + assert sub1.expires == 1129636886 and sub2.expires == 1129636939, \ + "Subkey of `{}' has wrong expiration date".format(name) + +keys = [ + [ "A0FF4590BB6122EDEF6E3C542D727CC768697734", "6AE6D7EE46A871F8", + [ [ "Alfa Test", "demo key", "alfa@example.net" ], + [ "Alpha Test", "demo key", "alpha@example.net" ], + [ "Alice", "demo key", "" ] ], 1 ], + [ "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F", + [ [ "Bob", "demo key", "" ], + [ "Bravo Test", "demo key", "bravo@example.net" ] ], 1 ], + [ "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60", + [ [ "Charlie Test", "demo key", "charlie@example.net" ] ], 1 ], + [ "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424", + [ [ "Delta Test", "demo key", "delta@example.net" ] ], 1 ], + [ "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D", + [ [ "Echelon", "demo key", "" ], + [ "Echo Test", "demo key", "echo@example.net" ], + [ "Eve", "demo key", "" ] ], 1 ], + [ "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E", + [ [ "Foxtrot Test", "demo key", "foxtrot@example.net" ] ], 1 ], + [ "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354", + [ [ "Golf Test", "demo key", "golf@example.net" ] ], 1 ], + [ "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A", + [ [ "Hotel Test", "demo key", "hotel@example.net" ] ], 1 ], + [ "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73", + [ [ "India Test", "demo key", "india@example.net" ] ], 1 ], + [ "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136", + [ [ "Juliet Test", "demo key", "juliet@example.net" ] ], 1 ], + [ "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02", + [ [ "Kilo Test", "demo key", "kilo@example.net" ] ], 1 ], + [ "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C", + [ [ "Lima Test", "demo key", "lima@example.net" ] ], 1 ], + [ "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8", + [ [ "Mallory", "demo key", "" ], + [ "Mike Test", "demo key", "mike@example.net" ] ], 1 ], + [ "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472", + [ [ "November Test", "demo key", "november@example.net" ] ], 1 ], + [ "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F", + [ [ "Oscar Test", "demo key", "oscar@example.net" ] ], 1 ], + [ "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C", + [ [ "Papa test", "demo key", "papa@example.net" ] ], 1 ], + [ "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4", + [ [ "Quebec Test", "demo key", "quebec@example.net" ] ], 1 ], + [ "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA", + [ [ "Romeo Test", "demo key", "romeo@example.net" ] ], 1 ], + [ "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4", + [ [ "Sierra Test", "demo key", "sierra@example.net" ] ], 1 ], + [ "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402", + [ [ "Tango Test", "demo key", "tango@example.net" ] ], 1 ], + [ "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9", + [ [ "Uniform Test", "demo key", "uniform@example.net" ] ], 1 ], + [ "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134", + [ [ "Victor Test", "demo key", "victor@example.org" ] ], 1 ], + [ "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6", + [ [ "Whisky Test", "demo key", "whisky@example.net" ] ], 3, + check_whisky ], + [ "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE", + [ [ "XRay Test", "demo key", "xray@example.net" ] ], 1 ], + [ "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD", + [ [ "Yankee Test", "demo key", "yankee@example.net" ] ], 1 ], + [ "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881", + [ [ "Zulu Test", "demo key", "zulu@example.net" ] ], 1 ], +] + +def check_global(key, uids, n_subkeys): + assert not key.revoked, "Key unexpectedly revoked" + assert not key.expired, "Key unexpectedly expired" + assert not key.disabled, "Key unexpectedly disabled" + assert not key.invalid, "Key unexpectedly invalid" + assert key.can_sign, "Key unexpectedly unusable for signing" + assert key.can_certify, "Key unexpectedly unusable for certifications" + assert not key.secret, "Key unexpectedly secret" + assert not key.protocol != gpg.constants.protocol.OpenPGP, \ + "Key has unexpected protocol: {}".format(key.protocol) + assert not key.issuer_serial, \ + "Key unexpectedly carries issuer serial: {}".format(key.issuer_serial) + assert not key.issuer_name, \ + "Key unexpectedly carries issuer name: {}".format(key.issuer_name) + assert not key.chain_id, \ + "Key unexpectedly carries chain ID: {}".format(key.chain_id) + assert key.owner_trust == gpg.constants.validity.UNKNOWN, \ + "Key has unexpected owner trust: {}".format(key.owner_trust) + assert len(key.subkeys) - 1 == n_subkeys, \ + "Key `{}' has unexpected number of subkeys".format(uids[0][0]) + + +def check_subkey(fpr, which, subkey): + assert not subkey.revoked, which + " key unexpectedly revoked" + assert not subkey.expired, which + " key unexpectedly expired" + assert not subkey.disabled, which + " key unexpectedly disabled" + assert not subkey.invalid, which + " key unexpectedly invalid" + + if which == "Primary": + assert not subkey.can_encrypt, \ + which + " key unexpectedly usable for encryption" + assert subkey.can_sign, \ + which + " key unexpectedly unusable for signing" + assert subkey.can_certify, \ + which + " key unexpectedly unusable for certifications" + else: + assert subkey.can_encrypt, \ + which + " key unexpectedly unusable for encryption" + assert not subkey.can_sign, \ + which + " key unexpectedly usable for signing" + assert not subkey.can_certify, \ + which + " key unexpectedly usable for certifications" + + assert not subkey.secret, which + " key unexpectedly secret" + assert not subkey.is_cardkey, "Public key marked as card key" + assert not subkey.card_number, "Public key with card number set" + assert not subkey.pubkey_algo != (gpg.constants.pk.DSA if which == "Primary" + else gpg.constants.pk.ELG_E), \ + which + " key has unexpected public key algo: {}".\ + format(subkey.pubkey_algo) + assert subkey.length == 1024, \ + which + " key has unexpected length: {}".format(subkey.length) + assert fpr.endswith(subkey.keyid), \ + which + " key has unexpected key ID: {}".format(subkey.keyid) + assert which == "Secondary" or subkey.fpr == fpr, \ + which + " key has unexpected fingerprint: {}".format(subkey.fpr) + assert not subkey.expires, \ + which + " key unexpectedly expires: {}".format(subkey.expires) + +def check_uid(which, ref, uid): + assert not uid.revoked, which + " user ID unexpectedly revoked" + assert not uid.invalid, which + " user ID unexpectedly invalid" + assert uid.validity == gpg.constants.validity.UNKNOWN, \ + which + " user ID has unexpected validity: {}".format(uid.validity) + assert not uid.signatures, which + " user ID unexpectedly signed" + assert uid.name == ref[0], \ + "Unexpected name in {} user ID: {!r}".format(which.lower(), uid.name) + assert uid.comment == ref[1], \ + "Unexpected comment in {} user ID: {!r}".format(which.lower(), + uid.comment) + assert uid.email == ref[2], \ + "Unexpected email in {} user ID: {!r}".format(which.lower(), uid.email) + +# Export all the data from our keyring... +key_data = gpg.Data() +with gpg.Context() as c: + c.op_export_keys([c.get_key(k[0]) for k in keys], 0, key_data) + +# ... rewind the tape... +key_data.rewind() + +# ... and feed it into a keylist in an empty context. +with support.EphemeralContext() as c: + for i, key in enumerate(c.keylist(source=key_data)): + try: + if len(keys[i]) == 4: + fpr, sec_keyid, uids, n_subkeys = keys[i] + misc_check = None + else: + fpr, sec_keyid, uids, n_subkeys, misc_check = keys[i] + except IndexError: + # There are more keys. We don't check for that. + break + + # Global key flags. + check_global(key, uids, n_subkeys) + check_subkey(fpr, "Primary", key.subkeys[0]) + check_subkey(sec_keyid, "Secondary", key.subkeys[1]) + + assert len(key.uids) == len(uids) + check_uid("First", uids[0], key.uids[0]) + if len(key.uids) > 1: + check_uid("Second", uids[1], key.uids[1]) + if len(key.uids) > 2: + check_uid("Third", uids[2], key.uids[2]) + + if misc_check: + misc_check (uids[0][0], key) + + assert len(list(c.keylist())) == 0, "Keys were imported" diff --git a/lang/python/tests/t-keylist.py b/lang/python/tests/t-keylist.py index ea2a724..76c793e 100755 --- a/lang/python/tests/t-keylist.py +++ b/lang/python/tests/t-keylist.py @@ -23,7 +23,6 @@ del absolute_import, print_function, unicode_literals import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() # Check expration of keys. This test assumes three subkeys of which @@ -219,6 +218,18 @@ result = c.op_keylist_result() assert not result.truncated, "Key listing unexpectedly truncated" +# We test for a parameter-less keylist +keyring_length = len(list(c.op_keylist_all())) +assert keyring_length > 1,\ + "Expected to find some keys, but got %r" % keyring_length + +# Then we do want to call with a pattern, only +# i.e. without giving secret=0 +alpha_keys = list(c.op_keylist_all(b"Alpha")) +assert len(alpha_keys) == 1, "Expected only one key for 'Alpha', got %r" % len(alpha_keys) + + + for i, key in enumerate(c.keylist()): try: if len(keys[i]) == 4: diff --git a/lang/python/tests/t-protocol-assuan.py b/lang/python/tests/t-protocol-assuan.py index 0084a6b..27b28c7 100755 --- a/lang/python/tests/t-protocol-assuan.py +++ b/lang/python/tests/t-protocol-assuan.py @@ -24,9 +24,12 @@ import gpg with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c: # Do nothing. - c.assuan_transact('nop') - c.assuan_transact('NOP') - c.assuan_transact(['NOP']) + err = c.assuan_transact('nop') + assert err == None + err = c.assuan_transact(b'NOP') + assert err == None + err = c.assuan_transact(['NOP']) + assert err == None err = c.assuan_transact('idontexist') assert err.getsource() == gpg.errors.SOURCE_GPGAGENT diff --git a/lang/python/tests/t-quick-key-creation.py b/lang/python/tests/t-quick-key-creation.py new file mode 100755 index 0000000..8b7372e --- /dev/null +++ b/lang/python/tests/t-quick-key-creation.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME 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. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +import gpg +import itertools +import time + +import support +support.assert_gpg_version((2, 1, 2)) + +alpha = "Alpha <alpha@invalid.example.net>" + +with support.EphemeralContext() as ctx: + res = ctx.create_key(alpha) + + keys = list(ctx.keylist()) + assert len(keys) == 1, "Weird number of keys created" + + key = keys[0] + assert key.fpr == res.fpr + assert len(key.subkeys) == 2, "Expected one primary key and one subkey" + assert key.subkeys[0].expires > 0, "Expected primary key to expire" + + # Try to create a key with the same UID + try: + ctx.create_key(alpha) + assert False, "Expected an error but got none" + except gpg.errors.GpgError as e: + pass + + # Try to create a key with the same UID, now with force! + res2 = ctx.create_key(alpha, force=True) + assert res.fpr != res2.fpr + + +# From here on, we use one context, and create unique UIDs +uid_counter = 0 +def make_uid(): + global uid_counter + uid_counter += 1 + return "user{0}@invalid.example.org".format(uid_counter) + +with support.EphemeralContext() as ctx: + # Check gpg.constants.create.NOEXPIRE... + res = ctx.create_key(make_uid(), expires=False) + key = ctx.get_key(res.fpr, secret=True) + assert key.fpr == res.fpr + assert len(key.subkeys) == 2, "Expected one primary key and one subkey" + assert key.subkeys[0].expires == 0, "Expected primary key not to expire" + + t = 2 * 24 * 60 * 60 + slack = 5 * 60 + res = ctx.create_key(make_uid(), expires_in=t) + key = ctx.get_key(res.fpr, secret=True) + assert key.fpr == res.fpr + assert len(key.subkeys) == 2, "Expected one primary key and one subkey" + assert abs(time.time() + t - key.subkeys[0].expires) < slack, \ + "Primary keys expiration time is off" + + # Check capabilities + for sign, encrypt, certify, authenticate in itertools.product([False, True], + [False, True], + [False, True], + [False, True]): + # Filter some out + if not (sign or encrypt or certify or authenticate): + # This triggers the default capabilities tested before. + continue + if (sign or encrypt or authenticate) and not certify: + # The primary key always certifies. + continue + + res = ctx.create_key(make_uid(), algorithm="rsa", + sign=sign, encrypt=encrypt, certify=certify, + authenticate=authenticate) + key = ctx.get_key(res.fpr, secret=True) + assert key.fpr == res.fpr + assert len(key.subkeys) == 1, \ + "Expected no subkey for non-default capabilities" + + p = key.subkeys[0] + assert sign == p.can_sign + assert encrypt == p.can_encrypt + assert certify == p.can_certify + assert authenticate == p.can_authenticate + + # Check algorithm + res = ctx.create_key(make_uid(), algorithm="rsa") + key = ctx.get_key(res.fpr, secret=True) + assert key.fpr == res.fpr + for k in key.subkeys: + assert k.pubkey_algo == 1 + + # Check algorithm with size + res = ctx.create_key(make_uid(), algorithm="rsa1024") + key = ctx.get_key(res.fpr, secret=True) + assert key.fpr == res.fpr + for k in key.subkeys: + assert k.pubkey_algo == 1 + assert k.length == 1024 + + # Check algorithm future-default + ctx.create_key(make_uid(), algorithm="future-default") + + # Check passphrase protection + recipient = make_uid() + passphrase = "streng geheim" + res = ctx.create_key(recipient, passphrase=passphrase) + ciphertext, _, _ = ctx.encrypt(b"hello there", recipients=[ctx.get_key(res.fpr)]) + + cb_called = False + def cb(*args): + global cb_called + cb_called = True + return passphrase + ctx.pinentry_mode = gpg.constants.PINENTRY_MODE_LOOPBACK + ctx.set_passphrase_cb(cb) + + plaintext, _, _ = ctx.decrypt(ciphertext) + assert plaintext == b"hello there" + assert cb_called diff --git a/lang/python/tests/t-quick-key-manipulation.py b/lang/python/tests/t-quick-key-manipulation.py new file mode 100755 index 0000000..0f47006 --- /dev/null +++ b/lang/python/tests/t-quick-key-manipulation.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME 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. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +import os +import gpg + +import support +support.assert_gpg_version((2, 1, 14)) + +alpha = "Alpha <alpha@invalid.example.net>" +bravo = "Bravo <bravo@invalid.example.net>" + +with support.EphemeralContext() as ctx: + res = ctx.create_key(alpha, certify=True) + key = ctx.get_key(res.fpr) + assert len(key.subkeys) == 1, "Expected one primary key and no subkeys" + assert len(key.uids) == 1, "Expected exactly one UID" + + def get_uid(uid): + key = ctx.get_key(res.fpr) + for u in key.uids: + if u.uid == uid: + return u + return None + + # sanity check + uid = get_uid(alpha) + assert uid, "UID alpha not found" + assert uid.revoked == 0 + + # add bravo + ctx.key_add_uid(key, bravo) + uid = get_uid(bravo) + assert uid, "UID bravo not found" + assert uid.revoked == 0 + + # revoke alpha + ctx.key_revoke_uid(key, alpha) + uid = get_uid(alpha) + assert uid, "UID alpha not found" + assert uid.revoked == 1 + uid = get_uid(bravo) + assert uid, "UID bravo not found" + assert uid.revoked == 0 + + # try to revoke the last UID + try: + ctx.key_revoke_uid(key, alpha) + # IMHO this should fail. issue2961. + # assert False, "Expected an error but got none" + except gpg.errors.GpgError: + pass + + # Everything should be the same + uid = get_uid(alpha) + assert uid, "UID alpha not found" + assert uid.revoked == 1 + uid = get_uid(bravo) + assert uid, "UID bravo not found" + assert uid.revoked == 0 + + # try to revoke a non-existent UID + try: + ctx.key_revoke_uid(key, "i dont exist") + # IMHO this should fail. issue2963. + # assert False, "Expected an error but got none" + except gpg.errors.GpgError: + pass + + # try to add an pre-existent UID + try: + ctx.key_add_uid(key, bravo) + assert False, "Expected an error but got none" + except gpg.errors.GpgError: + pass + + # Check setting the TOFU policy. + with open(os.path.join(ctx.home_dir, "gpg.conf"), "a") as handle: + handle.write("trust-model tofu+pgp\n") + + for name, policy in [(name, getattr(gpg.constants.tofu.policy, name)) + for name in filter(lambda x: not x.startswith('__'), + dir(gpg.constants.tofu.policy))]: + if policy == gpg.constants.tofu.policy.NONE: + # We must not set the policy to NONE. + continue + + ctx.key_tofu_policy(key, policy) + + keys = list(ctx.keylist(key.uids[0].uid, + mode=(gpg.constants.keylist.mode.LOCAL + |gpg.constants.keylist.mode.WITH_TOFU))) + assert len(keys) == 1 + + if policy == gpg.constants.tofu.policy.AUTO: + # We cannot check that it is set to AUTO. + continue + + for uid in keys[0].uids: + if uid.uid == alpha: + # TOFU information of revoked UIDs is not updated. + # XXX: Is that expected? + continue + assert uid.tofu[0].policy == policy, \ + "Expected policy {0} ({1}), got {2}".format(policy, name, + uid.tofu[0].policy) diff --git a/lang/python/tests/t-quick-key-signing.py b/lang/python/tests/t-quick-key-signing.py new file mode 100755 index 0000000..3d648c5 --- /dev/null +++ b/lang/python/tests/t-quick-key-signing.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME 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. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +import gpg +import itertools +import time + +import support +support.assert_gpg_version((2, 1, 1)) + +with support.EphemeralContext() as ctx: + uid_counter = 0 + def make_uid(): + global uid_counter + uid_counter += 1 + return "user{0}@invalid.example.org".format(uid_counter) + + def make_key(): + uids = [make_uid() for i in range(3)] + res = ctx.create_key(uids[0], certify=True) + key = ctx.get_key(res.fpr) + for u in uids[1:]: + ctx.key_add_uid(key, u) + return key, uids + + def check_sigs(key, expected_sigs): + keys = list(ctx.keylist(key.fpr, mode=(gpg.constants.keylist.mode.LOCAL + |gpg.constants.keylist.mode.SIGS))) + assert len(keys) == 1 + key_uids = {uid.uid: [s for s in uid.signatures] for uid in keys[0].uids} + expected = list(expected_sigs) + + while key_uids and expected: + uid, signing_key, func = expected[0] + match = False + for i, s in enumerate(key_uids[uid]): + if signing_key.fpr.endswith(s.keyid): + if func: + func(s) + match = True + break + if match: + expected.pop(0) + key_uids[uid].pop(i) + if not key_uids[uid]: + del key_uids[uid] + + assert not key_uids, "Superfluous signatures: {0}".format(key_uids) + assert not expected, "Missing signatures: {0}".format(expected) + + # Simplest case. Sign without any options. + key_a, uids_a = make_key() + key_b, uids_b = make_key() + ctx.signers = [key_a] + + def exportable_non_expiring(s): + assert s.exportable + assert s.expires == 0 + + check_sigs(key_b, itertools.product(uids_b, [key_b], [exportable_non_expiring])) + ctx.key_sign(key_b) + check_sigs(key_b, itertools.product(uids_b, [key_b, key_a], [exportable_non_expiring])) + + # Create a non-exportable signature, and explicitly name all uids. + key_c, uids_c = make_key() + ctx.signers = [key_a, key_b] + + def non_exportable_non_expiring(s): + assert s.exportable == 0 + assert s.expires == 0 + + ctx.key_sign(key_c, local=True, uids=uids_c) + check_sigs(key_c, + list(itertools.product(uids_c, [key_c], + [exportable_non_expiring])) + + list(itertools.product(uids_c, [key_b, key_a], + [non_exportable_non_expiring]))) + + # Create a non-exportable, expiring signature for a single uid. + key_d, uids_d = make_key() + ctx.signers = [key_c] + expires_in = 600 + slack = 10 + + def non_exportable_expiring(s): + assert s.exportable == 0 + assert abs(time.time() + expires_in - s.expires) < slack + + ctx.key_sign(key_d, local=True, expires_in=expires_in, uids=uids_d[0]) + check_sigs(key_d, + list(itertools.product(uids_d, [key_d], + [exportable_non_expiring])) + + list(itertools.product(uids_d[:1], [key_c], + [non_exportable_expiring]))) + + # Now sign the second in the same fashion, but use a singleton list. + ctx.key_sign(key_d, local=True, expires_in=expires_in, uids=uids_d[1:2]) + check_sigs(key_d, + list(itertools.product(uids_d, [key_d], + [exportable_non_expiring])) + + list(itertools.product(uids_d[:2], [key_c], + [non_exportable_expiring]))) diff --git a/lang/python/tests/t-quick-subkey-creation.py b/lang/python/tests/t-quick-subkey-creation.py new file mode 100755 index 0000000..ad4f35c --- /dev/null +++ b/lang/python/tests/t-quick-subkey-creation.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME 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. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +import gpg +import itertools +import time + +import support + +alpha = "Alpha <alpha@invalid.example.net>" +bravo = "Bravo <bravo@invalid.example.net>" + +with support.EphemeralContext() as ctx: + res = ctx.create_key(alpha, certify=True) + keys = list(ctx.keylist()) + assert len(keys) == 1, "Weird number of keys created" + key = keys[0] + assert key.fpr == res.fpr + assert len(key.subkeys) == 1, "Expected one primary key and no subkeys" + + def get_subkey(fpr): + k = ctx.get_key(fpr) + for sk in k.subkeys: + if sk.fpr == fpr: + return sk + return None + + # Check gpg.constants.create.NOEXPIRE... + res = ctx.create_subkey(key, expires=False) + subkey = get_subkey(res.fpr) + assert subkey.expires == 0, "Expected subkey not to expire" + assert subkey.can_encrypt, \ + "Default subkey capabilities do not include encryption" + + t = 2 * 24 * 60 * 60 + slack = 5 * 60 + res = ctx.create_subkey(key, expires_in=t) + subkey = get_subkey(res.fpr) + assert abs(time.time() + t - subkey.expires) < slack, \ + "subkeys expiration time is off" + + # Check capabilities + for sign, encrypt, authenticate in itertools.product([False, True], + [False, True], + [False, True]): + # Filter some out + if not (sign or encrypt or authenticate): + # This triggers the default capabilities tested before. + continue + + res = ctx.create_subkey(key, sign=sign, encrypt=encrypt, + authenticate=authenticate) + subkey = get_subkey(res.fpr) + assert sign == subkey.can_sign + assert encrypt == subkey.can_encrypt + assert authenticate == subkey.can_authenticate + + # Check algorithm + res = ctx.create_subkey(key, algorithm="rsa") + subkey = get_subkey(res.fpr) + assert subkey.pubkey_algo == 1 + + # Check algorithm with size + res = ctx.create_subkey(key, algorithm="rsa1024") + subkey = get_subkey(res.fpr) + assert subkey.pubkey_algo == 1 + assert subkey.length == 1024 + + # Check algorithm future-default + ctx.create_subkey(key, algorithm="future-default") + + # Check passphrase protection. For this we create a new key + # so that we have a key with just one encryption subkey. + bravo_res = ctx.create_key(bravo, certify=True) + bravo_key = ctx.get_key(bravo_res.fpr) + assert len(bravo_key.subkeys) == 1, "Expected one primary key and no subkeys" + + passphrase = "streng geheim" + res = ctx.create_subkey(bravo_key, passphrase=passphrase) + ciphertext, _, _ = ctx.encrypt(b"hello there", + recipients=[ctx.get_key(bravo_res.fpr)]) + + cb_called = False + def cb(*args): + global cb_called + cb_called = True + return passphrase + ctx.pinentry_mode = gpg.constants.PINENTRY_MODE_LOOPBACK + ctx.set_passphrase_cb(cb) + + plaintext, _, _ = ctx.decrypt(ciphertext) + assert plaintext == b"hello there" + assert cb_called diff --git a/lang/python/tests/t-sig-notation.py b/lang/python/tests/t-sig-notation.py index f1342b1..2277497 100755 --- a/lang/python/tests/t-sig-notation.py +++ b/lang/python/tests/t-sig-notation.py @@ -62,8 +62,6 @@ def check_result(result): assert len(expected_notations) == 0 -support.init_gpgme(gpg.constants.protocol.OpenPGP) - source = gpg.Data("Hallo Leute\n") signed = gpg.Data() diff --git a/lang/python/tests/t-sign.py b/lang/python/tests/t-sign.py index 9418ed8..d375729 100755 --- a/lang/python/tests/t-sign.py +++ b/lang/python/tests/t-sign.py @@ -53,8 +53,6 @@ def check_result(r, typ): if signature.fpr != "A0FF4590BB6122EDEF6E3C542D727CC768697734": fail("Wrong fingerprint reported: {}".format(signature.fpr)) - -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_textmode(True) c.set_armor(True) diff --git a/lang/python/tests/t-signers.py b/lang/python/tests/t-signers.py index 80e797c..5864ee5 100755 --- a/lang/python/tests/t-signers.py +++ b/lang/python/tests/t-signers.py @@ -53,8 +53,6 @@ def check_result(r, typ): "23FD347A419429BACCD5E72D6BC4778054ACD246"): fail("Wrong fingerprint reported: {}".format(signature.fpr)) - -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_textmode(True) c.set_armor(True) diff --git a/lang/python/tests/t-trustlist.py b/lang/python/tests/t-trustlist.py index 8c5e214..8586596 100755 --- a/lang/python/tests/t-trustlist.py +++ b/lang/python/tests/t-trustlist.py @@ -23,7 +23,6 @@ del absolute_import, print_function, unicode_literals import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() def dump_item(item): diff --git a/lang/python/tests/t-verify.py b/lang/python/tests/t-verify.py index f18e1dd..0347638 100755 --- a/lang/python/tests/t-verify.py +++ b/lang/python/tests/t-verify.py @@ -97,8 +97,6 @@ def check_result(result, summary, validity, fpr, status, notation): sig.validity, validity) assert gpg.errors.GPGMEError(sig.validity_reason).getcode() == gpg.errors.NO_ERROR - -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_armor(True) diff --git a/lang/python/tests/t-wait.py b/lang/python/tests/t-wait.py index b1f2043..0c403fa 100755 --- a/lang/python/tests/t-wait.py +++ b/lang/python/tests/t-wait.py @@ -24,7 +24,6 @@ import time import gpg import support -support.init_gpgme(gpg.constants.protocol.OpenPGP) c = gpg.Context() c.set_armor(True) diff --git a/lang/qt/Makefile.in b/lang/qt/Makefile.in index 5c32620..e054e25 100644 --- a/lang/qt/Makefile.in +++ b/lang/qt/Makefile.in @@ -110,8 +110,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs diff --git a/lang/qt/doc/Makefile.in b/lang/qt/doc/Makefile.in index c4aff43..7e86941 100644 --- a/lang/qt/doc/Makefile.in +++ b/lang/qt/doc/Makefile.in @@ -108,8 +108,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs diff --git a/lang/qt/src/Makefile.am b/lang/qt/src/Makefile.am index 87e2ec2..c81461e 100644 --- a/lang/qt/src/Makefile.am +++ b/lang/qt/src/Makefile.am @@ -37,7 +37,7 @@ qgpgme_sources = \ qgpgmekeyformailboxjob.cpp gpgme_backend_debug.cpp \ qgpgmetofupolicyjob.cpp \ defaultkeygenerationjob.cpp qgpgmewkspublishjob.cpp \ - dn.cpp + dn.cpp cryptoconfig.cpp # If you add one here make sure that you also add one in camelcase qgpgme_headers= \ @@ -220,6 +220,12 @@ libqgpgme_la_LIBADD = ../../cpp/src/libgpgmepp.la ../../../src/libgpgme.la \ libqgpgme_la_LDFLAGS = -no-undefined -version-info \ @LIBQGPGME_LT_CURRENT@:@LIBQGPGME_LT_REVISION@:@LIBQGPGME_LT_AGE@ +if HAVE_MACOS_SYSTEM +libsuffix=.dylib +else +libsuffix=.so +endif + if HAVE_W32_SYSTEM QGpgmeConfig.cmake: QGpgmeConfig-w32.cmake.in sed -e 's|[@]resolved_bindir@|$(bindir)|g' < "$<" | \ @@ -228,6 +234,7 @@ QGpgmeConfig.cmake: QGpgmeConfig-w32.cmake.in else QGpgmeConfig.cmake: QGpgmeConfig.cmake.in sed -e 's|[@]resolved_libdir@|$(libdir)|g' < "$<" | \ + sed -e 's|[@]libsuffix@|$(libsuffix)|g' | \ sed -e 's|[@]resolved_includedir@|$(includedir)|g' > $@ endif diff --git a/lang/qt/src/Makefile.in b/lang/qt/src/Makefile.in index 5b87f6e..635aaaa 100644 --- a/lang/qt/src/Makefile.in +++ b/lang/qt/src/Makefile.in @@ -97,8 +97,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs @@ -154,7 +154,8 @@ am__objects_1 = dataprovider.lo job.lo multideletejob.lo \ qgpgmeverifydetachedjob.lo qgpgmeverifyopaquejob.lo \ threadedjobmixin.lo qgpgmekeyformailboxjob.lo \ gpgme_backend_debug.lo qgpgmetofupolicyjob.lo \ - defaultkeygenerationjob.lo qgpgmewkspublishjob.lo dn.lo + defaultkeygenerationjob.lo qgpgmewkspublishjob.lo dn.lo \ + cryptoconfig.lo am__objects_2 = am_libqgpgme_la_OBJECTS = $(am__objects_1) $(am__objects_2) \ $(am__objects_2) @@ -484,7 +485,7 @@ qgpgme_sources = \ qgpgmekeyformailboxjob.cpp gpgme_backend_debug.cpp \ qgpgmetofupolicyjob.cpp \ defaultkeygenerationjob.cpp qgpgmewkspublishjob.cpp \ - dn.cpp + dn.cpp cryptoconfig.cpp # If you add one here make sure that you also add one in camelcase @@ -667,6 +668,8 @@ libqgpgme_la_LIBADD = ../../cpp/src/libgpgmepp.la ../../../src/libgpgme.la \ libqgpgme_la_LDFLAGS = -no-undefined -version-info \ @LIBQGPGME_LT_CURRENT@:@LIBQGPGME_LT_REVISION@:@LIBQGPGME_LT_AGE@ +@HAVE_MACOS_SYSTEM_FALSE@libsuffix = .so +@HAVE_MACOS_SYSTEM_TRUE@libsuffix = .dylib BUILT_SOURCES = $(qgpgme_moc_sources) $(camelcase_headers) CLEANFILES = $(qgpgme_moc_sources) $(camelcase_headers) QGpgmeConfig.cmake \ qgpgme_version.h QGpgmeConfig.cmake.in \ @@ -761,6 +764,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cryptoconfig.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dataprovider.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/defaultkeygenerationjob.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dn.Plo@am__quote@ @@ -1113,6 +1117,7 @@ uninstall-am: uninstall-camelcaseincludeHEADERS \ @HAVE_W32_SYSTEM_TRUE@ sed -e 's|[@]resolved_includedir@|$(includedir)|g' > $@ @HAVE_W32_SYSTEM_FALSE@QGpgmeConfig.cmake: QGpgmeConfig.cmake.in @HAVE_W32_SYSTEM_FALSE@ sed -e 's|[@]resolved_libdir@|$(libdir)|g' < "$<" | \ +@HAVE_W32_SYSTEM_FALSE@ sed -e 's|[@]libsuffix@|$(libsuffix)|g' | \ @HAVE_W32_SYSTEM_FALSE@ sed -e 's|[@]resolved_includedir@|$(includedir)|g' > $@ $(camelcase_headers): Makefile.am diff --git a/lang/qt/src/QGpgmeConfig.cmake.in.in b/lang/qt/src/QGpgmeConfig.cmake.in.in index 88ed242..a17a19f 100644 --- a/lang/qt/src/QGpgmeConfig.cmake.in.in +++ b/lang/qt/src/QGpgmeConfig.cmake.in.in @@ -64,7 +64,7 @@ add_library(QGpgme SHARED IMPORTED) set_target_properties(QGpgme PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "@resolved_includedir@/qgpgme;@resolved_includedir@" INTERFACE_LINK_LIBRARIES "Gpgmepp;Qt5::Core" - IMPORTED_LOCATION "@resolved_libdir@/libqgpgme.so" + IMPORTED_LOCATION "@resolved_libdir@/libqgpgme@libsuffix@" ) if(CMAKE_VERSION VERSION_LESS 2.8.12) diff --git a/lang/qt/src/cryptoconfig.cpp b/lang/qt/src/cryptoconfig.cpp new file mode 100644 index 0000000..be265d8 --- /dev/null +++ b/lang/qt/src/cryptoconfig.cpp @@ -0,0 +1,44 @@ +/* + cryptoconfig.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2017 Intevation GmbH + + 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. +*/ +#include "cryptoconfig.h" +#include "qgpgmenewcryptoconfig.h" + +using namespace QGpgME; + +QStringList CryptoConfigEntry::stringValueList() const +{ + const QGpgMENewCryptoConfigEntry *entry = dynamic_cast <const QGpgMENewCryptoConfigEntry*> (this); + if (!entry) { + return QStringList(); + } + return entry->stringValueList(); +} diff --git a/lang/qt/src/cryptoconfig.h b/lang/qt/src/cryptoconfig.h index c3f0c7e..c4de22d 100644 --- a/lang/qt/src/cryptoconfig.h +++ b/lang/qt/src/cryptoconfig.h @@ -248,6 +248,15 @@ public: * @return true if the value was changed */ virtual bool isDirty() const = 0; + + // Design change from here on we are closely bound to one implementation + // of cryptoconfig. To avoid ABI breaks with every new function we + // add real functions from now on. + + /** + * @return a stringValueList. + */ + QStringList stringValueList() const; }; /** @@ -379,9 +388,8 @@ public: /** * Write back changes * - * @param runtime If this option is set, the changes will take effect at run-time, as - * far as this is possible. Otherwise, they will take effect at the next - * start of the respective backend programs. + * @param runtime this parameter is ignored. Changes will always + * be made with --runtime set. */ virtual void sync(bool runtime) = 0; diff --git a/lang/qt/src/defaultkeygenerationjob.cpp b/lang/qt/src/defaultkeygenerationjob.cpp index 020f4d2..f589384 100644 --- a/lang/qt/src/defaultkeygenerationjob.cpp +++ b/lang/qt/src/defaultkeygenerationjob.cpp @@ -91,6 +91,11 @@ void DefaultKeyGenerationJob::slotCancel() GpgME::Error DefaultKeyGenerationJob::start(const QString &email, const QString &name) { + const QString namePart = name.isEmpty() ? QString() : + QStringLiteral("name-real: %1\n").arg(name); + const QString mailPart = email.isEmpty() ? QString() : + QStringLiteral("name-email: %1\n").arg(email); + const QString args = QStringLiteral("<GnupgKeyParms format=\"internal\">\n" "%ask-passphrase\n" "key-type: RSA\n" @@ -99,9 +104,9 @@ GpgME::Error DefaultKeyGenerationJob::start(const QString &email, const QString "subkey-type: RSA\n" "subkey-length: 2048\n" "subkey-usage: encrypt\n" - "name-email: %1\n" - "name-real: %2\n" - "</GnupgKeyParms>").arg(email, name); + "%1" + "%2" + "</GnupgKeyParms>").arg(mailPart, namePart); d->job = openpgp()->keyGenerationJob(); d->job->installEventFilter(this); diff --git a/lang/qt/src/dn.cpp b/lang/qt/src/dn.cpp index 0f81a4c..f9fb2f6 100644 --- a/lang/qt/src/dn.cpp +++ b/lang/qt/src/dn.cpp @@ -37,6 +37,8 @@ #include "dn.h" +#include <gpg-error.h> + static const struct { const char *name; const char *oid; @@ -165,7 +167,7 @@ parse_dn_part(DnPair *array, const unsigned char *string) for (unsigned int i = 0; i < numOidMaps; ++i) if (!strcasecmp((char *)p, oidmap[i].oid)) { free(p); - p = strdup(oidmap[i].name); + gpgrt_asprintf(&p, oidmap[i].name); break; } array->key = p; diff --git a/lang/qt/src/qgpgmenewcryptoconfig.cpp b/lang/qt/src/qgpgmenewcryptoconfig.cpp index eb3af56..6901eef 100644 --- a/lang/qt/src/qgpgmenewcryptoconfig.cpp +++ b/lang/qt/src/qgpgmenewcryptoconfig.cpp @@ -49,6 +49,7 @@ #include <sstream> #include <string> #include <cassert> +#include <functional> using namespace QGpgME; using namespace GpgME; @@ -216,17 +217,12 @@ QGpgMENewCryptoConfigGroup *QGpgMENewCryptoConfigComponent::group(const QString void QGpgMENewCryptoConfigComponent::sync(bool runtime) { - Q_UNUSED(runtime) - // ### how to pass --runtime to gpgconf? -> marcus: not yet supported (2010-11-20) + Q_UNUSED(runtime) // runtime is always set by engine_gpgconf if (const Error err = m_component.save()) { -#if 0 - TODO port - const QString wmsg = i18n("Error from gpgconf while saving configuration: %1", QString::fromLocal8Bit(err.asString())); - qCWarning(GPGPME_BACKEND_LOG) << ":" << wmsg; - KMessageBox::error(0, wmsg); -#endif + qCWarning(GPGPME_BACKEND_LOG) << ":" + << "Error from gpgconf while saving configuration: %1" + << QString::fromLocal8Bit(err.asString()); } - // ### unset dirty state again } //// @@ -551,6 +547,18 @@ std::vector<unsigned int> QGpgMENewCryptoConfigEntry::uintValueList() const return m_option.currentValue().uintValues(); } +QStringList QGpgMENewCryptoConfigEntry::stringValueList() const +{ + Q_ASSERT(isList()); + const Argument arg = m_option.currentValue(); + const std::vector<const char *> values = arg.stringValues(); + QStringList ret; + for(const char *value: values) { + ret << QString::fromUtf8(value); + } + return ret; +} + QList<QUrl> QGpgMENewCryptoConfigEntry::urlValueList() const { const Type type = m_option.type(); diff --git a/lang/qt/src/qgpgmenewcryptoconfig.h b/lang/qt/src/qgpgmenewcryptoconfig.h index 81b4cb4..7100e70 100644 --- a/lang/qt/src/qgpgmenewcryptoconfig.h +++ b/lang/qt/src/qgpgmenewcryptoconfig.h @@ -93,6 +93,8 @@ public: void setURLValueList(const QList<QUrl> &) Q_DECL_OVERRIDE; bool isDirty() const Q_DECL_OVERRIDE; + QStringList stringValueList() const; + #if 0 void setDirty(bool b); QString outputString() const; diff --git a/lang/qt/src/threadedjobmixin.h b/lang/qt/src/threadedjobmixin.h index 32b23db..5ad2737 100644 --- a/lang/qt/src/threadedjobmixin.h +++ b/lang/qt/src/threadedjobmixin.h @@ -51,6 +51,7 @@ #include "job.h" #include <cassert> +#include <functional> namespace QGpgME { diff --git a/lang/qt/tests/Makefile.am b/lang/qt/tests/Makefile.am index ad08ad4..93dce07 100644 --- a/lang/qt/tests/Makefile.am +++ b/lang/qt/tests/Makefile.am @@ -25,10 +25,11 @@ TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) EXTRA_DIST = initial.test TESTS = initial.test t-keylist t-keylocate t-ownertrust t-tofuinfo \ - t-encrypt t-verify + t-encrypt t-verify t-various t-config 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-encrypt.moc t-support.hmoc t-wkspublish.moc t-verify.moc \ + t-various.moc t-config.moc AM_LDFLAGS = -no-install @@ -57,6 +58,8 @@ t_tofuinfo_SOURCES = t-tofuinfo.cpp $(support_src) t_encrypt_SOURCES = t-encrypt.cpp $(support_src) t_wkspublish_SOURCES = t-wkspublish.cpp $(support_src) t_verify_SOURCES = t-verify.cpp $(support_src) +t_various_SOURCES = t-various.cpp $(support_src) +t_config_SOURCES = t-config.cpp $(support_src) run_keyformailboxjob_SOURCES = run-keyformailboxjob.cpp nodist_t_keylist_SOURCES = $(moc_files) @@ -64,12 +67,12 @@ nodist_t_keylist_SOURCES = $(moc_files) BUILT_SOURCES = $(moc_files) noinst_PROGRAMS = t-keylist t-keylocate t-ownertrust t-tofuinfo t-encrypt \ - run-keyformailboxjob t-wkspublish t-verify + run-keyformailboxjob t-wkspublish t-verify t-various t-config CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \ gpg-agent.conf pubring.kbx~ S.gpg-agent gpg.conf pubring.gpg~ \ random_seed S.gpg-agent .gpg-v21-migrated pubring-stamp $(moc_files) \ - gpg.conf + gpg.conf tofu.db clean-local: -rm -fR private-keys-v1.d crls.d diff --git a/lang/qt/tests/Makefile.in b/lang/qt/tests/Makefile.in index e0ac22e..f370058 100644 --- a/lang/qt/tests/Makefile.in +++ b/lang/qt/tests/Makefile.in @@ -98,11 +98,11 @@ build_triplet = @build@ host_triplet = @host@ TESTS = initial.test t-keylist$(EXEEXT) t-keylocate$(EXEEXT) \ t-ownertrust$(EXEEXT) t-tofuinfo$(EXEEXT) t-encrypt$(EXEEXT) \ - t-verify$(EXEEXT) + t-verify$(EXEEXT) t-various$(EXEEXT) t-config$(EXEEXT) noinst_PROGRAMS = t-keylist$(EXEEXT) t-keylocate$(EXEEXT) \ t-ownertrust$(EXEEXT) t-tofuinfo$(EXEEXT) t-encrypt$(EXEEXT) \ run-keyformailboxjob$(EXEEXT) t-wkspublish$(EXEEXT) \ - t-verify$(EXEEXT) + t-verify$(EXEEXT) t-various$(EXEEXT) t-config$(EXEEXT) subdir = lang/qt/tests DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ $(top_srcdir)/build-aux/mkinstalldirs \ @@ -117,8 +117,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ - $(top_srcdir)/m4/qt.m4 $(top_srcdir)/acinclude.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs @@ -136,6 +136,11 @@ am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am__objects_1 = t-support.$(OBJEXT) +am_t_config_OBJECTS = t-config.$(OBJEXT) $(am__objects_1) +t_config_OBJECTS = $(am_t_config_OBJECTS) +t_config_LDADD = $(LDADD) +t_config_DEPENDENCIES = ../../cpp/src/libgpgmepp.la \ + ../src/libqgpgme.la ../../../src/libgpgme.la am_t_encrypt_OBJECTS = t-encrypt.$(OBJEXT) $(am__objects_1) t_encrypt_OBJECTS = $(am_t_encrypt_OBJECTS) t_encrypt_LDADD = $(LDADD) @@ -164,6 +169,11 @@ t_tofuinfo_OBJECTS = $(am_t_tofuinfo_OBJECTS) t_tofuinfo_LDADD = $(LDADD) t_tofuinfo_DEPENDENCIES = ../../cpp/src/libgpgmepp.la \ ../src/libqgpgme.la ../../../src/libgpgme.la +am_t_various_OBJECTS = t-various.$(OBJEXT) $(am__objects_1) +t_various_OBJECTS = $(am_t_various_OBJECTS) +t_various_LDADD = $(LDADD) +t_various_DEPENDENCIES = ../../cpp/src/libgpgmepp.la \ + ../src/libqgpgme.la ../../../src/libgpgme.la am_t_verify_OBJECTS = t-verify.$(OBJEXT) $(am__objects_1) t_verify_OBJECTS = $(am_t_verify_OBJECTS) t_verify_LDADD = $(LDADD) @@ -226,15 +236,17 @@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = -SOURCES = $(run_keyformailboxjob_SOURCES) $(t_encrypt_SOURCES) \ - $(t_keylist_SOURCES) $(nodist_t_keylist_SOURCES) \ +SOURCES = $(run_keyformailboxjob_SOURCES) $(t_config_SOURCES) \ + $(t_encrypt_SOURCES) $(t_keylist_SOURCES) \ + $(nodist_t_keylist_SOURCES) $(t_keylocate_SOURCES) \ + $(t_ownertrust_SOURCES) $(t_tofuinfo_SOURCES) \ + $(t_various_SOURCES) $(t_verify_SOURCES) \ + $(t_wkspublish_SOURCES) +DIST_SOURCES = $(run_keyformailboxjob_SOURCES) $(t_config_SOURCES) \ + $(t_encrypt_SOURCES) $(t_keylist_SOURCES) \ $(t_keylocate_SOURCES) $(t_ownertrust_SOURCES) \ - $(t_tofuinfo_SOURCES) $(t_verify_SOURCES) \ + $(t_tofuinfo_SOURCES) $(t_various_SOURCES) $(t_verify_SOURCES) \ $(t_wkspublish_SOURCES) -DIST_SOURCES = $(run_keyformailboxjob_SOURCES) $(t_encrypt_SOURCES) \ - $(t_keylist_SOURCES) $(t_keylocate_SOURCES) \ - $(t_ownertrust_SOURCES) $(t_tofuinfo_SOURCES) \ - $(t_verify_SOURCES) $(t_wkspublish_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -481,7 +493,8 @@ GPG = gpg TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) EXTRA_DIST = initial.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-encrypt.moc t-support.hmoc t-wkspublish.moc t-verify.moc \ + t-various.moc t-config.moc AM_LDFLAGS = -no-install LDADD = ../../cpp/src/libgpgmepp.la ../src/libqgpgme.la \ @@ -502,13 +515,15 @@ t_tofuinfo_SOURCES = t-tofuinfo.cpp $(support_src) t_encrypt_SOURCES = t-encrypt.cpp $(support_src) t_wkspublish_SOURCES = t-wkspublish.cpp $(support_src) t_verify_SOURCES = t-verify.cpp $(support_src) +t_various_SOURCES = t-various.cpp $(support_src) +t_config_SOURCES = t-config.cpp $(support_src) run_keyformailboxjob_SOURCES = run-keyformailboxjob.cpp nodist_t_keylist_SOURCES = $(moc_files) BUILT_SOURCES = $(moc_files) CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \ gpg-agent.conf pubring.kbx~ S.gpg-agent gpg.conf pubring.gpg~ \ random_seed S.gpg-agent .gpg-v21-migrated pubring-stamp $(moc_files) \ - gpg.conf + gpg.conf tofu.db all: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) all-am @@ -559,6 +574,10 @@ run-keyformailboxjob$(EXEEXT): $(run_keyformailboxjob_OBJECTS) $(run_keyformailb @rm -f run-keyformailboxjob$(EXEEXT) $(AM_V_CXXLD)$(CXXLINK) $(run_keyformailboxjob_OBJECTS) $(run_keyformailboxjob_LDADD) $(LIBS) +t-config$(EXEEXT): $(t_config_OBJECTS) $(t_config_DEPENDENCIES) $(EXTRA_t_config_DEPENDENCIES) + @rm -f t-config$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(t_config_OBJECTS) $(t_config_LDADD) $(LIBS) + t-encrypt$(EXEEXT): $(t_encrypt_OBJECTS) $(t_encrypt_DEPENDENCIES) $(EXTRA_t_encrypt_DEPENDENCIES) @rm -f t-encrypt$(EXEEXT) $(AM_V_CXXLD)$(CXXLINK) $(t_encrypt_OBJECTS) $(t_encrypt_LDADD) $(LIBS) @@ -579,6 +598,10 @@ t-tofuinfo$(EXEEXT): $(t_tofuinfo_OBJECTS) $(t_tofuinfo_DEPENDENCIES) $(EXTRA_t_ @rm -f t-tofuinfo$(EXEEXT) $(AM_V_CXXLD)$(CXXLINK) $(t_tofuinfo_OBJECTS) $(t_tofuinfo_LDADD) $(LIBS) +t-various$(EXEEXT): $(t_various_OBJECTS) $(t_various_DEPENDENCIES) $(EXTRA_t_various_DEPENDENCIES) + @rm -f t-various$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(t_various_OBJECTS) $(t_various_LDADD) $(LIBS) + t-verify$(EXEEXT): $(t_verify_OBJECTS) $(t_verify_DEPENDENCIES) $(EXTRA_t_verify_DEPENDENCIES) @rm -f t-verify$(EXEEXT) $(AM_V_CXXLD)$(CXXLINK) $(t_verify_OBJECTS) $(t_verify_LDADD) $(LIBS) @@ -594,12 +617,14 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run-keyformailboxjob.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-config.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-encrypt.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-keylist.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-keylocate.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-ownertrust.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-support.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-tofuinfo.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-various.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-verify.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-wkspublish.Po@am__quote@ diff --git a/lang/qt/tests/t-config.cpp b/lang/qt/tests/t-config.cpp new file mode 100644 index 0000000..0a7df22 --- /dev/null +++ b/lang/qt/tests/t-config.cpp @@ -0,0 +1,94 @@ +/* t-config.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2016 Intevation GmbH + + 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 <QDebug> +#include <QTest> +#include <QTemporaryDir> +#include "t-support.h" +#include "protocol.h" +#include "cryptoconfig.h" +#include <unistd.h> + +using namespace QGpgME; + +class CryptoConfigTest: public QGpgMETest +{ + Q_OBJECT + +private Q_SLOTS: + void testKeyserver() + { + // Repeatedly set a config value and clear it + // this war broken at some point so it gets a + // unit test. + for (int i = 0; i < 10; i++) { + auto conf = cryptoConfig(); + QVERIFY(conf); + auto entry = conf->entry(QStringLiteral("gpg"), + QStringLiteral("Keyserver"), + QStringLiteral("keyserver")); + QVERIFY(entry); + const QString url(QStringLiteral("hkp://foo.bar.baz")); + entry->setStringValue(url); + conf->sync(false); + conf->clear(); + entry = conf->entry(QStringLiteral("gpg"), + QStringLiteral("Keyserver"), + QStringLiteral("keyserver")); + QCOMPARE (entry->stringValue(), url); + entry->setStringValue(QString()); + conf->sync(false); + conf->clear(); + entry = conf->entry(QStringLiteral("gpg"), + QStringLiteral("Keyserver"), + QStringLiteral("keyserver")); + QCOMPARE (entry->stringValue(), QString()); + } + } + + void initTestCase() + { + QGpgMETest::initTestCase(); + const QString gpgHome = qgetenv("GNUPGHOME"); + qputenv("GNUPGHOME", mDir.path().toUtf8()); + QVERIFY(mDir.isValid()); + } +private: + QTemporaryDir mDir; + +}; + +QTEST_MAIN(CryptoConfigTest) + +#include "t-config.moc" diff --git a/lang/qt/tests/t-encrypt.cpp b/lang/qt/tests/t-encrypt.cpp index 4d65dc7..a2d8dc4 100644 --- a/lang/qt/tests/t-encrypt.cpp +++ b/lang/qt/tests/t-encrypt.cpp @@ -39,6 +39,8 @@ #include <QBuffer> #include "keylistjob.h" #include "encryptjob.h" +#include "signencryptjob.h" +#include "signingresult.h" #include "qgpgmeencryptjob.h" #include "encryptionresult.h" #include "decryptionresult.h" @@ -46,6 +48,7 @@ #include "qgpgmebackend.h" #include "keylistresult.h" #include "engineinfo.h" +#include "verifyopaquejob.h" #include "t-support.h" #define PROGRESS_TEST_SIZE 1 * 1024 * 1024 @@ -85,18 +88,18 @@ private Q_SLOTS: std::vector<Key> keys; auto keylistresult = listjob->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); - Q_ASSERT(!keylistresult.error()); - Q_ASSERT(keys.size() == 1); + QVERIFY(!keylistresult.error()); + QVERIFY(keys.size() == 1); delete listjob; auto job = openpgp()->encryptJob(/*ASCII Armor */true, /* Textmode */ true); - Q_ASSERT(job); + QVERIFY(job); QByteArray cipherText; auto result = job->exec(keys, QStringLiteral("Hello World").toUtf8(), Context::AlwaysTrust, cipherText); delete job; - Q_ASSERT(!result.error()); + QVERIFY(!result.error()); const auto cipherString = QString::fromUtf8(cipherText); - Q_ASSERT(cipherString.startsWith("-----BEGIN PGP MESSAGE-----")); + QVERIFY(cipherString.startsWith("-----BEGIN PGP MESSAGE-----")); /* Now decrypt */ if (!decryptSupported()) { @@ -109,8 +112,8 @@ private Q_SLOTS: auto decJob = new QGpgMEDecryptJob(ctx); QByteArray plainText; auto decResult = decJob->exec(cipherText, plainText); - Q_ASSERT(!result.error()); - Q_ASSERT(QString::fromUtf8(plainText) == QStringLiteral("Hello World")); + QVERIFY(!decResult.error()); + QVERIFY(QString::fromUtf8(plainText) == QStringLiteral("Hello World")); delete decJob; } @@ -125,12 +128,12 @@ private Q_SLOTS: std::vector<Key> keys; auto keylistresult = listjob->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); - Q_ASSERT(!keylistresult.error()); - Q_ASSERT(keys.size() == 1); + QVERIFY(!keylistresult.error()); + QVERIFY(keys.size() == 1); delete listjob; auto job = openpgp()->encryptJob(/*ASCII Armor */false, /* Textmode */ false); - Q_ASSERT(job); + QVERIFY(job); QByteArray plainBa; plainBa.fill('X', PROGRESS_TEST_SIZE); QByteArray cipherText; @@ -140,21 +143,21 @@ private Q_SLOTS: connect(job, &Job::progress, this, [this, &initSeen, &finishSeen] (const QString&, int current, int total) { // We only check for progress 0 and max progress as the other progress // lines depend on the system speed and are as such unreliable to test. - Q_ASSERT(total == PROGRESS_TEST_SIZE); + QVERIFY(total == PROGRESS_TEST_SIZE); if (current == 0) { initSeen = true; } if (current == total) { finishSeen = true; } - Q_ASSERT(current >= 0 && current <= total); + QVERIFY(current >= 0 && current <= total); }); connect(job, &EncryptJob::result, this, [this, &initSeen, &finishSeen] (const GpgME::EncryptionResult &, const QByteArray &, const QString, const GpgME::Error) { - Q_ASSERT(initSeen); - Q_ASSERT(finishSeen); + QVERIFY(initSeen); + QVERIFY(finishSeen); Q_EMIT asyncDone(); }); @@ -165,7 +168,7 @@ private Q_SLOTS: job->start(keys, inptr, outptr, Context::AlwaysTrust); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } void testSymmetricEncryptDecrypt() @@ -183,9 +186,9 @@ private Q_SLOTS: QByteArray cipherText; auto result = job->exec(std::vector<Key>(), QStringLiteral("Hello symmetric World").toUtf8(), Context::AlwaysTrust, cipherText); delete job; - Q_ASSERT(!result.error()); + QVERIFY(!result.error()); const auto cipherString = QString::fromUtf8(cipherText); - Q_ASSERT(cipherString.startsWith("-----BEGIN PGP MESSAGE-----")); + QVERIFY(cipherString.startsWith("-----BEGIN PGP MESSAGE-----")); killAgent(mDir.path()); @@ -195,12 +198,76 @@ private Q_SLOTS: auto decJob = new QGpgMEDecryptJob(ctx2); QByteArray plainText; auto decResult = decJob->exec(cipherText, plainText); - Q_ASSERT(!result.error()); - Q_ASSERT(QString::fromUtf8(plainText) == QStringLiteral("Hello symmetric World")); + QVERIFY(!result.error()); + QVERIFY(QString::fromUtf8(plainText) == QStringLiteral("Hello symmetric World")); delete decJob; } private: + /* This apparently does not work under ASAN currently. TODO fix and reeanble */ + void testEncryptDecryptNowrap() + { + /* Now decrypt */ + if (!decryptSupported()) { + return; + } + auto listjob = openpgp()->keyListJob(false, false, false); + std::vector<Key> keys; + auto keylistresult = listjob->exec(QStringList() << QStringLiteral("alfa@example.net"), + false, keys); + QVERIFY(!keylistresult.error()); + QVERIFY(keys.size() == 1); + delete listjob; + + auto job = openpgp()->signEncryptJob(/*ASCII Armor */true, /* Textmode */ true); + + auto encSignCtx = Job::context(job); + TestPassphraseProvider provider1; + encSignCtx->setPassphraseProvider(&provider1); + encSignCtx->setPinentryMode(Context::PinentryLoopback); + + QVERIFY(job); + QByteArray cipherText; + auto result = job->exec(keys, keys, QStringLiteral("Hello World").toUtf8(), Context::AlwaysTrust, cipherText); + delete job; + QVERIFY(!result.first.error()); + QVERIFY(!result.second.error()); + const auto cipherString = QString::fromUtf8(cipherText); + QVERIFY(cipherString.startsWith("-----BEGIN PGP MESSAGE-----")); + + /* Now decrypt */ + if (!decryptSupported()) { + return; + } + auto ctx = Context::createForProtocol(OpenPGP); + TestPassphraseProvider provider; + ctx->setPassphraseProvider(&provider); + ctx->setPinentryMode(Context::PinentryLoopback); + ctx->setDecryptionFlags(Context::DecryptUnwrap); + + auto decJob = new QGpgMEDecryptJob(ctx); + QByteArray plainText; + auto decResult = decJob->exec(cipherText, plainText); + + QVERIFY(!decResult.error()); + + delete decJob; + + // Now verify the unwrapeped data. + auto verifyJob = openpgp()->verifyOpaqueJob(true); + QByteArray verified; + + auto verResult = verifyJob->exec(plainText, verified); + QVERIFY(!verResult.error()); + delete verifyJob; + + QVERIFY(verResult.numSignatures() == 1); + auto sig = verResult.signatures()[0]; + + QVERIFY(verified == QStringLiteral("Hello World")); + } + +private: /* Loopback and passphrase provider don't work for mixed encryption. * So this test is disabled until gnupg(?) is fixed for this. */ void testMixedEncryptDecrypt() @@ -212,8 +279,8 @@ private: std::vector<Key> keys; auto keylistresult = listjob->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); - Q_ASSERT(!keylistresult.error()); - Q_ASSERT(keys.size() == 1); + QVERIFY(!keylistresult.error()); + QVERIFY(keys.size() == 1); delete listjob; auto ctx = Context::createForProtocol(OpenPGP); @@ -229,10 +296,10 @@ private: cipherText); printf("After exec\n"); delete job; - Q_ASSERT(!result.error()); + QVERIFY(!result.error()); printf("Cipher:\n%s\n", cipherText.constData()); const auto cipherString = QString::fromUtf8(cipherText); - Q_ASSERT(cipherString.startsWith("-----BEGIN PGP MESSAGE-----")); + QVERIFY(cipherString.startsWith("-----BEGIN PGP MESSAGE-----")); killAgent(mDir.path()); @@ -240,7 +307,7 @@ private: QTemporaryDir tmp; qputenv("GNUPGHOME", tmp.path().toUtf8()); QFile agentConf(tmp.path() + QStringLiteral("/gpg-agent.conf")); - Q_ASSERT(agentConf.open(QIODevice::WriteOnly)); + QVERIFY(agentConf.open(QIODevice::WriteOnly)); agentConf.write("allow-loopback-pinentry"); agentConf.close(); @@ -251,9 +318,9 @@ private: auto decJob = new QGpgMEDecryptJob(ctx2); QByteArray plainText; auto decResult = decJob->exec(cipherText, plainText); - Q_ASSERT(!decResult.error()); + QVERIFY(!decResult.error()); qDebug() << "Plain: " << plainText; - Q_ASSERT(QString::fromUtf8(plainText) == QStringLiteral("Hello symmetric World")); + QVERIFY(QString::fromUtf8(plainText) == QStringLiteral("Hello symmetric World")); delete decJob; killAgent(tmp.path()); @@ -267,12 +334,12 @@ public Q_SLOT: QGpgMETest::initTestCase(); const QString gpgHome = qgetenv("GNUPGHOME"); qputenv("GNUPGHOME", mDir.path().toUtf8()); - Q_ASSERT(mDir.isValid()); + QVERIFY(mDir.isValid()); QFile agentConf(mDir.path() + QStringLiteral("/gpg-agent.conf")); - Q_ASSERT(agentConf.open(QIODevice::WriteOnly)); + QVERIFY(agentConf.open(QIODevice::WriteOnly)); agentConf.write("allow-loopback-pinentry"); agentConf.close(); - Q_ASSERT(copyKeyrings(gpgHome, mDir.path())); + QVERIFY(copyKeyrings(gpgHome, mDir.path())); } private: diff --git a/lang/qt/tests/t-keylist.cpp b/lang/qt/tests/t-keylist.cpp index 2578576..a140236 100644 --- a/lang/qt/tests/t-keylist.cpp +++ b/lang/qt/tests/t-keylist.cpp @@ -61,14 +61,14 @@ private Q_SLOTS: GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); delete job; - Q_ASSERT (!result.error()); - Q_ASSERT (keys.size() == 1); + QVERIFY (!result.error()); + QVERIFY (keys.size() == 1); const QString kId = QLatin1String(keys.front().keyID()); - Q_ASSERT (kId == QStringLiteral("2D727CC768697734")); + QVERIFY (kId == QStringLiteral("2D727CC768697734")); - Q_ASSERT (keys[0].subkeys().size() == 2); - Q_ASSERT (keys[0].subkeys()[0].publicKeyAlgorithm() == Subkey::AlgoDSA); - Q_ASSERT (keys[0].subkeys()[1].publicKeyAlgorithm() == Subkey::AlgoELG_E); + QVERIFY (keys[0].subkeys().size() == 2); + QVERIFY (keys[0].subkeys()[0].publicKeyAlgorithm() == Subkey::AlgoDSA); + QVERIFY (keys[0].subkeys()[1].publicKeyAlgorithm() == Subkey::AlgoELG_E); } void testPubkeyAlgoAsString() @@ -87,7 +87,7 @@ private Q_SLOTS: { Subkey::AlgoUnknown, QString() } }; Q_FOREACH (Subkey::PubkeyAlgo algo, expected.keys()) { - Q_ASSERT(QString::fromUtf8(Subkey::publicKeyAlgorithmAsString(algo)) == + QVERIFY(QString::fromUtf8(Subkey::publicKeyAlgorithmAsString(algo)) == expected.value(algo)); } } @@ -97,12 +97,12 @@ private Q_SLOTS: KeyListJob *job = openpgp()->keyListJob(); connect(job, &KeyListJob::result, job, [this, job](KeyListResult, std::vector<Key> keys, QString, Error) { - Q_ASSERT(keys.size() == 1); + QVERIFY(keys.size() == 1); Q_EMIT asyncDone(); }); job->start(QStringList() << "alfa@example.net"); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } }; diff --git a/lang/qt/tests/t-keylocate.cpp b/lang/qt/tests/t-keylocate.cpp index 63cb836..8c99c8b 100644 --- a/lang/qt/tests/t-keylocate.cpp +++ b/lang/qt/tests/t-keylocate.cpp @@ -63,7 +63,7 @@ private Q_SLOTS: qputenv("GNUPGHOME", dir.path().toUtf8()); /* Could do this with gpgconf but this is not a gpgconf test ;-) */ QFile conf(dir.path() + QStringLiteral("/gpg.conf")); - Q_ASSERT(conf.open(QIODevice::WriteOnly)); + QVERIFY(conf.open(QIODevice::WriteOnly)); conf.write("auto-key-locate dane"); conf.close(); @@ -71,11 +71,11 @@ private Q_SLOTS: mTestpattern = QStringLiteral("wk@gnupg.org"); connect(job, &KeyListJob::result, job, [this, job](KeyListResult result, std::vector<Key> keys, QString, Error) { - Q_ASSERT(!result.error()); - Q_ASSERT(keys.size() == 1); + QVERIFY(!result.error()); + QVERIFY(keys.size() == 1); Key k = keys.front(); - Q_ASSERT(k.numUserIDs()); + QVERIFY(k.numUserIDs()); bool found = false; Q_FOREACH (const UserID uid, k.userIDs()) { const QString mailBox = QString::fromUtf8(uid.email()); @@ -83,12 +83,12 @@ private Q_SLOTS: found = true; } } - Q_ASSERT(found); + QVERIFY(found); Q_EMIT asyncDone(); }); job->start(QStringList() << mTestpattern); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); qputenv("GNUPGHOME", oldHome.toUtf8()); } #endif @@ -103,13 +103,13 @@ private Q_SLOTS: connect(job, &KeyListJob::result, job, [this, job](KeyListResult result, std::vector<Key> keys, QString, Error) { - Q_ASSERT(!result.isNull()); - Q_ASSERT(!result.isTruncated()); - Q_ASSERT(!result.error()); - Q_ASSERT(keys.size() == 1); + QVERIFY(!result.isNull()); + QVERIFY(!result.isTruncated()); + QVERIFY(!result.error()); + QVERIFY(keys.size() == 1); Key k = keys.front(); - Q_ASSERT(k.numUserIDs()); + QVERIFY(k.numUserIDs()); bool found = false; Q_FOREACH (const UserID uid, k.userIDs()) { const QString mailBox = QString::fromUtf8(uid.email()); @@ -117,12 +117,12 @@ private Q_SLOTS: found = true; } } - Q_ASSERT(found); + QVERIFY(found); Q_EMIT asyncDone(); }); job->start(QStringList() << mTestpattern); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } private: diff --git a/lang/qt/tests/t-ownertrust.cpp b/lang/qt/tests/t-ownertrust.cpp index db863b2..e9a4378 100644 --- a/lang/qt/tests/t-ownertrust.cpp +++ b/lang/qt/tests/t-ownertrust.cpp @@ -62,10 +62,10 @@ private Q_SLOTS: GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); delete job; - Q_ASSERT (!result.error()); - Q_ASSERT (keys.size() == 1); + QVERIFY (!result.error()); + QVERIFY (keys.size() == 1); Key key = keys.front(); - Q_ASSERT (key.ownerTrust() == Key::Unknown); + QVERIFY (key.ownerTrust() == Key::Unknown); ChangeOwnerTrustJob *job2 = openpgp()->changeOwnerTrustJob(); connect(job2, &ChangeOwnerTrustJob::result, this, [this](Error e) @@ -73,28 +73,28 @@ private Q_SLOTS: if (e) { qDebug() << "Error in result: " << e.asString(); } - Q_ASSERT(!e); + QVERIFY(!e); Q_EMIT asyncDone(); }); job2->start(key, Key::Ultimate); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); job = openpgp()->keyListJob(false, true, true); result = job->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); delete job; key = keys.front(); - Q_ASSERT (key.ownerTrust() == Key::Ultimate); + QVERIFY (key.ownerTrust() == Key::Ultimate); ChangeOwnerTrustJob *job3 = openpgp()->changeOwnerTrustJob(); connect(job3, &ChangeOwnerTrustJob::result, this, [this](Error e) { - Q_ASSERT(!e); + QVERIFY(!e); Q_EMIT asyncDone(); }); job3->start(key, Key::Unknown); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); job = openpgp()->keyListJob(false, true, true); result = job->exec(QStringList() << QStringLiteral("alfa@example.net"), @@ -102,7 +102,7 @@ private Q_SLOTS: delete job; key = keys.front(); - Q_ASSERT (key.ownerTrust() == Key::Unknown); + QVERIFY (key.ownerTrust() == Key::Unknown); } }; diff --git a/lang/qt/tests/t-support.cpp b/lang/qt/tests/t-support.cpp index 857d0a3..b3a7a70 100644 --- a/lang/qt/tests/t-support.cpp +++ b/lang/qt/tests/t-support.cpp @@ -34,6 +34,7 @@ #endif #include "t-support.h" +#include "context.h" #include <QTest> @@ -44,6 +45,7 @@ void QGpgMETest::initTestCase() { + GpgME::initializeLibrary(); const QString gpgHome = qgetenv("GNUPGHOME"); QVERIFY2(!gpgHome.isEmpty(), "GNUPGHOME environment variable is not set."); } diff --git a/lang/qt/tests/t-support.h b/lang/qt/tests/t-support.h index 704fab4..b03b05d 100644 --- a/lang/qt/tests/t-support.h +++ b/lang/qt/tests/t-support.h @@ -34,6 +34,8 @@ #include "interfaces/passphraseprovider.h" #include <QObject> +#include <gpg-error.h> + namespace GpgME { class TestPassphraseProvider : public PassphraseProvider @@ -42,7 +44,9 @@ public: char *getPassphrase(const char * /*useridHint*/, const char * /*description*/, bool /*previousWasBad*/, bool &/*canceled*/) Q_DECL_OVERRIDE { - return strdup("abc"); + char *ret; + gpgrt_asprintf(&ret, "abc"); + return ret; } }; } // namespace GpgME @@ -60,4 +64,8 @@ public Q_SLOTS: void cleanupTestCase(); }; +/* Timeout, in milliseconds, for use with QSignalSpy to wait on + signals. */ +#define QSIGNALSPY_TIMEOUT 60000 + #endif // T_SUPPORT_H diff --git a/lang/qt/tests/t-tofuinfo.cpp b/lang/qt/tests/t-tofuinfo.cpp index f89e1c2..e16b1fd 100644 --- a/lang/qt/tests/t-tofuinfo.cpp +++ b/lang/qt/tests/t-tofuinfo.cpp @@ -35,12 +35,16 @@ #include <QDebug> #include <QTest> #include <QTemporaryDir> +#include <QSignalSpy> + #include "protocol.h" #include "tofuinfo.h" #include "tofupolicyjob.h" #include "verifyopaquejob.h" #include "verificationresult.h" #include "signingresult.h" +#include "importjob.h" +#include "importresult.h" #include "keylistjob.h" #include "keylistresult.h" #include "qgpgmesignjob.h" @@ -61,10 +65,57 @@ static const char testMsg1[] = "=Crq6\n" "-----END PGP MESSAGE-----\n"; +static const char conflictKey1[] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +"\n" +"mDMEWG+w/hYJKwYBBAHaRw8BAQdAiq1oStvDYg8ZfFs5DgisYJo8dJxD+C/AA21O\n" +"K/aif0O0GXRvZnVfY29uZmxpY3RAZXhhbXBsZS5jb22IlgQTFggAPhYhBHoJBLaV\n" +"DamYAgoa1L5BwMOl/x88BQJYb7D+AhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMB\n" +"Ah4BAheAAAoJEL5BwMOl/x88GvwA/0SxkbLyAcshGm2PRrPsFQsSVAfwaSYFVmS2\n" +"cMVIw1PfAQDclRH1Z4MpufK07ju4qI33o4s0UFpVRBuSxt7A4P2ZD7g4BFhvsP4S\n" +"CisGAQQBl1UBBQEBB0AmVrgaDNJ7K2BSalsRo2EkRJjHGqnp5bBB0tapnF81CQMB\n" +"CAeIeAQYFggAIBYhBHoJBLaVDamYAgoa1L5BwMOl/x88BQJYb7D+AhsMAAoJEL5B\n" +"wMOl/x88OR0BAMq4/vmJUORRTmzjHcv/DDrQB030DSq666rlckGIKTShAPoDXM9N\n" +"0gZK+YzvrinSKZXHmn0aSwmC1/hyPybJPEljBw==\n" +"=p2Oj\n" +"-----END PGP PUBLIC KEY BLOCK-----\n"; + +static const char conflictKey2[] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +"\n" +"mDMEWG+xShYJKwYBBAHaRw8BAQdA567gPEPJRpqKnZjlFJMRNUqruRviYMyygfF6\n" +"6Ok+ygu0GXRvZnVfY29uZmxpY3RAZXhhbXBsZS5jb22IlgQTFggAPhYhBJ5kRh7E\n" +"I98w8kgUcmkAfYFvqqHsBQJYb7FKAhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMB\n" +"Ah4BAheAAAoJEGkAfYFvqqHsYR0BAOz8JjYB4VvGkt6noLS3F5TLfsedGwQkBCw5\n" +"znw/vGZsAQD9DSX+ekwdrN56mNO8ISt5uVS7B1ZQtouNBF+nzcwbDbg4BFhvsUoS\n" +"CisGAQQBl1UBBQEBB0BFupW8+Xc1ikab8TJqANjQhvFVh6uLsgcK4g9lZgbGXAMB\n" +"CAeIeAQYFggAIBYhBJ5kRh7EI98w8kgUcmkAfYFvqqHsBQJYb7FKAhsMAAoJEGkA\n" +"fYFvqqHs15ABALdN3uiV/07cJ3RkNb3WPcijGsto+lECDS11dKEwTMFeAQDx+V36\n" +"ocbYC/xEuwi3w45oNqGieazzcD/GBbt8OBk3BA==\n" +"=45IR\n" +"-----END PGP PUBLIC KEY BLOCK-----\n"; + +static const char conflictMsg1[] = "-----BEGIN PGP MESSAGE-----\n" +"\n" +"owGbwMvMwCG2z/HA4aX/5W0YT3MlMUTkb2xPSizi6ihlYRDjYJAVU2Sp4mTZNpV3\n" +"5QwmLqkrMLWsTCCFDFycAjCR1vcMf4U0Qrs6qzqfHJ9puGOFduLN2nVmhsumxjBE\n" +"mdw4lr1ehIWR4QdLuNBpe86PGx1PtNXfVAzm/hu+vfjCp5BVNjPTM9L0eAA=\n" +"=MfBD\n" +"-----END PGP MESSAGE-----\n"; + +static const char conflictMsg2[] = "-----BEGIN PGP MESSAGE-----\n" +"\n" +"owGbwMvMwCGWyVDbmL9q4RvG01xJDBH5GyvS8vO5OkpZGMQ4GGTFFFnmpbjJHVG+\n" +"b/DJQ6QIppaVCaSQgYtTACaySZHhr/SOPrdFJ89KrcwKY5i1XnflXYf2PK76SafK\n" +"tkxXuXzvJAvDX4kCybuqFk3HXCexz2+IrnZ+5X5EqOnuo3ens2cte+uzlhMA\n" +"=BIAi\n" +"-----END PGP MESSAGE-----\n"; + class TofuInfoTest: public QGpgMETest { Q_OBJECT +Q_SIGNALS: + void asyncDone(); +private: bool testSupported() { return !(GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16"); @@ -72,12 +123,12 @@ class TofuInfoTest: public QGpgMETest void testTofuCopy(TofuInfo other, const TofuInfo &orig) { - Q_ASSERT(!orig.isNull()); - Q_ASSERT(!other.isNull()); - Q_ASSERT(orig.signLast() == other.signLast()); - Q_ASSERT(orig.signCount() == other.signCount()); - Q_ASSERT(orig.validity() == other.validity()); - Q_ASSERT(orig.policy() == other.policy()); + QVERIFY(!orig.isNull()); + QVERIFY(!other.isNull()); + QVERIFY(orig.signLast() == other.signLast()); + QVERIFY(orig.signCount() == other.signCount()); + QVERIFY(orig.validity() == other.validity()); + QVERIFY(orig.policy() == other.policy()); } void signAndVerify(const QString &what, const GpgME::Key &key, int expected) @@ -94,10 +145,10 @@ class TofuInfoTest: public QGpgMETest auto sigResult = job->exec(keys, what.toUtf8(), NormalSignatureMode, signedData); delete job; - Q_ASSERT(!sigResult.error()); + QVERIFY(!sigResult.error()); foreach (const auto uid, keys[0].userIDs()) { auto info = uid.tofuInfo(); - Q_ASSERT(info.signCount() == expected - 1); + QVERIFY(info.signCount() == expected - 1); } auto verifyJob = openpgp()->verifyOpaqueJob(); @@ -106,25 +157,25 @@ class TofuInfoTest: public QGpgMETest auto result = verifyJob->exec(signedData, verified); delete verifyJob; - Q_ASSERT(!result.error()); - Q_ASSERT(verified == what.toUtf8()); + QVERIFY(!result.error()); + QVERIFY(verified == what.toUtf8()); - Q_ASSERT(result.numSignatures() == 1); + QVERIFY(result.numSignatures() == 1); auto sig = result.signatures()[0]; auto key2 = sig.key(); - Q_ASSERT(!key.isNull()); - Q_ASSERT(!strcmp (key2.primaryFingerprint(), key.primaryFingerprint())); - Q_ASSERT(!strcmp (key.primaryFingerprint(), sig.fingerprint())); + QVERIFY(!key.isNull()); + QVERIFY(!strcmp (key2.primaryFingerprint(), key.primaryFingerprint())); + QVERIFY(!strcmp (key.primaryFingerprint(), sig.fingerprint())); auto stats = key2.userID(0).tofuInfo(); - Q_ASSERT(!stats.isNull()); + QVERIFY(!stats.isNull()); if (stats.signCount() != expected) { std::cout << "################ Key before verify: " << key << "################ Key after verify: " << key2; } - Q_ASSERT(stats.signCount() == expected); + QVERIFY(stats.signCount() == expected); } private Q_SLOTS: @@ -134,13 +185,13 @@ private Q_SLOTS: return; } TofuInfo tofu; - Q_ASSERT(tofu.isNull()); - Q_ASSERT(!tofu.description()); - Q_ASSERT(!tofu.signCount()); - Q_ASSERT(!tofu.signLast()); - Q_ASSERT(!tofu.signFirst()); - Q_ASSERT(tofu.validity() == TofuInfo::ValidityUnknown); - Q_ASSERT(tofu.policy() == TofuInfo::PolicyUnknown); + QVERIFY(tofu.isNull()); + QVERIFY(!tofu.description()); + QVERIFY(!tofu.signCount()); + QVERIFY(!tofu.signLast()); + QVERIFY(!tofu.signFirst()); + QVERIFY(tofu.validity() == TofuInfo::ValidityUnknown); + QVERIFY(tofu.policy() == TofuInfo::PolicyUnknown); } void testTofuInfo() @@ -153,30 +204,30 @@ private Q_SLOTS: QByteArray plaintext; auto ctx = Job::context(job); - Q_ASSERT(ctx); + QVERIFY(ctx); ctx->setSender("alfa@example.net"); auto result = job->exec(data1, plaintext); delete job; - Q_ASSERT(!result.isNull()); - Q_ASSERT(!result.error()); - Q_ASSERT(!strcmp(plaintext.constData(), "Just GNU it!\n")); + QVERIFY(!result.isNull()); + QVERIFY(!result.error()); + QVERIFY(!strcmp(plaintext.constData(), "Just GNU it!\n")); - Q_ASSERT(result.numSignatures() == 1); + QVERIFY(result.numSignatures() == 1); Signature sig = result.signatures()[0]; /* TOFU is always marginal */ - Q_ASSERT(sig.validity() == Signature::Marginal); + QVERIFY(sig.validity() == Signature::Marginal); auto stats = sig.key().userID(0).tofuInfo(); - Q_ASSERT(!stats.isNull()); - Q_ASSERT(sig.key().primaryFingerprint()); - Q_ASSERT(sig.fingerprint()); - Q_ASSERT(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); - Q_ASSERT(stats.signFirst() == stats.signLast()); - Q_ASSERT(stats.signCount() == 1); - Q_ASSERT(stats.policy() == TofuInfo::PolicyAuto); - Q_ASSERT(stats.validity() == TofuInfo::LittleHistory); + QVERIFY(!stats.isNull()); + QVERIFY(sig.key().primaryFingerprint()); + QVERIFY(sig.fingerprint()); + QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); + QVERIFY(stats.signFirst() == stats.signLast()); + QVERIFY(stats.signCount() == 1); + QVERIFY(stats.policy() == TofuInfo::PolicyAuto); + QVERIFY(stats.validity() == TofuInfo::LittleHistory); testTofuCopy(stats, stats); @@ -186,42 +237,42 @@ private Q_SLOTS: result = job->exec(data1, plaintext); delete job; - Q_ASSERT(!result.isNull()); - Q_ASSERT(!result.error()); + QVERIFY(!result.isNull()); + QVERIFY(!result.error()); - Q_ASSERT(result.numSignatures() == 1); + QVERIFY(result.numSignatures() == 1); sig = result.signatures()[0]; /* TOFU is always marginal */ - Q_ASSERT(sig.validity() == Signature::Marginal); + QVERIFY(sig.validity() == Signature::Marginal); stats = sig.key().userID(0).tofuInfo(); - Q_ASSERT(!stats.isNull()); - Q_ASSERT(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); - Q_ASSERT(stats.signFirst() == stats.signLast()); - Q_ASSERT(stats.signCount() == 1); - Q_ASSERT(stats.policy() == TofuInfo::PolicyAuto); - Q_ASSERT(stats.validity() == TofuInfo::LittleHistory); + QVERIFY(!stats.isNull()); + QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); + QVERIFY(stats.signFirst() == stats.signLast()); + QVERIFY(stats.signCount() == 1); + QVERIFY(stats.policy() == TofuInfo::PolicyAuto); + QVERIFY(stats.validity() == TofuInfo::LittleHistory); /* Verify that another call yields the same result */ job = openpgp()->verifyOpaqueJob(true); result = job->exec(data1, plaintext); delete job; - Q_ASSERT(!result.isNull()); - Q_ASSERT(!result.error()); + QVERIFY(!result.isNull()); + QVERIFY(!result.error()); - Q_ASSERT(result.numSignatures() == 1); + QVERIFY(result.numSignatures() == 1); sig = result.signatures()[0]; /* TOFU is always marginal */ - Q_ASSERT(sig.validity() == Signature::Marginal); + QVERIFY(sig.validity() == Signature::Marginal); stats = sig.key().userID(0).tofuInfo(); - Q_ASSERT(!stats.isNull()); - Q_ASSERT(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); - Q_ASSERT(stats.signFirst() == stats.signLast()); - Q_ASSERT(stats.signCount() == 1); - Q_ASSERT(stats.policy() == TofuInfo::PolicyAuto); - Q_ASSERT(stats.validity() == TofuInfo::LittleHistory); + QVERIFY(!stats.isNull()); + QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); + QVERIFY(stats.signFirst() == stats.signLast()); + QVERIFY(stats.signCount() == 1); + QVERIFY(stats.policy() == TofuInfo::PolicyAuto); + QVERIFY(stats.validity() == TofuInfo::LittleHistory); } void testTofuSignCount() @@ -235,9 +286,9 @@ private Q_SLOTS: GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("zulu@example.net"), true, keys); delete job; - Q_ASSERT(!keys.empty()); + QVERIFY(!keys.empty()); Key key = keys[0]; - Q_ASSERT(!key.isNull()); + QVERIFY(!key.isNull()); /* As we sign & verify quickly here we need different * messages to avoid having them treated as the same @@ -266,10 +317,10 @@ private Q_SLOTS: auto result = job->exec(QStringList() << QStringLiteral("zulu@example.net"), true, keys); delete job; - Q_ASSERT(!keys.empty()); + QVERIFY(!keys.empty()); auto key = keys[0]; - Q_ASSERT(!key.isNull()); - Q_ASSERT(key.userID(0).tofuInfo().isNull()); + QVERIFY(!key.isNull()); + QVERIFY(key.userID(0).tofuInfo().isNull()); auto keyCopy = key; keyCopy.update(); auto sigCnt = keyCopy.userID(0).tofuInfo().signCount(); @@ -285,13 +336,13 @@ private Q_SLOTS: result = job->exec(QStringList() << QStringLiteral("zulu@example.net"), true, keys); delete job; - Q_ASSERT(!result.error()); - Q_ASSERT(!keys.empty()); + QVERIFY(!result.error()); + QVERIFY(!keys.empty()); auto key2 = keys[0]; - Q_ASSERT(!key2.isNull()); + QVERIFY(!key2.isNull()); auto info = key2.userID(0).tofuInfo(); - Q_ASSERT(!info.isNull()); - Q_ASSERT(info.signCount()); + QVERIFY(!info.isNull()); + QVERIFY(info.signCount()); } void testTofuPolicy() @@ -326,44 +377,126 @@ private Q_SLOTS: << ">\n fpr: " << key.primaryFingerprint(); } } - Q_ASSERT(!result.error()); - Q_ASSERT(!keys.empty()); + QVERIFY(!result.error()); + QVERIFY(!keys.empty()); auto key = keys[0]; - Q_ASSERT(!key.isNull()); - Q_ASSERT(key.userID(0).tofuInfo().policy() != TofuInfo::PolicyBad); + QVERIFY(!key.isNull()); + QVERIFY(key.userID(0).tofuInfo().policy() != TofuInfo::PolicyBad); auto *tofuJob = openpgp()->tofuPolicyJob(); auto err = tofuJob->exec(key, TofuInfo::PolicyBad); - Q_ASSERT(!err); + QVERIFY(!err); result = job->exec(QStringList() << QStringLiteral("bravo@example.net"), false, keys); - Q_ASSERT(!keys.empty()); + QVERIFY(!keys.empty()); key = keys[0]; - Q_ASSERT(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyBad); + QVERIFY(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyBad); err = tofuJob->exec(key, TofuInfo::PolicyGood); result = job->exec(QStringList() << QStringLiteral("bravo@example.net"), false, keys); key = keys[0]; - Q_ASSERT(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyGood); + QVERIFY(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyGood); delete tofuJob; delete job; } + void testTofuConflict() + { + if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.19") { + return; + } + + // Import key 1 + auto importjob = openpgp()->importJob(); + connect(importjob, &ImportJob::result, this, + [this](ImportResult result, QString, Error) + { + QVERIFY(!result.error()); + QVERIFY(!result.imports().empty()); + QVERIFY(result.numImported()); + Q_EMIT asyncDone(); + }); + importjob->start(QByteArray(conflictKey1)); + QSignalSpy spy (this, SIGNAL(asyncDone())); + QVERIFY(spy.wait()); + + // Verify Message 1 + const QByteArray signedData(conflictMsg1); + auto verifyJob = openpgp()->verifyOpaqueJob(true); + QByteArray verified; + auto result = verifyJob->exec(signedData, verified); + delete verifyJob; + + QVERIFY(!result.isNull()); + QVERIFY(!result.error()); + + QVERIFY(result.numSignatures() == 1); + auto sig = result.signatures()[0]; + QVERIFY(sig.validity() == Signature::Marginal); + + auto stats = sig.key().userID(0).tofuInfo(); + QVERIFY(!stats.isNull()); + QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); + QVERIFY(stats.signFirst() == stats.signLast()); + QVERIFY(stats.signCount() == 1); + QVERIFY(stats.policy() == TofuInfo::PolicyAuto); + QVERIFY(stats.validity() == TofuInfo::LittleHistory); + + // Import key 2 + importjob = openpgp()->importJob(); + connect(importjob, &ImportJob::result, this, + [this](ImportResult result, QString, Error) + { + QVERIFY(!result.error()); + QVERIFY(!result.imports().empty()); + QVERIFY(result.numImported()); + Q_EMIT asyncDone(); + }); + importjob->start(QByteArray(conflictKey2)); + QSignalSpy spy2 (this, SIGNAL(asyncDone())); + QVERIFY(spy2.wait()); + + // Verify Message 2 + const QByteArray signedData2(conflictMsg2); + QByteArray verified2; + verifyJob = openpgp()->verifyOpaqueJob(true); + result = verifyJob->exec(signedData2, verified2); + delete verifyJob; + + QVERIFY(!result.isNull()); + QVERIFY(!result.error()); + + QVERIFY(result.numSignatures() == 1); + sig = result.signatures()[0]; + QVERIFY(sig.validity() == Signature::Unknown); + // TODO activate when implemented + // QVERIFY(sig.summary() == Signature::TofuConflict); + + stats = sig.key().userID(0).tofuInfo(); + QVERIFY(!stats.isNull()); + QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint())); + QVERIFY(stats.signFirst() == stats.signLast()); + QVERIFY(stats.signCount() == 1); + QVERIFY(stats.policy() == TofuInfo::PolicyAsk); + QVERIFY(stats.validity() == TofuInfo::Conflict); + } + + void initTestCase() { QGpgMETest::initTestCase(); const QString gpgHome = qgetenv("GNUPGHOME"); qputenv("GNUPGHOME", mDir.path().toUtf8()); - Q_ASSERT(mDir.isValid()); + QVERIFY(mDir.isValid()); QFile conf(mDir.path() + QStringLiteral("/gpg.conf")); - Q_ASSERT(conf.open(QIODevice::WriteOnly)); + QVERIFY(conf.open(QIODevice::WriteOnly)); conf.write("trust-model tofu+pgp"); conf.close(); QFile agentConf(mDir.path() + QStringLiteral("/gpg-agent.conf")); - Q_ASSERT(agentConf.open(QIODevice::WriteOnly)); + QVERIFY(agentConf.open(QIODevice::WriteOnly)); agentConf.write("allow-loopback-pinentry"); agentConf.close(); - Q_ASSERT(copyKeyrings(gpgHome, mDir.path())); + QVERIFY(copyKeyrings(gpgHome, mDir.path())); } private: QTemporaryDir mDir; diff --git a/lang/qt/tests/t-various.cpp b/lang/qt/tests/t-various.cpp new file mode 100644 index 0000000..35d8da9 --- /dev/null +++ b/lang/qt/tests/t-various.cpp @@ -0,0 +1,167 @@ +/* t-various.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2017 Intevation GmbH + + 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 <QDebug> +#include <QTest> +#include <QSignalSpy> +#include <QTemporaryDir> +#include "keylistjob.h" +#include "protocol.h" +#include "keylistresult.h" +#include "context.h" +#include "engineinfo.h" +#include "dn.h" +#include "data.h" +#include "dataprovider.h" + +#include "t-support.h" + +using namespace QGpgME; +using namespace GpgME; + +static const char aKey[] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +"\n" +"mDMEWG+w/hYJKwYBBAHaRw8BAQdAiq1oStvDYg8ZfFs5DgisYJo8dJxD+C/AA21O\n" +"K/aif0O0GXRvZnVfY29uZmxpY3RAZXhhbXBsZS5jb22IlgQTFggAPhYhBHoJBLaV\n" +"DamYAgoa1L5BwMOl/x88BQJYb7D+AhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMB\n" +"Ah4BAheAAAoJEL5BwMOl/x88GvwA/0SxkbLyAcshGm2PRrPsFQsSVAfwaSYFVmS2\n" +"cMVIw1PfAQDclRH1Z4MpufK07ju4qI33o4s0UFpVRBuSxt7A4P2ZD7g4BFhvsP4S\n" +"CisGAQQBl1UBBQEBB0AmVrgaDNJ7K2BSalsRo2EkRJjHGqnp5bBB0tapnF81CQMB\n" +"CAeIeAQYFggAIBYhBHoJBLaVDamYAgoa1L5BwMOl/x88BQJYb7D+AhsMAAoJEL5B\n" +"wMOl/x88OR0BAMq4/vmJUORRTmzjHcv/DDrQB030DSq666rlckGIKTShAPoDXM9N\n" +"0gZK+YzvrinSKZXHmn0aSwmC1/hyPybJPEljBw==\n" +"=p2Oj\n" +"-----END PGP PUBLIC KEY BLOCK-----\n"; + +class TestVarious: public QGpgMETest +{ + Q_OBJECT + +Q_SIGNALS: + void asyncDone(); + +private Q_SLOTS: + void testDN() + { + DN dn(QStringLiteral("CN=Before\\0DAfter,OU=Test,DC=North America,DC=Fabrikam,DC=COM")); + QVERIFY(dn.dn() == QStringLiteral("CN=Before\rAfter,OU=Test,DC=North America,DC=Fabrikam,DC=COM")); + QStringList attrOrder; + attrOrder << QStringLiteral("DC") << QStringLiteral("OU") << QStringLiteral("CN"); + dn.setAttributeOrder(attrOrder); + QVERIFY(dn.prettyDN() == QStringLiteral("DC=North America,DC=Fabrikam,DC=COM,OU=Test,CN=Before\rAfter")); + } + + void testKeyFromFile() + { + if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.14") { + return; + } + QGpgME::QByteArrayDataProvider dp(aKey); + Data data(&dp); + const auto keys = data.toKeys(); + QVERIFY(keys.size() == 1); + const auto key = keys[0]; + QVERIFY(!key.isNull()); + QVERIFY(key.primaryFingerprint() == QStringLiteral("7A0904B6950DA998020A1AD4BE41C0C3A5FF1F3C")); + } + + void testQuickUid() + { + if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.13") { + return; + } + KeyListJob *job = openpgp()->keyListJob(false, true, true); + std::vector<GpgME::Key> keys; + GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("alfa@example.net"), + false, keys); + delete job; + QVERIFY (!result.error()); + QVERIFY (keys.size() == 1); + Key key = keys.front(); + + QVERIFY (key.numUserIDs() == 3); + const char uid[] = "Foo Bar (with comment) <foo@bar.baz>"; + + auto ctx = Context::createForProtocol(key.protocol()); + QVERIFY (ctx); + TestPassphraseProvider provider; + ctx->setPassphraseProvider(&provider); + ctx->setPinentryMode(Context::PinentryLoopback); + + QVERIFY(!ctx->addUid(key, uid)); + delete ctx; + key.update(); + + QVERIFY (key.numUserIDs() == 4); + bool id_found = false;; + for (const auto &u: key.userIDs()) { + if (!strcmp (u.id(), uid)) { + QVERIFY (!u.isRevoked()); + id_found = true; + break; + } + } + QVERIFY (id_found); + + ctx = Context::createForProtocol(key.protocol()); + QVERIFY (!ctx->revUid(key, uid)); + delete ctx; + key.update(); + + bool id_revoked = false;; + for (const auto &u: key.userIDs()) { + if (!strcmp (u.id(), uid)) { + id_revoked = true; + break; + } + } + QVERIFY(id_revoked); + } + + void initTestCase() + { + QGpgMETest::initTestCase(); + const QString gpgHome = qgetenv("GNUPGHOME"); + QVERIFY(copyKeyrings(gpgHome, mDir.path())); + qputenv("GNUPGHOME", mDir.path().toUtf8()); + } + +private: + QTemporaryDir mDir; +}; + +QTEST_MAIN(TestVarious) + +#include "t-various.moc" diff --git a/lang/qt/tests/t-verify.cpp b/lang/qt/tests/t-verify.cpp index aedfc19..7caed28 100644 --- a/lang/qt/tests/t-verify.cpp +++ b/lang/qt/tests/t-verify.cpp @@ -70,14 +70,14 @@ private Q_SLOTS: QByteArray verified; auto result = verifyJob->exec(signedData, verified); - Q_ASSERT(!result.error()); + QVERIFY(!result.error()); delete verifyJob; - Q_ASSERT(result.numSignatures() == 1); + QVERIFY(result.numSignatures() == 1); auto sig = result.signatures()[0]; const auto key = sig.key(true, false); - Q_ASSERT(!key.isNull()); + QVERIFY(!key.isNull()); bool found = false; for (const auto subkey: key.subkeys()) { @@ -85,7 +85,7 @@ private Q_SLOTS: found = true; } } - Q_ASSERT(found); + QVERIFY(found); } }; diff --git a/lang/qt/tests/t-wkspublish.cpp b/lang/qt/tests/t-wkspublish.cpp index 326ecaa..c51e8f9 100644 --- a/lang/qt/tests/t-wkspublish.cpp +++ b/lang/qt/tests/t-wkspublish.cpp @@ -127,12 +127,12 @@ private Q_SLOTS: auto job = openpgp()->wksPublishJob(); connect(job, &WKSPublishJob::result, this, [this] (Error err, QByteArray, QByteArray, QString, Error) { - Q_ASSERT(err); + QVERIFY(err); Q_EMIT asyncDone(); }); job->startCheck ("testuser1@localhost"); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } #ifdef DO_ONLINE_TESTS private Q_SLOTS: @@ -147,15 +147,15 @@ private: [this] (Error err, QByteArray, QByteArray, QString, Error) { if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.0.16") { std::cout << err; - Q_ASSERT(err); + QVERIFY(err); } else { - Q_ASSERT(!err); + QVERIFY(!err); } Q_EMIT asyncDone(); }); job->startCheck ("testuser1@test.gnupg.org"); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } void testWKSPublishErrors() { @@ -166,13 +166,13 @@ private: auto job = openpgp()->wksPublishJob(); connect(job, &WKSPublishJob::result, this, [this] (Error err, QByteArray, QByteArray, QString, Error) { - Q_ASSERT(err); + QVERIFY(err); Q_EMIT asyncDone(); }); job->startCreate("AB874F24E98EBB8487EE7B170F8E3D97FE7011B7", QStringLiteral("Foo@bar.baz")); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } void testWKSPublishCreate() { @@ -199,31 +199,31 @@ private: connect(keygenjob, &KeyGenerationJob::result, this, [this, &fpr](KeyGenerationResult result, QByteArray, QString, Error) { - Q_ASSERT(!result.error()); + QVERIFY(!result.error()); fpr = QByteArray(result.fingerprint()); - Q_ASSERT(!fpr.isEmpty()); + QVERIFY(!fpr.isEmpty()); Q_EMIT asyncDone(); }); keygenjob->start(args); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); /* Then try to create a request. */ auto job = openpgp()->wksPublishJob(); connect(job, &WKSPublishJob::result, this, [this] (Error err, QByteArray out, QByteArray, QString, Error) { - Q_ASSERT(!err); + QVERIFY(!err); Q_EMIT asyncDone(); const QString outstr = QString(out); - Q_ASSERT(outstr.contains( + QVERIFY(outstr.contains( QStringLiteral("-----BEGIN PGP PUBLIC KEY BLOCK-----"))); - Q_ASSERT(outstr.contains( + QVERIFY(outstr.contains( QStringLiteral("Content-Type: application/pgp-keys"))); - Q_ASSERT(outstr.contains( + QVERIFY(outstr.contains( QStringLiteral("From: " TEST_ADDRESS))); }); job->startCreate(fpr.constData(), QLatin1String(TEST_ADDRESS)); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } void testWKSPublishReceive() { @@ -235,31 +235,31 @@ private: connect(importjob, &ImportJob::result, this, [this](ImportResult result, QString, Error) { - Q_ASSERT(!result.error()); - Q_ASSERT(!result.imports().empty()); - Q_ASSERT(result.numSecretKeysImported()); + QVERIFY(!result.error()); + QVERIFY(!result.imports().empty()); + QVERIFY(result.numSecretKeysImported()); Q_EMIT asyncDone(); }); importjob->start(QByteArray(testSecKey)); QSignalSpy spy (this, SIGNAL(asyncDone())); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); /* Get a response. */ auto job = openpgp()->wksPublishJob(); connect(job, &WKSPublishJob::result, this, [this] (Error err, QByteArray out, QByteArray, QString, Error) { - Q_ASSERT(!err); + QVERIFY(!err); Q_EMIT asyncDone(); const QString outstr = QString(out); - Q_ASSERT(outstr.contains( + QVERIFY(outstr.contains( QStringLiteral("-----BEGIN PGP MESSAGE-----"))); - Q_ASSERT(outstr.contains( + QVERIFY(outstr.contains( QStringLiteral("Content-Type: multipart/encrypted;"))); - Q_ASSERT(outstr.contains( + QVERIFY(outstr.contains( QStringLiteral("From: " TEST_ADDRESS))); }); job->startReceive(QByteArray(testResponse)); - Q_ASSERT(spy.wait()); + QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT)); } void initTestCase() @@ -267,9 +267,9 @@ private: QGpgMETest::initTestCase(); const QString gpgHome = qgetenv("GNUPGHOME"); qputenv("GNUPGHOME", mDir.path().toUtf8()); - Q_ASSERT(mDir.isValid()); + QVERIFY(mDir.isValid()); QFile agentConf(mDir.path() + QStringLiteral("/gpg-agent.conf")); - Q_ASSERT(agentConf.open(QIODevice::WriteOnly)); + QVERIFY(agentConf.open(QIODevice::WriteOnly)); agentConf.write("allow-loopback-pinentry"); agentConf.close(); } |