diff options
Diffstat (limited to 'lang')
104 files changed, 4158 insertions, 1888 deletions
| diff --git a/lang/cpp/src/context.cpp b/lang/cpp/src/context.cpp index 135e4d56..1e4e5490 100644 --- a/lang/cpp/src/context.cpp +++ b/lang/cpp/src/context.cpp @@ -1028,6 +1028,9 @@ unsigned int to_auditlog_flags(unsigned int flags)      if (flags & Context::AuditLogWithHelp) {          result |= GPGME_AUDITLOG_WITH_HELP;      } +    if (flags & Context::DiagnosticAuditLog) { +        result |= GPGME_AUDITLOG_DIAG; +    }      return result;  } @@ -1436,6 +1439,23 @@ Error Context::createKey (const char *userid,                   flags));  } +KeyGenerationResult Context::createKeyEx (const char *userid, +                                          const char *algo, +                                          unsigned long reserved, +                                          unsigned long expires, +                                          const Key &certkey, +                                          unsigned int flags) +{ +    d->lasterr = gpgme_op_createkey(d->ctx, +                 userid, +                 algo, +                 reserved, +                 expires, +                 certkey.impl(), +                 flags); +    return KeyGenerationResult(d->ctx, Error(d->lasterr)); +} +  Error Context::addUid(const Key &k, const char *userid)  {      return Error(d->lasterr = gpgme_op_adduid(d->ctx, @@ -1478,6 +1498,16 @@ Error Context::startCreateSubkey(const Key &k, const char *algo,                   k.impl(), algo, reserved, expires, flags));  } +Error Context::setFlag(const char *name, const char *value) +{ +  return Error(d->lasterr = gpgme_set_ctx_flag(d->ctx, name, value)); +} + +const char *Context::getFlag(const char *name) const +{ +  return gpgme_get_ctx_flag(d->ctx, name); +} +  // 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 aff8e49a..6e27daa6 100644 --- a/lang/cpp/src/context.h +++ b/lang/cpp/src/context.h @@ -86,6 +86,9 @@ public:      void setOffline(bool useOfflineMode);      bool offline() const; +    const char *getFlag(const char *name) const; +    Error setFlag(const char *name, const char *value); +      enum CertificateInclusion {          DefaultCertificates = -256,          AllCertificatesExceptRoot = -2, @@ -231,6 +234,14 @@ public:                       const Key &certkey,                       unsigned int flags); +    // Same as create key but returning a result +    GpgME::KeyGenerationResult createKeyEx (const char *userid, +                                            const char *algo, +                                            unsigned long reserved, +                                            unsigned long expires, +                                            const Key &certkey, +                                            unsigned int flags); +      Error addUid(const Key &key, const char *userid);      Error startAddUid(const Key &key, const char *userid); @@ -390,7 +401,9 @@ public:      //      //      enum AuditLogFlags { +        DefaultAuditLog = 0,          HtmlAuditLog = 1, +        DiagnosticAuditLog = 2,          AuditLogWithHelp = 128      };      GpgME::Error startGetAuditLog(Data &output, unsigned int flags = 0); @@ -453,6 +466,7 @@ public:      {          return d;      } +  private:      // Helper functions that need to be context because they rely      // on the "Friendlyness" of context to access the gpgme types. diff --git a/lang/cpp/src/data.cpp b/lang/cpp/src/data.cpp index 52b8da24..2782aa79 100644 --- a/lang/cpp/src/data.cpp +++ b/lang/cpp/src/data.cpp @@ -232,6 +232,11 @@ off_t GpgME::Data::seek(off_t offset, int whence)      return gpgme_data_seek(d->data, offset, whence);  } +GpgME::Error GpgME::Data::rewind() +{ +    return Error(gpgme_data_rewind(d->data)); +} +  std::vector<GpgME::Key> GpgME::Data::toKeys(Protocol proto) const  {      std::vector<GpgME::Key> ret; diff --git a/lang/cpp/src/data.h b/lang/cpp/src/data.h index 446f6fa3..df8607e7 100644 --- a/lang/cpp/src/data.h +++ b/lang/cpp/src/data.h @@ -110,6 +110,9 @@ public:      ssize_t write(const void *buffer, size_t length);      off_t seek(off_t offset, int whence); +    /* Convenience function to do a seek (0, SEEK_SET).  */ +    Error rewind(); +      /** 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; diff --git a/lang/cpp/src/decryptionresult.cpp b/lang/cpp/src/decryptionresult.cpp index 17524db9..ea0a8a5c 100644 --- a/lang/cpp/src/decryptionresult.cpp +++ b/lang/cpp/src/decryptionresult.cpp @@ -51,6 +51,9 @@ public:          if (res.file_name) {              res.file_name = strdup(res.file_name);          } +        if (res.symkey_algo) { +            res.symkey_algo = strdup(res.symkey_algo); +        }          //FIXME: copying gpgme_recipient_t objects invalidates the keyid member,          //thus we use _keyid for now (internal API)          for (gpgme_recipient_t r = res.recipients ; r ; r = r->next) { @@ -68,6 +71,10 @@ public:              std::free(res.file_name);          }          res.file_name = 0; +        if (res.symkey_algo) { +            std::free(res.symkey_algo); +        } +        res.symkey_algo = 0;      }      _gpgme_op_decrypt_result res; @@ -165,6 +172,11 @@ const char *GpgME::DecryptionResult::symkeyAlgo() const    return d ? d->res.symkey_algo : nullptr;  } +bool GpgME::DecryptionResult::isLegacyCipherNoMDC() const +{ +  return d && d->res.legacy_cipher_nomdc; +} +  class GpgME::DecryptionResult::Recipient::Private : public _gpgme_recipient  {  public: @@ -241,6 +253,7 @@ std::ostream &GpgME::operator<<(std::ostream &os, const DecryptionResult &result             << "\n unsupportedAlgorithm: " << protect(result.unsupportedAlgorithm())             << "\n isWrongKeyUsage:      " << result.isWrongKeyUsage()             << "\n isDeVs                " << result.isDeVs() +           << "\n legacyCipherNoMDC     " << result.isLegacyCipherNoMDC()             << "\n symkeyAlgo:           " << protect(result.symkeyAlgo())             << "\n recipients:\n";          const std::vector<DecryptionResult::Recipient> recipients = result.recipients(); diff --git a/lang/cpp/src/decryptionresult.h b/lang/cpp/src/decryptionresult.h index c270223d..e4d542dd 100644 --- a/lang/cpp/src/decryptionresult.h +++ b/lang/cpp/src/decryptionresult.h @@ -87,6 +87,8 @@ public:      Recipient recipient(unsigned int idx) const;      std::vector<Recipient> recipients() const; +    bool isLegacyCipherNoMDC() const; +  private:      class Private;      void init(gpgme_ctx_t ctx); diff --git a/lang/cpp/src/gpggencardkeyinteractor.cpp b/lang/cpp/src/gpggencardkeyinteractor.cpp index 6f42e473..0ed6781a 100644 --- a/lang/cpp/src/gpggencardkeyinteractor.cpp +++ b/lang/cpp/src/gpggencardkeyinteractor.cpp @@ -36,12 +36,11 @@ using namespace GpgME;  class GpgGenCardKeyInteractor::Private  {  public: -    Private() : keysize(2048), backup(false) +    Private() : keysize("2048"), backup(false)      {      } -    std::string name, email, backupFileName, expiry, serial; -    int keysize; +    std::string name, email, backupFileName, expiry, serial, keysize;      bool backup;  }; @@ -70,7 +69,7 @@ void GpgGenCardKeyInteractor::setDoBackup(bool value)  void GpgGenCardKeyInteractor::setKeySize(int value)  { -    d->keysize = value; +    d->keysize = std::to_string(value);  }  void GpgGenCardKeyInteractor::setExpiry(const std::string &timeStr) @@ -132,7 +131,7 @@ const char *GpgGenCardKeyInteractor::action(Error &err) const      case SIZE:      case SIZE2:      case SIZE3: -        return std::to_string(d->keysize).c_str(); +        return d->keysize.c_str();      case COMMENT:          return "";      case SAVE: diff --git a/lang/cpp/src/key.cpp b/lang/cpp/src/key.cpp index 034286f0..8fc266ff 100644 --- a/lang/cpp/src/key.cpp +++ b/lang/cpp/src/key.cpp @@ -347,6 +347,9 @@ const Key &Key::mergeWith(const Key &other)  void Key::update()  { +    if (isNull() || !primaryFingerprint()) { +        return; +    }      auto ctx = Context::createForProtocol(protocol());      if (!ctx) {          return; @@ -1042,6 +1045,8 @@ std::ostream &operator<<(std::ostream &os, const UserID &uid)             << "\n revoked:   " << uid.isRevoked()             << "\n invalid:   " << uid.isInvalid()             << "\n numsigs:   " << uid.numSignatures() +           << "\n origin:    " << uid.origin() +           << "\n updated:   " << uid.lastUpdate()             << "\n tofuinfo:\n" << uid.tofuInfo();      }      return os << ')'; @@ -1060,6 +1065,8 @@ std::ostream &operator<<(std::ostream &os, const Key &key)             << "\n canEncrypt: " << key.canEncrypt()             << "\n canCertify: " << key.canCertify()             << "\n canAuth:    " << key.canAuthenticate() +           << "\n origin:     " << key.origin() +           << "\n updated:    " << key.lastUpdate()             << "\n uids:\n";          const std::vector<UserID> uids = key.userIDs();          std::copy(uids.begin(), uids.end(), diff --git a/lang/cpp/src/verificationresult.cpp b/lang/cpp/src/verificationresult.cpp index 2c42d074..fa8237ad 100644 --- a/lang/cpp/src/verificationresult.cpp +++ b/lang/cpp/src/verificationresult.cpp @@ -406,7 +406,7 @@ GpgME::Key GpgME::Signature::key(bool search, bool update) const      }      GpgME::Key ret = key(); -    if (ret.isNull() && search) { +    if (ret.isNull() && search && fingerprint ()) {          auto ctx = Context::createForProtocol (d->proto);          if (ctx) {              ctx->setKeyListMode(KeyListMode::Local | diff --git a/lang/python/README b/lang/python/README index 99da4dd7..a13345f7 100644 --- a/lang/python/README +++ b/lang/python/README @@ -13,7 +13,7 @@ Table of Contents  The "gpg" module is a python interface to the GPGME library: -[https://www.gnupg.org/software/gpgme/] +<https://www.gnupg.org/software/gpgme/>  "gpg" offers two interfaces, one is a high-level, curated, and idiomatic  interface that is implemented as a shim on top of the low-level @@ -27,16 +27,16 @@ functionality of the underlying library.  ══════════════    For general discussion and help see the gnupg-users mailing list: -  [https://lists.gnupg.org/mailman/listinfo/gnupg-users] +  <https://lists.gnupg.org/mailman/listinfo/gnupg-users>    For development see the gnupg-devel mailing list: -  [https://lists.gnupg.org/mailman/listinfo/gnupg-devel] +  <https://lists.gnupg.org/mailman/listinfo/gnupg-devel>  2 Bugs  ══════ -  Please report bugs using our bug tracker [https://bugs.gnupg.org] with +  Please report bugs using our bug tracker <https://bugs.gnupg.org> with    tag (aka project) 'gpgme'. @@ -44,8 +44,8 @@ functionality of the underlying library.  ═════════    PyME was created by John Goerzen, and maintained, developed, and -  cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, and everyone -  who contributed to it in any way. +  cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, Justus +  Winter, and everyone who contributed to it in any way.    In 2016 we merged a port of PyME to into the GPGME repository, and    development will continue there.  Please see the VCS history for the @@ -64,14 +64,14 @@ functionality of the underlying library.    • The bindings have been merged into the GPGME repository in 2016.    • The latest version of PyME for Python 3.2 and above (as of May, -    2015) is v0.9.1.  [https://git.gnupg.org/gpgme.git/lang/py3-pyme] +    2015) is v0.9.1.  <https://git.gnupg.org/gpgme.git/lang/py3-pyme>    • The latest version of PyME for Python 2.6 and 2.7 (as of this -    writing) is v0.9.0.  [https://bitbucket.org/malb/pyme] +    writing) is v0.9.0.  <https://bitbucket.org/malb/pyme>    • A previous version of PyME v0.8.0 can be found on sourceforge: -    [http://pyme.sourceforge.net/] +    <http://pyme.sourceforge.net/>    • A previous version of PyME v0.5.1 which works with GPGME v0.3.15 can -    be found on John Goerzen's PyME page: [http://quux.org/devel/pyme/] -    [http://www.complete.org/JohnGoerzen] +    be found on John Goerzen's PyME page: <http://quux.org/devel/pyme/> +    <http://www.complete.org/JohnGoerzen> diff --git a/lang/python/README.org b/lang/python/README.org index cba99669..bd7047cc 100644 --- a/lang/python/README.org +++ b/lang/python/README.org @@ -2,7 +2,7 @@  #+OPTIONS: author:nil  The "gpg" module is a python interface to the GPGME library: -[[https://www.gnupg.org/software/gpgme/]] +[[https://www.gnupg.org/software/gpgme/][https://www.gnupg.org/software/gpgme/]]  "gpg" offers two interfaces, one is a high-level, curated, and  idiomatic interface that is implemented as a shim on top of the @@ -14,21 +14,21 @@ functionality of the underlying library.  * Mailing List  For general discussion and help see the gnupg-users mailing list: -https://lists.gnupg.org/mailman/listinfo/gnupg-users +[[https://lists.gnupg.org/mailman/listinfo/gnupg-users][gnupg-users]]  For development see the gnupg-devel mailing list: -https://lists.gnupg.org/mailman/listinfo/gnupg-devel +[[https://lists.gnupg.org/mailman/listinfo/gnupg-devel][gnupg-devel]]  * Bugs  Please report bugs using our bug tracker -[[https://bugs.gnupg.org]] with tag (aka project) 'gpgme'. +[[https://bugs.gnupg.org][bugs.gnupg.org]] with tag (aka project) 'gpgme'.  * Authors  PyME was created by John Goerzen, and maintained, developed, and -cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, and everyone -who contributed to it in any way. +cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, Justus Winter, +and everyone who contributed to it in any way.  In 2016 we merged a port of PyME to into the GPGME repository, and  development will continue there.  Please see the VCS history for the @@ -46,15 +46,15 @@ references to previous versions.   - The latest version of PyME for Python 3.2 and above (as of     May, 2015) is v0.9.1. -   https://git.gnupg.org/gpgme.git/lang/py3-pyme +   [[https://git.gnupg.org/gpgme.git/lang/py3-pyme][Python 3 PyME]]   - The latest version of PyME for Python 2.6 and 2.7 (as of this -   writing) is v0.9.0.  https://bitbucket.org/malb/pyme +   writing) is v0.9.0.  [[https://bitbucket.org/malb/pyme][PyME 0.9.0]]   - A previous version of PyME v0.8.0 can be found on sourceforge: -   http://pyme.sourceforge.net/ +   [[http://pyme.sourceforge.net/][PyME 0.8.0]]   - A previous version of PyME v0.5.1 which works with GPGME v0.3.15     can be found on John Goerzen's PyME page: -   http://quux.org/devel/pyme/ -   http://www.complete.org/JohnGoerzen +   [[http://quux.org/devel/pyme/][PyME 0.3.15]] +   [[http://www.complete.org/JohnGoerzen][John Goerzen]] diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index ef66effc..a712ec27 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -1,4 +1,5 @@  #+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) +#+AUTHOR: Ben McGinnes  #+LATEX_COMPILER: xelatex  #+LATEX_CLASS: article  #+LATEX_CLASS_OPTIONS: [12pt] @@ -14,14 +15,14 @@    :CUSTOM_ID: intro    :END: -  | Version:        | 0.1.1                                    | -  | Author:         | Ben McGinnes <[email protected]>             | -  | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D | -  | Language:       | Australian English, British English      | -  | xml:lang:       | en-AU, en-GB, en                         | +| Version:        | 0.1.3                                    | +| Author:         | Ben McGinnes <[email protected]>             | +| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D | +| Language:       | Australian English, British English      | +| xml:lang:       | en-AU, en-GB, en                         | -  This document provides basic instruction in how to use the GPGME -  Python bindings to programmatically leverage the GPGME library. +This document provides basic instruction in how to use the GPGME +Python bindings to programmatically leverage the GPGME library.  ** Python 2 versus Python 3 @@ -29,25 +30,25 @@     :CUSTOM_ID: py2-vs-py3     :END: -   Though the GPGME Python bindings themselves provide support for -   both Python 2 and 3, the focus is unequivocally on Python 3 and -   specifically from Python 3.4 and above.  As a consequence all the -   examples and instructions in this guide use Python 3 code. +Though the GPGME Python bindings themselves provide support for both +Python 2 and 3, the focus is unequivocally on Python 3 and +specifically from Python 3.4 and above.  As a consequence all the +examples and instructions in this guide use Python 3 code. -   Much of it will work with Python 2, but much of it also deals with -   Python 3 byte literals, particularly when reading and writing data. -   Developers concentrating on Python 2.7, and possibly even 2.6, will -   need to make the appropriate modifications to support the older -   string and unicode types as opposed to bytes. +Much of it will work with Python 2, but much of it also deals with +Python 3 byte literals, particularly when reading and writing data. +Developers concentrating on Python 2.7, and possibly even 2.6, will +need to make the appropriate modifications to support the older string +and unicode types as opposed to bytes. -   There are multiple reasons for concentrating on Python 3; some of -   which relate to the immediate integration of these bindings, some -   of which relate to longer term plans for both GPGME and the python -   bindings and some of which relate to the impending EOL period for -   Python 2.7.  Essentially, though, there is little value in tying -   the bindings to a version of the language which is a dead end and -   the advantages offered by Python 3 over Python 2 make handling the -   data types with which GPGME deals considerably easier. +There are multiple reasons for concentrating on Python 3; some of +which relate to the immediate integration of these bindings, some of +which relate to longer term plans for both GPGME and the python +bindings and some of which relate to the impending EOL period for +Python 2.7.  Essentially, though, there is little value in tying the +bindings to a version of the language which is a dead end and the +advantages offered by Python 3 over Python 2 make handling the data +types with which GPGME deals considerably easier.  ** Examples @@ -55,8 +56,8 @@     :CUSTOM_ID: howto-python3-examples     :END: -   All of the examples found in this document can be found as Python 3 -   scripts in the =lang/python/examples/howto= directory. +All of the examples found in this document can be found as Python 3 +scripts in the =lang/python/examples/howto= directory.  * GPGME Concepts @@ -70,19 +71,17 @@     :CUSTOM_ID: gpgme-c-api     :END: -   Unlike many modern APIs with which programmers will be more -   familiar with these days, the GPGME API is a C API.  The API is -   intended for use by C coders who would be able to access its -   features by including the =gpgme.h= header file with their own C -   source code and then access its functions just as they would any -   other C headers. +Unlike many modern APIs with which programmers will be more familiar +with these days, the GPGME API is a C API.  The API is intended for +use by C coders who would be able to access its features by including +the =gpgme.h= header file with their own C source code and then access +its functions just as they would any other C headers. -   This is a very effective method of gaining complete access to the -   API and in the most efficient manner possible.  It does, however, -   have the drawback that it cannot be directly used by other -   languages without some means of providing an interface to those -   languages.  This is where the need for bindings in various -   languages stems. +This is a very effective method of gaining complete access to the API +and in the most efficient manner possible.  It does, however, have the +drawback that it cannot be directly used by other languages without +some means of providing an interface to those languages.  This is +where the need for bindings in various languages stems.  ** Python bindings @@ -90,16 +89,16 @@     :CUSTOM_ID: gpgme-python-bindings     :END: -   The Python bindings for GPGME provide a higher level means of -   accessing the complete feature set of GPGME itself.  It also -   provides a more pythonic means of calling these API functions. +The Python bindings for GPGME provide a higher level means of +accessing the complete feature set of GPGME itself.  It also provides +a more pythonic means of calling these API functions. -   The bindings are generated dynamically with SWIG and the copy of -   =gpgme.h= generated when GPGME is compiled. +The bindings are generated dynamically with SWIG and the copy of +=gpgme.h= generated when GPGME is compiled. -   This means that a version of the Python bindings is fundamentally -   tied to the exact same version of GPGME used to generate that copy -   of =gpgme.h=. +This means that a version of the Python bindings is fundamentally tied +to the exact same version of GPGME used to generate that copy of +=gpgme.h=.  ** Difference between the Python bindings and other GnuPG Python packages @@ -107,9 +106,9 @@     :CUSTOM_ID: gpgme-python-bindings-diffs     :END: -   There have been numerous attempts to add GnuPG support to Python -   over the years.  Some of the most well known are listed here, along -   with what differentiates them. +There have been numerous attempts to add GnuPG support to Python over +the years.  Some of the most well known are listed here, along with +what differentiates them.  *** The python-gnupg package maintained by Vinay Sajip @@ -117,23 +116,23 @@      :CUSTOM_ID: diffs-python-gnupg      :END: -    This is arguably the most popular means of integrating GPG with -    Python.  The package utilises the =subprocess= module to implement -    wrappers for the =gpg= and =gpg2= executables normally invoked on -    the command line (=gpg.exe= and =gpg2.exe= on Windows). +This is arguably the most popular means of integrating GPG with +Python.  The package utilises the =subprocess= module to implement +wrappers for the =gpg= and =gpg2= executables normally invoked on the +command line (=gpg.exe= and =gpg2.exe= on Windows). -    The popularity of this package stemmed from its ease of use and -    capability in providing the most commonly required features. +The popularity of this package stemmed from its ease of use and +capability in providing the most commonly required features. -    Unfortunately it has been beset by a number of security issues in -    the past; most of which stemmed from using unsafe methods of -    accessing the command line via the =subprocess= calls.  While some -    effort has been made over the last two to three years (as of 2018) -    to mitigate this, particularly by no longer providing shell access -    through those subprocess calls, the wrapper is still somewhat -    limited in the scope of its GnuPG features coverage. +Unfortunately it has been beset by a number of security issues in the +past; most of which stemmed from using unsafe methods of accessing the +command line via the =subprocess= calls.  While some effort has been +made over the last two to three years (as of 2018) to mitigate this, +particularly by no longer providing shell access through those +subprocess calls, the wrapper is still somewhat limited in the scope +of its GnuPG features coverage. -    The python-gnupg package is available under the MIT license. +The python-gnupg package is available under the MIT license.  *** The gnupg package created and maintained by Isis Lovecruft @@ -141,20 +140,20 @@      :CUSTOM_ID: diffs-isis-gnupg      :END: -    In 2015 Isis Lovecruft from the Tor Project forked and then -    re-implemented the python-gnupg package as just gnupg.  This new -    package also relied on subprocess to call the =gpg= or =gpg2= -    binaries, but did so somewhat more securely. +In 2015 Isis Lovecruft from the Tor Project forked and then +re-implemented the python-gnupg package as just gnupg.  This new +package also relied on subprocess to call the =gpg= or =gpg2= +binaries, but did so somewhat more securely. -    The naming and version numbering selected for this package, -    however, resulted in conflicts with the original python-gnupg and -    since its functions were called in a different manner to -    python-gnupg, the release of this package also resulted in a great -    deal of consternation when people installed what they thought was -    an upgrade that subsequently broke the code relying on it. +The naming and version numbering selected for this package, however, +resulted in conflicts with the original python-gnupg and since its +functions were called in a different manner to python-gnupg, the +release of this package also resulted in a great deal of consternation +when people installed what they thought was an upgrade that +subsequently broke the code relying on it. -    The gnupg package is available under the GNU General Public -    License version 3.0 (or any later version). +The gnupg package is available under the GNU General Public License +version 3.0 (or any later version).  *** The PyME package maintained by Martin Albrecht @@ -162,26 +161,26 @@      :CUSTOM_ID: diffs-pyme      :END: -    This package is the origin of these bindings, though they are -    somewhat different now.  For details of when and how the PyME -    package was folded back into GPGME itself see the /Short History/ -    document[fn:1] in the Python bindings =docs= directory.[fn:2] +This package is the origin of these bindings, though they are somewhat +different now.  For details of when and how the PyME package was +folded back into GPGME itself see the /Short History/ document[fn:1] +in the Python bindings =docs= directory.[fn:2] -    The PyME package was first released in 2002 and was also the first -    attempt to implement a low level binding to GPGME.  In doing so it -    provided access to considerably more functionality than either the -    =python-gnupg= or =gnupg= packages. +The PyME package was first released in 2002 and was also the first +attempt to implement a low level binding to GPGME.  In doing so it +provided access to considerably more functionality than either the +=python-gnupg= or =gnupg= packages. -    The PyME package is only available for Python 2.6 and 2.7. +The PyME package is only available for Python 2.6 and 2.7. -    Porting the PyME package to Python 3.4 in 2015 is what resulted in -    it being folded into the GPGME project and the current bindings -    are the end result of that effort. +Porting the PyME package to Python 3.4 in 2015 is what resulted in it +being folded into the GPGME project and the current bindings are the +end result of that effort. -    The PyME package is available under the same dual licensing as -    GPGME itself: the GNU General Public License version 2.0 (or any -    later version) and the GNU Lesser General Public License version -    2.1 (or any later version). +The PyME package is available under the same dual licensing as GPGME +itself: the GNU General Public License version 2.0 (or any later +version) and the GNU Lesser General Public License version 2.1 (or any +later version).  * GPGME Python bindings installation @@ -195,19 +194,18 @@     :CUSTOM_ID: do-not-use-pypi     :END: -   Most third-party Python packages and modules are available and -   distributed through the Python Package Installer, known as PyPI. +Most third-party Python packages and modules are available and +distributed through the Python Package Installer, known as PyPI. -   Due to the nature of what these bindings are and how they work, it -   is infeasible to install the GPGME Python bindings in the same way. +Due to the nature of what these bindings are and how they work, it is +infeasible to install the GPGME Python bindings in the same way. -   This is because the bindings use SWIG to dynamically generate C -   bindings against =gpgme.h= and =gpgme.h= is generated from -   =gpgme.h.in= at compile time when GPGME is built from source.  Thus -   to include a package in PyPI which actually built correctly would -   require either statically built libraries for every architecture -   bundled with it or a full implementation of C for each -   architecture. +This is because the bindings use SWIG to dynamically generate C +bindings against =gpgme.h= and =gpgme.h= is generated from +=gpgme.h.in= at compile time when GPGME is built from source.  Thus to +include a package in PyPI which actually built correctly would require +either statically built libraries for every architecture bundled with +it or a full implementation of C for each architecture.  ** Requirements @@ -215,14 +213,13 @@     :CUSTOM_ID: gpgme-python-requirements     :END: -   The GPGME Python bindings only have three requirements: +The GPGME Python bindings only have three requirements: -   1. A suitable version of Python 2 or Python 3.  With Python 2 that -      means Python 2.7 and with Python 3 that means Python 3.4 or -      higher. -   2. SWIG. -   3. GPGME itself.  Which also means that all of GPGME's dependencies -      must be installed too. +1. A suitable version of Python 2 or Python 3.  With Python 2 that +   means Python 2.7 and with Python 3 that means Python 3.4 or higher. +2. SWIG. +3. GPGME itself.  Which also means that all of GPGME's dependencies +   must be installed too.  ** Installation @@ -230,24 +227,23 @@     :CUSTOM_ID: installation     :END: -   Installing the Python bindings is effectively achieved by compiling -   and installing GPGME itself. +Installing the Python bindings is effectively achieved by compiling +and installing GPGME itself. -   Once SWIG is installed with Python and all the dependencies for -   GPGME are installed you only need to confirm that the version(s) of -   Python you want the bindings installed for are in your =$PATH=. +Once SWIG is installed with Python and all the dependencies for GPGME +are installed you only need to confirm that the version(s) of Python +you want the bindings installed for are in your =$PATH=. -   By default GPGME will attempt to install the bindings for the most -   recent or highest version number of Python 2 and Python 3 it -   detects in =$PATH=.  It specifically checks for the =python= and -   =python3= executables first and then checks for specific version -   numbers. +By default GPGME will attempt to install the bindings for the most +recent or highest version number of Python 2 and Python 3 it detects +in =$PATH=.  It specifically checks for the =python= and =python3= +executables first and then checks for specific version numbers. -   For Python 2 it checks for these executables in this order: -   =python=, =python2= and =python2.7=. +For Python 2 it checks for these executables in this order: =python=, +=python2= and =python2.7=. -   For Python 3 it checks for these executables in this order: -   =python3=, =python3.6=, =python3.5= and =python3.4=. +For Python 3 it checks for these executables in this order: =python3=, +=python3.6=, =python3.5=, =python3.4= and =python3.7=.[fn:4]  *** Installing GPGME @@ -255,8 +251,8 @@      :CUSTOM_ID: install-gpgme      :END: -    See the GPGME =README= file for details of how to install GPGME from -    source. +See the GPGME =README= file for details of how to install GPGME from +source.  * Fundamentals @@ -264,9 +260,9 @@    :CUSTOM_ID: howto-fund-a-mental    :END: -  Before we can get to the fun stuff, there are a few matters -  regarding GPGME's design which hold true whether you're dealing with -  the C code directly or these Python bindings. +Before we can get to the fun stuff, there are a few matters regarding +GPGME's design which hold true whether you're dealing with the C code +directly or these Python bindings.  ** No REST @@ -274,23 +270,23 @@     :CUSTOM_ID: no-rest-for-the-wicked     :END: -   The first part of which is or will be fairly blatantly obvious upon -   viewing the first example, but it's worth reiterating anyway.  That -   being that this API is /*not*/ a REST API.  Nor indeed could it -   ever be one. +The first part of which is or will be fairly blatantly obvious upon +viewing the first example, but it's worth reiterating anyway.  That +being that this API is /*not*/ a REST API.  Nor indeed could it ever +be one. -   Most, if not all, Python programmers (and not just Python -   programmers) know how easy it is to work with a RESTful API.  In -   fact they've become so popular that many other APIs attempt to -   emulate REST-like behaviour as much as they are able.  Right down -   to the use of JSON formatted output to facilitate the use of their -   API without having to retrain developers. +Most, if not all, Python programmers (and not just Python programmers) +know how easy it is to work with a RESTful API.  In fact they've +become so popular that many other APIs attempt to emulate REST-like +behaviour as much as they are able.  Right down to the use of JSON +formatted output to facilitate the use of their API without having to +retrain developers. -   This API does not do that.  It would not be able to do that and -   also provide access to the entire C API on which it's built.  It -   does, however, provide a very pythonic interface on top of the -   direct bindings and it's this pythonic layer with which this HOWTO -   deals with. +This API does not do that.  It would not be able to do that and also +provide access to the entire C API on which it's built.  It does, +however, provide a very pythonic interface on top of the direct +bindings and it's this pythonic layer with which this HOWTO deals +with.  ** Context @@ -298,22 +294,22 @@     :CUSTOM_ID: howto-get-context     :END: -   One of the reasons which prevents this API from being RESTful is -   that most operations require more than one instruction to the API -   to perform the task.  Sure, there are certain functions which can -   be performed simultaneously, particularly if the result known or -   strongly anticipated (e.g. selecting and encrypting to a key known -   to be in the public keybox). +One of the reasons which prevents this API from being RESTful is that +most operations require more than one instruction to the API to +perform the task.  Sure, there are certain functions which can be +performed simultaneously, particularly if the result known or strongly +anticipated (e.g. selecting and encrypting to a key known to be in the +public keybox). -   There are many more, however, which cannot be manipulated so -   readily: they must be performed in a specific sequence and the -   result of one operation has a direct bearing on the outcome of -   subsequent operations.  Not merely by generating an error either. +There are many more, however, which cannot be manipulated so readily: +they must be performed in a specific sequence and the result of one +operation has a direct bearing on the outcome of subsequent +operations.  Not merely by generating an error either. -   When dealing with this type of persistent state on the web, full of -   both the RESTful and REST-like, it's most commonly referred to as a -   session.  In GPGME, however, it is called a context and every -   operation type has one. +When dealing with this type of persistent state on the web, full of +both the RESTful and REST-like, it's most commonly referred to as a +session.  In GPGME, however, it is called a context and every +operation type has one.  * Working with keys @@ -327,58 +323,58 @@     :CUSTOM_ID: howto-keys-selection     :END: -   Selecting keys to encrypt to or to sign with will be a common -   occurrence when working with GPGMe and the means available for -   doing so are quite simple. +Selecting keys to encrypt to or to sign with will be a common +occurrence when working with GPGMe and the means available for doing +so are quite simple. -   They do depend on utilising a Context; however once the data is -   recorded in another variable, that Context does not need to be the -   same one which subsequent operations are performed. +They do depend on utilising a Context; however once the data is +recorded in another variable, that Context does not need to be the +same one which subsequent operations are performed. -   The easiest way to select a specific key is by searching for that -   key's key ID or fingerprint, preferably the full fingerprint -   without any spaces in it.  A long key ID will probably be okay, but -   is not advised and short key IDs are already a problem with some -   being generated to match specific patterns.  It does not matter -   whether the pattern is upper or lower case. +The easiest way to select a specific key is by searching for that +key's key ID or fingerprint, preferably the full fingerprint without +any spaces in it.  A long key ID will probably be okay, but is not +advised and short key IDs are already a problem with some being +generated to match specific patterns.  It does not matter whether the +pattern is upper or lower case. -   So this is the best method: +So this is the best method: -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") -     keys = list(k) -   #+end_src +k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") +keys = list(k) +#+END_SRC -   This is passable and very likely to be common: +This is passable and very likely to be common: -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") -     keys = list(k) -   #+end_src +k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") +keys = list(k) +#+END_SRC -   And this is a really bad idea: +And this is a really bad idea: -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     k = gpg.Context().keylist(pattern="0xDEADBEEF") -     keys = list(k) -   #+end_src +k = gpg.Context().keylist(pattern="0xDEADBEEF") +keys = list(k) +#+END_SRC -   Alternatively it may be that the intention is to create a list of -   keys which all match a particular search string.  For instance all -   the addresses at a particular domain, like this: +Alternatively it may be that the intention is to create a list of keys +which all match a particular search string.  For instance all the +addresses at a particular domain, like this: -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     ncsc = gpg.Context().keylist(pattern="ncsc.mil") -     nsa = list(ncsc) -   #+end_src +ncsc = gpg.Context().keylist(pattern="ncsc.mil") +nsa = list(ncsc) +#+END_SRC  *** Counting keys @@ -386,29 +382,28 @@      :CUSTOM_ID: howto-keys-counting      :END: -    Counting the number of keys in your public keybox (=pubring.kbx=), -    the format which has superseded the old keyring format -    (=pubring.gpg= and =secring.gpg=), or the number of secret keys is -    a very simple task. +Counting the number of keys in your public keybox (=pubring.kbx=), the +format which has superseded the old keyring format (=pubring.gpg= and +=secring.gpg=), or the number of secret keys is a very simple task. -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      c = gpg.Context() -      seckeys = c.keylist(pattern=None, secret=True) -      pubkeys = c.keylist(pattern=None, secret=False) +c = gpg.Context() +seckeys = c.keylist(pattern=None, secret=True) +pubkeys = c.keylist(pattern=None, secret=False) -      seclist = list(seckeys) -      secnum = len(seclist) +seclist = list(seckeys) +secnum = len(seclist) -      publist = list(pubkeys) -      pubnum = len(publist) +publist = list(pubkeys) +pubnum = len(publist) -      print(""" -      Number of secret keys:  {0} -      Number of public keys:  {1} -      """.format(secnum, pubnum)) -    #+end_src +print(""" +  Number of secret keys:  {0} +  Number of public keys:  {1} +""".format(secnum, pubnum)) +#+END_SRC  ** Get key @@ -416,42 +411,398 @@     :CUSTOM_ID: howto-get-key     :END: -   An alternative method of getting a single key via its fingerprint -   is available directly within a Context with =Context().get_key=. -   This is the preferred method of selecting a key in order to modify -   it, sign or certify it and for obtaining relevant data about a -   single key as a part of other functions; when verifying a signature -   made by that key, for instance. +An alternative method of getting a single key via its fingerprint is +available directly within a Context with =Context().get_key=.  This is +the preferred method of selecting a key in order to modify it, sign or +certify it and for obtaining relevant data about a single key as a +part of other functions; when verifying a signature made by that key, +for instance. -   By default this method will select public keys, but it can select -   secret keys as well. +By default this method will select public keys, but it can select +secret keys as well. -   This first example demonstrates selecting the current key of Werner -   Koch, which is due to expire at the end of 2018: +This first example demonstrates selecting the current key of Werner +Koch, which is due to expire at the end of 2018: -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" -     key = gpg.Context().get_key(fingerprint) -   #+end_src +fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" +key = gpg.Context().get_key(fingerprint) +#+END_SRC -   Whereas this example demonstrates selecting the author's current -   key with the =secret= key word argument set to =True=: +Whereas this example demonstrates selecting the author's current key +with the =secret= key word argument set to =True=: -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" -     key = gpg.Context().get_key(fingerprint, secret=True) -   #+end_src +fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" +key = gpg.Context().get_key(fingerprint, secret=True) +#+END_SRC -   It is, of course, quite possible to select expired, disabled and -   revoked keys with this function, but only to effectively display -   information about those keys. +It is, of course, quite possible to select expired, disabled and +revoked keys with this function, but only to effectively display +information about those keys. -   It is also possible to use both unicode or string literals and byte -   literals with the fingerprint when getting a key in this way. +It is also possible to use both unicode or string literals and byte +literals with the fingerprint when getting a key in this way. + + +** Importing keys +   :PROPERTIES: +   :CUSTOM_ID: howto-import-key +   :END: + +Importing keys is possible with the =key_import()= method and takes +one argument which is a bytes literal object containing either the +binary or ASCII armoured key data for one or more keys. + +The following example retrieves one or more keys from the SKS +keyservers via the web using the requests module. Since requests +returns the content as a bytes literal object, we can then use that +directly to import the resulting data into our keybox. + +#+BEGIN_SRC python -i +import gpg +import os.path +import requests + +c = gpg.Context() +url = "https://sks-keyservers.net/pks/lookup" +pattern = input("Enter the pattern to search for key or user IDs: ") +payload = { "op": "get", "search": pattern } + +r = requests.get(url, verify=True, params=payload) +result = c.key_import(r.content) + +if result is not None and hasattr(result, "considered") is False: +    print(result) +elif result is not None and hasattr(result, "considered") is True: +    num_keys = len(result.imports) +    new_revs = result.new_revocations +    new_sigs = result.new_signatures +    new_subs = result.new_sub_keys +    new_uids = result.new_user_ids +    new_scrt = result.secret_imported +    nochange = result.unchanged +    print(""" +  The total number of keys considered for import was:  {0} + +     Number of keys revoked:  {1} +   Number of new signatures:  {2} +      Number of new subkeys:  {3} +     Number of new user IDs:  {4} +  Number of new secret keys:  {5} +   Number of unchanged keys:  {6} + +  The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, +           nochange)) +    for i in range(num_keys): +        print("{0}\n".format(result.imports[i].fpr)) +else: +    pass +#+END_SRC + +*NOTE:* When searching for a key ID of any length or a fingerprint +(without spaces), the SKS servers require the the leading =0x= +indicative of hexadecimal be included. Also note that the old short +key IDs (e.g. =0xDEADBEEF=) should no longer be used due to the +relative ease by which such key IDs can be reproduced, as demonstrated +by the Evil32 Project in 2014 (which was subsequently exploited in +2016). + + +** Exporting keys +   :PROPERTIES: +   :CUSTOM_ID: howto-export-key +   :END: + +Exporting keys remains a reasonably simple task, but has been +separated into three different functions for the OpenPGP cryptographic +engine.  Two of those functions are for exporting public keys and the +third is for exporting secret keys. + + +*** Exporting public keys +    :PROPERTIES: +    :CUSTOM_ID: howto-export-public-key +    :END: + +There are two methods of exporting public keys, both of which are very +similar to the other.  The default method, =key_export()=, will export +a public key or keys matching a specified pattern as normal.  The +alternative, the =key_export_minimal()= method, will do the same thing +except producing a minimised output with extra signatures and third +party signatures or certifications removed. + +#+BEGIN_SRC python -i +import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the path and filename to save the secret key to: ") +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +try: +    result = c.key_export(pattern=logrus) +except: +    result = c.key_export(pattern=None) + +if result is not None: +    with open(keyfile, "wb") as f: +        f.write(result) +else: +    pass +#+END_SRC + +It is important to note that the result will only return =None= when a +pattern has been entered for =logrus=, but it has not matched any +keys. When the search pattern itself is set to =None= this triggers +the exporting of the entire public keybox. + +#+BEGIN_SRC python -i +import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys in minimised form. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the path and filename to save the secret key to: ") +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +try: +    result = c.key_export_minimal(pattern=logrus) +except: +    result = c.key_export_minimal(pattern=None) + +if result is not None: +    with open(keyfile, "wb") as f: +        f.write(result) +else: +    pass +#+END_SRC + + +*** Exporting secret keys +    :PROPERTIES: +    :CUSTOM_ID: howto-export-secret-key +    :END: + +Exporting secret keys is, functionally, very similar to exporting +public keys; save for the invocation of =pinentry= via =gpg-agent= in +order to securely enter the key's passphrase and authorise the export. + +The following example exports the secret key to a file which is then +set with the same permissions as the output files created by the +command line secret key export options. + +#+BEGIN_SRC python -i +import gpg +import os +import os.path +import sys + +print(""" +This script exports one or more secret keys. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the path and filename to save the secret key to: ") +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +try: +    result = c.key_export_secret(pattern=logrus) +except: +    result = c.key_export_secret(pattern=None) + +if result is not None: +    with open(keyfile, "wb") as f: +        f.write(result) +    os.chmod(keyfile, 0o600) +else: +    pass +#+END_SRC + +Alternatively the approach of the following script can be used.  This +longer example saves the exported secret key(s) in files in the GnuPG +home directory, in addition to setting the file permissions as only +readable and writable by the user.  It also exports the secret key(s) +twice in order to output both GPG binary (=.gpg=) and ASCII armoured +(=.asc=) files. + +#+BEGIN_SRC python -i +import gpg +import os +import os.path +import subprocess +import sys + +print(""" +This script exports one or more secret keys as both ASCII armored and binary +file formats, saved in files within the user's GPG home directory. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +if sys.platform == "win32": +    gpgconfcmd = "gpgconf.exe --list-dirs homedir" +else: +    gpgconfcmd = "gpgconf --list-dirs homedir" + +a = gpg.Context(armor=True) +b = gpg.Context() +c = gpg.Context() + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the filename to save the secret key to: ") +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +if c.home_dir is not None: +    if c.home_dir.endswith("/"): +        gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile) +        ascfile = "{0}{1}.asc".format(c.home_dir, keyfile) +    else: +        gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile) +        ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile) +else: +    if os.path.exists(os.environ["GNUPGHOME"]) is True: +        hd = os.environ["GNUPGHOME"] +    else: +        hd = subprocess.getoutput(gpgconfcmd) +    gpgfile = "{0}/{1}.gpg".format(hd, keyfile) +    ascfile = "{0}/{1}.asc".format(hd, keyfile) + +try: +    a_result = a.key_export_secret(pattern=logrus) +    b_result = b.key_export_secret(pattern=logrus) +except: +    a_result = a.key_export_secret(pattern=None) +    b_result = b.key_export_secret(pattern=None) + +if a_result is not None: +    with open(ascfile, "wb") as f: +        f.write(a_result) +    os.chmod(ascfile, 0o600) +else: +    pass + +if b_result is not None: +    with open(gpgfile, "wb") as f: +        f.write(b_result) +    os.chmod(gpgfile, 0o600) +else: +    pass +#+END_SRC  * Basic Functions @@ -459,10 +810,10 @@    :CUSTOM_ID: howto-the-basics    :END: -  The most frequently called features of any cryptographic library -  will be the most fundamental tasks for encryption software.  In this -  section we will look at how to programmatically encrypt data, -  decrypt it, sign it and verify signatures. +The most frequently called features of any cryptographic library will +be the most fundamental tasks for encryption software.  In this +section we will look at how to programmatically encrypt data, decrypt +it, sign it and verify signatures.  ** Encryption @@ -470,10 +821,9 @@     :CUSTOM_ID: howto-basic-encryption     :END: -   Encrypting is very straight forward.  In the first example below -   the message, =text=, is encrypted to a single recipient's key.  In -   the second example the message will be encrypted to multiple -   recipients. +Encrypting is very straight forward.  In the first example below the +message, =text=, is encrypted to a single recipient's key.  In the +second example the message will be encrypted to multiple recipients.  *** Encrypting to one key @@ -481,73 +831,70 @@      :CUSTOM_ID: howto-basic-encryption-single      :END: -    Once the the Context is set the main issues with encrypting data -    is essentially reduced to key selection and the keyword arguments -    specified in the =gpg.Context().encrypt()= method. - -    Those keyword arguments are: =recipients=, a list of keys -    encrypted to (covered in greater detail in the following section); -    =sign=, whether or not to sign the plaintext data, see subsequent -    sections on signing and verifying signatures below (defaults to -    =True=); =sink=, to write results or partial results to a secure -    sink instead of returning it (defaults to =None=); =passphrase=, -    only used when utilising symmetric encryption (defaults to -    =None=); =always_trust=, used to override the trust model settings -    for recipient keys (defaults to =False=); =add_encrypt_to=, -    utilises any preconfigured =encrypt-to= or =default-key= settings -    in the user's =gpg.conf= file (defaults to =False=); =prepare=, -    prepare for encryption (defaults to =False=); =expect_sign=, -    prepare for signing (defaults to =False=); =compress=, compresses -    the plaintext prior to encryption (defaults to =True=). - -    #+begin_src python -      import gpg - -      a_key = "0x12345678DEADBEEF" -      text = b"""Some text to test with. - -      Since the text in this case must be bytes, it is most likely that -      the input form will be a separate file which is opened with "rb" -      as this is the simplest method of obtaining the correct data -      format. -      """ - -      c = gpg.Context(armor=True) -      rkey = list(c.keylist(pattern=a_key, secret=False)) -      ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False) - -      with open("secret_plans.txt.asc", "wb") as afile: -	  afile.write(ciphertext) -    #+end_src - -    Though this is even more likely to be used like this; with the -    plaintext input read from a file, the recipient keys used for -    encryption regardless of key trust status and the encrypted output -    also encrypted to any preconfigured keys set in the =gpg.conf= -    file: - -    #+begin_src python -      import gpg - -      a_key = "0x12345678DEADBEEF" - -      with open("secret_plans.txt", "rb") as afile: -	  text = afile.read() - -      c = gpg.Context(armor=True) -      rkey = list(c.keylist(pattern=a_key, secret=False)) -      ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, -			      sign=True, always_trust=True, add_encrypt_to=True) - -      with open("secret_plans.txt.asc", "wb") as afile: -	  afile.write(ciphertext) -    #+end_src - -    If the =recipients= paramater is empty then the plaintext is -    encrypted symmetrically.  If no =passphrase= is supplied as a -    parameter or via a callback registered with the =Context()= then -    an out-of-band prompt for the passphrase via pinentry will be -    invoked. +Once the the Context is set the main issues with encrypting data is +essentially reduced to key selection and the keyword arguments +specified in the =gpg.Context().encrypt()= method. + +Those keyword arguments are: =recipients=, a list of keys encrypted to +(covered in greater detail in the following section); =sign=, whether +or not to sign the plaintext data, see subsequent sections on signing +and verifying signatures below (defaults to =True=); =sink=, to write +results or partial results to a secure sink instead of returning it +(defaults to =None=); =passphrase=, only used when utilising symmetric +encryption (defaults to =None=); =always_trust=, used to override the +trust model settings for recipient keys (defaults to =False=); +=add_encrypt_to=, utilises any preconfigured =encrypt-to= or +=default-key= settings in the user's =gpg.conf= file (defaults to +=False=); =prepare=, prepare for encryption (defaults to =False=); +=expect_sign=, prepare for signing (defaults to =False=); =compress=, +compresses the plaintext prior to encryption (defaults to =True=). + +#+BEGIN_SRC python -i +import gpg + +a_key = "0x12345678DEADBEEF" +text = b"""Some text to test with. + +Since the text in this case must be bytes, it is most likely that +the input form will be a separate file which is opened with "rb" +as this is the simplest method of obtaining the correct data format. +""" + +c = gpg.Context(armor=True) +rkey = list(c.keylist(pattern=a_key, secret=False)) +ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False) + +with open("secret_plans.txt.asc", "wb") as afile: +    afile.write(ciphertext) +#+END_SRC + +Though this is even more likely to be used like this; with the +plaintext input read from a file, the recipient keys used for +encryption regardless of key trust status and the encrypted output +also encrypted to any preconfigured keys set in the =gpg.conf= file: + +#+BEGIN_SRC python -i +import gpg + +a_key = "0x12345678DEADBEEF" + +with open("secret_plans.txt", "rb") as afile: +    text = afile.read() + +c = gpg.Context(armor=True) +rkey = list(c.keylist(pattern=a_key, secret=False)) +ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True, +                                            always_trust=True, +                                            add_encrypt_to=True) + +with open("secret_plans.txt.asc", "wb") as afile: +    afile.write(ciphertext) +#+END_SRC + +If the =recipients= paramater is empty then the plaintext is encrypted +symmetrically.  If no =passphrase= is supplied as a parameter or via a +callback registered with the =Context()= then an out-of-band prompt +for the passphrase via pinentry will be invoked.  *** Encrypting to multiple keys @@ -555,101 +902,101 @@      :CUSTOM_ID: howto-basic-encryption-multiple      :END: -    Encrypting to multiple keys essentially just expands upon the key -    selection process and the recipients from the previous examples. - -    The following example encrypts a message (=text=) to everyone with -    an email address on the =gnupg.org= domain,[fn:3] but does /not/ encrypt -    to a default key or other key which is configured to normally -    encrypt to. - -    #+begin_src python -      import gpg - -      text = b"""Oh look, another test message. - -      The same rules apply as with the previous example and more likely -      than not, the message will actually be drawn from reading the -      contents of a file or, maybe, from entering data at an input() -      prompt. - -      Since the text in this case must be bytes, it is most likely that -      the input form will be a separate file which is opened with "rb" -      as this is the simplest method of obtaining the correct data -      format. -      """ - -      c = gpg.Context(armor=True) -      rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) -      logrus = [] - -      for i in range(len(rpattern)): -	  if rpattern[i].can_encrypt == 1: -	      logrus.append(rpattern[i]) - -      ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, sign=False, -						  always_trust=True) - -      with open("secret_plans.txt.asc", "wb") as afile: -	  afile.write(ciphertext) -    #+end_src - -    All it would take to change the above example to sign the message -    and also encrypt the message to any configured default keys would -    be to change the =c.encrypt= line to this: - -    #+begin_src python -      ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, -						  always_trust=True, -						  add_encrypt_to=True) -    #+end_src - -    The only keyword arguments requiring modification are those for -    which the default values are changing.  The default value of -    =sign= is =True=, the default of =always_trust= is =False=, the -    default of =add_encrypt_to= is =False=. - -    If =always_trust= is not set to =True= and any of the recipient -    keys are not trusted (e.g. not signed or locally signed) then the -    encryption will raise an error.  It is possible to mitigate this -    somewhat with something more like this: - -    #+begin_src python -      import gpg - -      with open("secret_plans.txt.asc", "rb") as afile: -	  text = afile.read() - -      c = gpg.Context(armor=True) -      rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) -      logrus = [] - -      for i in range(len(rpattern)): -	  if rpattern[i].can_encrypt == 1: -	      logrus.append(rpattern[i]) - -      try: -	  ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, -						      add_encrypt_to=True) -      except gpg.errors.InvalidRecipients as e: -	  for i in range(len(e.recipients)): -	      for n in range(len(logrus)): -		  if logrus[n].fpr == e.recipients[i].fpr: -		      logrus.remove(logrus[n]) -		  else: -		      pass -	  try: -	      ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, -							  add_encrypt_to=True) -	  except: -	      pass - -      with open("secret_plans.txt.asc", "wb") as afile: -	  afile.write(ciphertext) -    #+end_src - -    This will attempt to encrypt to all the keys searched for, then -    remove invalid recipients if it fails and try again. +Encrypting to multiple keys essentially just expands upon the key +selection process and the recipients from the previous examples. + +The following example encrypts a message (=text=) to everyone with an +email address on the =gnupg.org= domain,[fn:3] but does /not/ encrypt +to a default key or other key which is configured to normally encrypt +to. + +#+BEGIN_SRC python -i +import gpg + +text = b"""Oh look, another test message. + +The same rules apply as with the previous example and more likely +than not, the message will actually be drawn from reading the +contents of a file or, maybe, from entering data at an input() +prompt. + +Since the text in this case must be bytes, it is most likely that +the input form will be a separate file which is opened with "rb" +as this is the simplest method of obtaining the correct data +format. +""" + +c = gpg.Context(armor=True) +rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) +logrus = [] + +for i in range(len(rpattern)): +    if rpattern[i].can_encrypt == 1: +        logrus.append(rpattern[i]) + +ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, +                                            sign=False, always_trust=True) + +with open("secret_plans.txt.asc", "wb") as afile: +    afile.write(ciphertext) +#+END_SRC + +All it would take to change the above example to sign the message +and also encrypt the message to any configured default keys would +be to change the =c.encrypt= line to this: + +#+BEGIN_SRC python -i +ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, +                                            always_trust=True, +                                            add_encrypt_to=True) +#+END_SRC + +The only keyword arguments requiring modification are those for which +the default values are changing.  The default value of =sign= is +=True=, the default of =always_trust= is =False=, the default of +=add_encrypt_to= is =False=. + +If =always_trust= is not set to =True= and any of the recipient keys +are not trusted (e.g. not signed or locally signed) then the +encryption will raise an error.  It is possible to mitigate this +somewhat with something more like this: + +#+BEGIN_SRC python -i +import gpg + +with open("secret_plans.txt.asc", "rb") as afile: +    text = afile.read() + +c = gpg.Context(armor=True) +rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) +logrus = [] + +for i in range(len(rpattern)): +    if rpattern[i].can_encrypt == 1: +        logrus.append(rpattern[i]) + +    try: +        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, +                                                    add_encrypt_to=True) +    except gpg.errors.InvalidRecipients as e: +        for i in range(len(e.recipients)): +            for n in range(len(logrus)): +                if logrus[n].fpr == e.recipients[i].fpr: +                    logrus.remove(logrus[n]) +                else: +                    pass +        try: +            ciphertext, result, sign_result = c.encrypt(text, +                                                        recipients=logrus, +                                                        add_encrypt_to=True) +            with open("secret_plans.txt.asc", "wb") as afile: +                afile.write(ciphertext) +        except: +            pass +#+END_SRC + +This will attempt to encrypt to all the keys searched for, then remove +invalid recipients if it fails and try again.  ** Decryption @@ -657,39 +1004,39 @@     :CUSTOM_ID: howto-basic-decryption     :END: -   Decrypting something encrypted to a key in one's secret keyring is -   fairly straight forward. +Decrypting something encrypted to a key in one's secret keyring is +fairly straight forward. -   In this example code, however, preconfiguring either -   =gpg.Context()= or =gpg.core.Context()= as =c= is unnecessary -   because there is no need to modify the Context prior to conducting -   the decryption and since the Context is only used once, setting it -   to =c= simply adds lines for no gain. +In this example code, however, preconfiguring either =gpg.Context()= +or =gpg.core.Context()= as =c= is unnecessary because there is no need +to modify the Context prior to conducting the decryption and since the +Context is only used once, setting it to =c= simply adds lines for no +gain. -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     ciphertext = input("Enter path and filename of encrypted file: ") -     newfile = input("Enter path and filename of file to save decrypted data to: ") +ciphertext = input("Enter path and filename of encrypted file: ") +newfile = input("Enter path and filename of file to save decrypted data to: ") -     with open(ciphertext, "rb") as cfile: -	 try: -	     plaintext, result, verify_result = gpg.Context().decrypt(cfile) -	 except gpg.errors.GPGMEError as e: -	     plaintext = None -	     print(e) +with open(ciphertext, "rb") as cfile: +    try: +        plaintext, result, verify_result = gpg.Context().decrypt(cfile) +    except gpg.errors.GPGMEError as e: +        plaintext = None +        print(e) -     if plaintext is not None: -	 with open(newfile, "wb") as nfile: -	     nfile.write(plaintext) -     else: -	 pass -   #+end_src +if plaintext is not None: +    with open(newfile, "wb") as nfile: +	    nfile.write(plaintext) +    else: +        pass +#+END_SRC -   The data available in =plaintext= in this example is the decrypted -   content as a byte object, the recipient key IDs and algorithms in -   =result= and the results of verifying any signatures of the data in -   =verify_result=. +The data available in =plaintext= in this example is the decrypted +content as a byte object, the recipient key IDs and algorithms in +=result= and the results of verifying any signatures of the data in +=verify_result=.  ** Signing text and files @@ -697,7 +1044,7 @@     :CUSTOM_ID: howto-basic-signing     :END: -   The following sections demonstrate how to specify keys to sign with. +The following sections demonstrate how to specify keys to sign with.  *** Signing key selection @@ -705,30 +1052,30 @@      :CUSTOM_ID: howto-basic-signing-signers      :END: -    By default GPGME and the Python bindings will use the default key -    configured for the user invoking the GPGME API.  If there is no -    default key specified and there is more than one secret key -    available it may be necessary to specify the key or keys with -    which to sign messages and files. +By default GPGME and the Python bindings will use the default key +configured for the user invoking the GPGME API.  If there is no +default key specified and there is more than one secret key available +it may be necessary to specify the key or keys with which to sign +messages and files. -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      logrus = input("Enter the email address or string to match signing keys to: ") -      hancock = gpg.Context().keylist(pattern=logrus, secret=True) -      sig_src = list(hancock) -    #+end_src +logrus = input("Enter the email address or string to match signing keys to: ") +hancock = gpg.Context().keylist(pattern=logrus, secret=True) +sig_src = list(hancock) +#+END_SRC -    The signing examples in the following sections include the -    explicitly designated =signers= parameter in two of the five -    examples; once where the resulting signature would be ASCII -    armoured and once where it would not be armoured. +The signing examples in the following sections include the explicitly +designated =signers= parameter in two of the five examples; once where +the resulting signature would be ASCII armoured and once where it +would not be armoured. -    While it would be possible to enter a key ID or fingerprint here -    to match a specific key, it is not possible to enter two -    fingerprints and match two keys since the patten expects a string, -    bytes or None and not a list.  A string with two fingerprints -    won't match any single key. +While it would be possible to enter a key ID or fingerprint here to +match a specific key, it is not possible to enter two fingerprints and +match two keys since the patten expects a string, bytes or None and +not a list.  A string with two fingerprints won't match any single +key.  *** Normal or default signing messages or files @@ -736,54 +1083,54 @@      :CUSTOM_ID: howto-basic-signing-normal      :END: -    The normal or default signing process is essentially the same as -    is most often invoked when also encrypting a message or file.  So -    when the encryption component is not utilised, the result is to -    produce an encoded and signed output which may or may not be ASCII -    armoured and which may or may not also be compressed. +The normal or default signing process is essentially the same as is +most often invoked when also encrypting a message or file.  So when +the encryption component is not utilised, the result is to produce an +encoded and signed output which may or may not be ASCII armoured and +which may or may not also be compressed. -    By default compression will be used unless GnuPG detects that the -    plaintext is already compressed.  ASCII armouring will be -    determined according to the value of =gpg.Context().armor=. +By default compression will be used unless GnuPG detects that the +plaintext is already compressed.  ASCII armouring will be determined +according to the value of =gpg.Context().armor=. -    The compression algorithm is selected in much the same way as the -    symmetric encryption algorithm or the hash digest algorithm is -    when multiple keys are involved; from the preferences saved into -    the key itself or by comparison with the preferences with all -    other keys involved. +The compression algorithm is selected in much the same way as the +symmetric encryption algorithm or the hash digest algorithm is when +multiple keys are involved; from the preferences saved into the key +itself or by comparison with the preferences with all other keys +involved. -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     text0 = """Declaration of ... something. +text0 = """Declaration of ... something. -     """ -     text = text0.encode() +""" +text = text0.encode() -     c = gpg.Context(armor=True, signers=sig_src) -     signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) +c = gpg.Context(armor=True, signers=sig_src) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) -     with open("/path/to/statement.txt.asc", "w") as afile: -	 afile.write(signed_data.decode()) -   #+end_src +with open("/path/to/statement.txt.asc", "w") as afile: +    afile.write(signed_data.decode()) +#+END_SRC -   Though everything in this example is accurate, it is more likely -   that reading the input data from another file and writing the -   result to a new file will be performed more like the way it is done -   in the next example.  Even if the output format is ASCII armoured. +Though everything in this example is accurate, it is more likely that +reading the input data from another file and writing the result to a +new file will be performed more like the way it is done in the next +example.  Even if the output format is ASCII armoured. -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     with open("/path/to/statement.txt", "rb") as tfile: -         text = tfile.read() +with open("/path/to/statement.txt", "rb") as tfile: +    text = tfile.read() -     c = gpg.Context() -     signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) -     with open("/path/to/statement.txt.sig", "wb") as afile: -         afile.write(signed_data) -   #+end_src +with open("/path/to/statement.txt.sig", "wb") as afile: +    afile.write(signed_data) +#+END_SRC  *** Detached signing messages and files @@ -791,41 +1138,40 @@      :CUSTOM_ID: howto-basic-signing-detached      :END: -    Detached signatures will often be needed in programmatic uses of -    GPGME, either for signing files (e.g. tarballs of code releases) -    or as a component of message signing (e.g. PGP/MIME encoded -    email). +Detached signatures will often be needed in programmatic uses of +GPGME, either for signing files (e.g. tarballs of code releases) or as +a component of message signing (e.g. PGP/MIME encoded email). -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      text0 = """Declaration of ... something. +text0 = """Declaration of ... something. -      """ -      text = text0.encode() +""" +text = text0.encode() -      c = gpg.Context(armor=True) -      signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) +c = gpg.Context(armor=True) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) -      with open("/path/to/statement.txt.asc", "w") as afile: -          afile.write(signed_data.decode()) -    #+end_src +with open("/path/to/statement.txt.asc", "w") as afile: +    afile.write(signed_data.decode()) +#+END_SRC -    As with normal signatures, detached signatures are best handled as -    byte literals, even when the output is ASCII armoured. +As with normal signatures, detached signatures are best handled as +byte literals, even when the output is ASCII armoured. -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      with open("/path/to/statement.txt", "rb") as tfile: -          text = tfile.read() +with open("/path/to/statement.txt", "rb") as tfile: +    text = tfile.read() -      c = gpg.Context(signers=sig_src) -      signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) +c = gpg.Context(signers=sig_src) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) -      with open("/path/to/statement.txt.sig", "wb") as afile: -          afile.write(signed_data) -    #+end_src +with open("/path/to/statement.txt.sig", "wb") as afile: +    afile.write(signed_data) +#+END_SRC  *** Clearsigning messages or text @@ -833,41 +1179,40 @@      :CUSTOM_ID: howto-basic-signing-clear      :END: -    Though PGP/in-line messages are no longer encouraged in favour of -    PGP/MIME, there is still sometimes value in utilising in-line -    signatures.  This is where clear-signed messages or text is of -    value. +Though PGP/in-line messages are no longer encouraged in favour of +PGP/MIME, there is still sometimes value in utilising in-line +signatures.  This is where clear-signed messages or text is of value. -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      text0 = """Declaration of ... something. +text0 = """Declaration of ... something. -      """ -      text = text0.encode() +""" +text = text0.encode() -      c = gpg.Context() -      signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) -      with open("/path/to/statement.txt.asc", "w") as afile: -	  afile.write(signed_data.decode()) -    #+end_src +with open("/path/to/statement.txt.asc", "w") as afile: +    afile.write(signed_data.decode()) +#+END_SRC -    In spite of the appearance of a clear-signed message, the data -    handled by GPGME in signing it must still be byte literals. +In spite of the appearance of a clear-signed message, the data handled +by GPGME in signing it must still be byte literals. -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      with open("/path/to/statement.txt", "rb") as tfile: -          text = tfile.read() +with open("/path/to/statement.txt", "rb") as tfile: +    text = tfile.read() -      c = gpg.Context() -      signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) -      with open("/path/to/statement.txt.asc", "wb") as afile: -          afile.write(signed_data) -    #+end_src +with open("/path/to/statement.txt.asc", "wb") as afile: +    afile.write(signed_data) +#+END_SRC  ** Signature verification @@ -875,152 +1220,152 @@     :CUSTOM_ID: howto-basic-verification     :END: -   Essentially there are two principal methods of verification of a -   signature.  The first of these is for use with the normal or -   default signing method and for clear-signed messages.  The second is -   for use with files and data with detached signatures. - -   The following example is intended for use with the default signing -   method where the file was not ASCII armoured: - -   #+begin_src python -     import gpg -     import time - -     filename = "statement.txt" -     gpg_file = "statement.txt.gpg" - -     c = gpg.Context() - -     try: -	 data, result = c.verify(open(gpg_file)) -	 verified = True -     except gpg.errors.BadSignatures as e: -	 verified = False -	 print(e) - -     if verified is True: -	 for i in range(len(result.signatures)): -	     sign = result.signatures[i] -	     print("""Good signature from: -     {0} -     with key {1} -     made at {2} -     """.format(c.get_key(sign.fpr).uids[0].uid, -		sign.fpr, time.ctime(sign.timestamp))) -     else: -	 pass -   #+end_src - -   Whereas this next example, which is almost identical would work -   with normal ASCII armoured files and with clear-signed files: - -   #+begin_src python -     import gpg -     import time - -     filename = "statement.txt" -     asc_file = "statement.txt.asc" - -     c = gpg.Context() - -     try: -	 data, result = c.verify(open(asc_file)) -	 verified = True -     except gpg.errors.BadSignatures as e: -	 verified = False -	 print(e) - -     if verified is True: -	 for i in range(len(result.signatures)): -	     sign = result.signatures[i] -	     print("""Good signature from: -     {0} -     with key {1} -     made at {2} -     """.format(c.get_key(sign.fpr).uids[0].uid, -		sign.fpr, time.ctime(sign.timestamp))) -     else: -	 pass -   #+end_src - -   In both of the previous examples it is also possible to compare the -   original data that was signed against the signed data in =data= to -   see if it matches with something like this: - -   #+begin_src python -     with open(filename, "rb") as afile: -         text = afile.read() - -     if text == data: -	 print("Good signature.") -     else: -	 pass -   #+end_src - -   The following two examples, however, deal with detached signatures. -   With his method of verification the data that was signed does not -   get returned since it is already being explicitly referenced in the -   first argument of =c.verify=.  So =data= is =None= and only the -   information in =result= is available. - -   #+begin_src python -     import gpg -     import time - -     filename = "statement.txt" -     sig_file = "statement.txt.sig" - -     c = gpg.Context() - -     try: -	 data, result = c.verify(open(filename), open(sig_file)) -	 verified = True -     except gpg.errors.BadSignatures as e: -	 verified = False -	 print(e) - -     if verified is True: -	 for i in range(len(result.signatures)): -	     sign = result.signatures[i] -	     print("""Good signature from: -     {0} -     with key {1} -     made at {2} -     """.format(c.get_key(sign.fpr).uids[0].uid, -		sign.fpr, time.ctime(sign.timestamp))) -     else: -	 pass -   #+end_src - -   #+begin_src python -     import gpg -     import time - -     filename = "statement.txt" -     asc_file = "statement.txt.asc" - -     c = gpg.Context() - -     try: -	 data, result = c.verify(open(filename), open(asc_file)) -	 verified = True -     except gpg.errors.BadSignatures as e: -	 verified = False -	 print(e) - -     if verified is not None: -	 for i in range(len(result.signatures)): -	     sign = result.signatures[i] -	     print("""Good signature from: -     {0} -     with key {1} -     made at {2} -     """.format(c.get_key(sign.fpr).uids[0].uid, -		sign.fpr, time.ctime(sign.timestamp))) -     else: -	 pass -   #+end_src +Essentially there are two principal methods of verification of a +signature.  The first of these is for use with the normal or default +signing method and for clear-signed messages.  The second is for use +with files and data with detached signatures. + +The following example is intended for use with the default signing +method where the file was not ASCII armoured: + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +gpg_file = "statement.txt.gpg" + +c = gpg.Context() + +try: +    data, result = c.verify(open(gpg_file)) +    verified = True +except gpg.errors.BadSignatures as e: +    verified = False +    print(e) + +if verified is True: +    for i in range(len(result.signatures)): +        sign = result.signatures[i] +        print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, +           time.ctime(sign.timestamp))) +else: +    pass +#+END_SRC + +Whereas this next example, which is almost identical would work with +normal ASCII armoured files and with clear-signed files: + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +asc_file = "statement.txt.asc" + +c = gpg.Context() + +try: +    data, result = c.verify(open(asc_file)) +    verified = True +except gpg.errors.BadSignatures as e: +    verified = False +    print(e) + +if verified is True: +    for i in range(len(result.signatures)): +        sign = result.signatures[i] +        print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, +           time.ctime(sign.timestamp))) +else: +    pass +#+END_SRC + +In both of the previous examples it is also possible to compare the +original data that was signed against the signed data in =data= to see +if it matches with something like this: + +#+BEGIN_SRC python -i +with open(filename, "rb") as afile: +    text = afile.read() + +if text == data: +    print("Good signature.") +else: +    pass +#+END_SRC + +The following two examples, however, deal with detached signatures. +With his method of verification the data that was signed does not get +returned since it is already being explicitly referenced in the first +argument of =c.verify=.  So =data= is =None= and only the information +in =result= is available. + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +sig_file = "statement.txt.sig" + +c = gpg.Context() + +try: +    data, result = c.verify(open(filename), open(sig_file)) +    verified = True +except gpg.errors.BadSignatures as e: +    verified = False +    print(e) + +if verified is True: +    for i in range(len(result.signatures)): +        sign = result.signatures[i] +        print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, +           time.ctime(sign.timestamp))) +else: +    pass +#+END_SRC + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +asc_file = "statement.txt.asc" + +c = gpg.Context() + +try: +    data, result = c.verify(open(filename), open(asc_file)) +    verified = True +except gpg.errors.BadSignatures as e: +    verified = False +    print(e) + +if verified is True: +    for i in range(len(result.signatures)): +        sign = result.signatures[i] +        print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, +           time.ctime(sign.timestamp))) +else: +    pass +#+END_SRC  * Creating keys and subkeys @@ -1028,35 +1373,33 @@    :CUSTOM_ID: key-generation    :END: -  The one thing, aside from GnuPG itself, that GPGME depends on, of -  course, is the keys themselves.  So it is necessary to be able to -  generate them and modify them by adding subkeys, revoking or -  disabling them, sometimes deleting them and doing the same for user -  IDs. - -  In the following examples a key will be created for the world's -  greatest secret agent, Danger Mouse.  Since Danger Mouse is a secret -  agent he needs to be able to protect information to =SECRET= level -  clearance, so his keys will be 3072-bit keys. - -  The pre-configured =gpg.conf= file which sets cipher, digest and -  other preferences contains the following configuration parameters: - -  #+begin_src conf -    expert -    allow-freeform-uid -    allow-secret-key-import -    trust-model tofu+pgp -    tofu-default-policy unknown -    enable-large-rsa -    enable-dsa2 -    # cert-digest-algo SHA256 -    cert-digest-algo SHA512 -    default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed -    personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES -    personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 -    personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed -  #+end_src +The one thing, aside from GnuPG itself, that GPGME depends on, of +course, is the keys themselves.  So it is necessary to be able to +generate them and modify them by adding subkeys, revoking or disabling +them, sometimes deleting them and doing the same for user IDs. + +In the following examples a key will be created for the world's +greatest secret agent, Danger Mouse.  Since Danger Mouse is a secret +agent he needs to be able to protect information to =SECRET= level +clearance, so his keys will be 3072-bit keys. + +The pre-configured =gpg.conf= file which sets cipher, digest and other +preferences contains the following configuration parameters: + +#+BEGIN_SRC conf +  expert +  allow-freeform-uid +  allow-secret-key-import +  trust-model tofu+pgp +  tofu-default-policy unknown +  enable-large-rsa +  enable-dsa2 +  cert-digest-algo SHA512 +  default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed +  personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES +  personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 +  personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed +#+END_SRC  ** Primary key @@ -1064,100 +1407,98 @@     :CUSTOM_ID: keygen-primary     :END: -   Generating a primary key uses the =create_key= method in a Context. -   It contains multiple arguments and keyword arguments, including: -   =userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=, -   =certify=, =authenticate=, =passphrase= and =force=.  The defaults -   for all of those except =userid=, =algorithm=, =expires_in=, -   =expires= and =passphrase= is =False=.  The defaults for -   =algorithm= and =passphrase= is =None=.  The default for -   =expires_in= is =0=.  The default for =expires= is =True=.  There -   is no default for =userid=. - -   If =passphrase= is left as =None= then the key will not be -   generated with a passphrase, if =passphrase= is set to a string -   then that will be the passphrase and if =passphrase= is set to -   =True= then gpg-agent will launch pinentry to prompt for a -   passphrase.  For the sake of convenience, these examples will keep -   =passphrase= set to =None=. - -   #+begin_src python -     import gpg - -     c = gpg.Context() - -     c.home_dir = "~/.gnupg-dm" -     userid = "Danger Mouse <[email protected]>" - -     dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, -			  sign=True, certify=True) -   #+end_src - -   One thing to note here is the use of setting the =c.home_dir= -   parameter.  This enables generating the key or keys in a different -   location.  In this case to keep the new key data created for this -   example in a separate location rather than adding it to existing -   and active key store data.  As with the default directory, -   =~/.gnupg=, any temporary or separate directory needs the -   permissions set to only permit access by the directory owner.  On -   posix systems this means setting the directory permissions to 700. - -   The =temp-homedir-config.py= script in the HOWTO examples directory -   will create an alternative homedir with these configuration options -   already set and the correct directory and file permissions. - -   The successful generation of the key can be confirmed via the -   returned =GenkeyResult= object, which includes the following data: - -   #+begin_src python -     print(""" -     Fingerprint:  {0} -     Primary Key:  {1} -      Public Key:  {2} -      Secret Key:  {3} -	 Sub Key:  {4} -	User IDs:  {5} -     """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub, -		dmkey.uid)) -   #+end_src - -   Alternatively the information can be confirmed using the command -   line program: - -   #+begin_src shell -     bash-4.4$ gpg --homedir ~/.gnupg-dm -K -     ~/.gnupg-dm/pubring.kbx -     ---------------------- -     sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15] -	   177B7C25DB99745EE2EE13ED026D2F19E99E63AA -     uid           [ultimate] Danger Mouse <[email protected]> - -     bash-4.4$ -   #+end_src - -   As with generating keys manually, to preconfigure expanded -   preferences for the cipher, digest and compression algorithms, the -   =gpg.conf= file must contain those details in the home directory in -   which the new key is being generated.  I used a cut down version of -   my own =gpg.conf= file in order to be able to generate this: - -   #+begin_src shell -     bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit -     Secret key is available. - -     sec  rsa3072/026D2F19E99E63AA -	  created: 2018-03-15  expires: 2019-03-15  usage: SC -	  trust: ultimate      validity: ultimate -     [ultimate] (1). Danger Mouse <[email protected]> - -     [ultimate] (1). Danger Mouse <[email protected]> -	  Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES -	  Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1 -	  Compression: ZLIB, BZIP2, ZIP, Uncompressed -	  Features: MDC, Keyserver no-modify - -     bash-4.4$ -   #+end_src +Generating a primary key uses the =create_key= method in a Context. +It contains multiple arguments and keyword arguments, including: +=userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=, +=certify=, =authenticate=, =passphrase= and =force=.  The defaults for +all of those except =userid=, =algorithm=, =expires_in=, =expires= and +=passphrase= is =False=.  The defaults for =algorithm= and +=passphrase= is =None=.  The default for =expires_in= is =0=.  The +default for =expires= is =True=.  There is no default for =userid=. + +If =passphrase= is left as =None= then the key will not be generated +with a passphrase, if =passphrase= is set to a string then that will +be the passphrase and if =passphrase= is set to =True= then gpg-agent +will launch pinentry to prompt for a passphrase.  For the sake of +convenience, these examples will keep =passphrase= set to =None=. + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() + +c.home_dir = "~/.gnupg-dm" +userid = "Danger Mouse <[email protected]>" + +dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, +                     sign=True, certify=True) +#+END_SRC + +One thing to note here is the use of setting the =c.home_dir= +parameter.  This enables generating the key or keys in a different +location.  In this case to keep the new key data created for this +example in a separate location rather than adding it to existing and +active key store data.  As with the default directory, =~/.gnupg=, any +temporary or separate directory needs the permissions set to only +permit access by the directory owner.  On posix systems this means +setting the directory permissions to 700. + +The =temp-homedir-config.py= script in the HOWTO examples directory +will create an alternative homedir with these configuration options +already set and the correct directory and file permissions. + +The successful generation of the key can be confirmed via the returned +=GenkeyResult= object, which includes the following data: + +#+BEGIN_SRC python -i +print(""" + Fingerprint:  {0} + Primary Key:  {1} +  Public Key:  {2} +  Secret Key:  {3} + Sub Key:  {4} +User IDs:  {5} +""".format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub, +           dmkey.uid)) +#+END_SRC + +Alternatively the information can be confirmed using the command line +program: + +#+BEGIN_SRC shell +  bash-4.4$ gpg --homedir ~/.gnupg-dm -K +  ~/.gnupg-dm/pubring.kbx +  ---------------------- +  sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15] +	177B7C25DB99745EE2EE13ED026D2F19E99E63AA +  uid           [ultimate] Danger Mouse <[email protected]> + +  bash-4.4$ +#+END_SRC + +As with generating keys manually, to preconfigure expanded preferences +for the cipher, digest and compression algorithms, the =gpg.conf= file +must contain those details in the home directory in which the new key +is being generated.  I used a cut down version of my own =gpg.conf= +file in order to be able to generate this: + +#+BEGIN_SRC shell +  bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit +  Secret key is available. + +  sec  rsa3072/026D2F19E99E63AA +       created: 2018-03-15  expires: 2019-03-15  usage: SC +       trust: ultimate      validity: ultimate +  [ultimate] (1). Danger Mouse <[email protected]> + +  [ultimate] (1). Danger Mouse <[email protected]> +       Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES +       Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1 +       Compression: ZLIB, BZIP2, ZIP, Uncompressed +       Features: MDC, Keyserver no-modify + +  bash-4.4$ +#+END_SRC  ** Subkeys @@ -1165,55 +1506,55 @@     :CUSTOM_ID: keygen-subkeys     :END: -   Adding subkeys to a primary key is fairly similar to creating the -   primary key with the =create_subkey= method.  Most of the arguments -   are the same, but not quite all.  Instead of the =userid= argument -   there is now a =key= argument for selecting which primary key to -   add the subkey to. - -   In the following example an encryption subkey will be added to the -   primary key.  Since Danger Mouse is a security conscious secret -   agent, this subkey will only be valid for about six months, half -   the length of the primary key. - -   #+begin_src python -     import gpg - -     c = gpg.Context() -     c.home_dir = "~/.gnupg-dm" - -     key = c.get_key(dmkey.fpr, secret=True) -     dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000, -			     encrypt=True) -   #+end_src - -   As with the primary key, the results here can be checked with: - -   #+begin_src python -     print(""" -     Fingerprint:  {0} -     Primary Key:  {1} -      Public Key:  {2} -      Secret Key:  {3} -	 Sub Key:  {4} -	User IDs:  {5} -     """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub, -		dmsub.uid)) -   #+end_src - -   As well as on the command line with: - -   #+begin_src shell -     bash-4.4$ gpg --homedir ~/.gnupg-dm -K -     ~/.gnupg-dm/pubring.kbx -     ---------------------- -     sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15] -	   177B7C25DB99745EE2EE13ED026D2F19E99E63AA -     uid           [ultimate] Danger Mouse <[email protected]> -     ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13] - -     bash-4.4$ -   #+end_src +Adding subkeys to a primary key is fairly similar to creating the +primary key with the =create_subkey= method.  Most of the arguments +are the same, but not quite all.  Instead of the =userid= argument +there is now a =key= argument for selecting which primary key to add +the subkey to. + +In the following example an encryption subkey will be added to the +primary key.  Since Danger Mouse is a security conscious secret agent, +this subkey will only be valid for about six months, half the length +of the primary key. + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +key = c.get_key(dmkey.fpr, secret=True) +dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000, +                        encrypt=True) +#+END_SRC + +As with the primary key, the results here can be checked with: + +#+BEGIN_SRC python -i +print(""" + Fingerprint:  {0} + Primary Key:  {1} +  Public Key:  {2} +  Secret Key:  {3} + Sub Key:  {4} +User IDs:  {5} +""".format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub, +           dmsub.uid)) +#+END_SRC + +As well as on the command line with: + +#+BEGIN_SRC shell +  bash-4.4$ gpg --homedir ~/.gnupg-dm -K +  ~/.gnupg-dm/pubring.kbx +  ---------------------- +  sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15] +	177B7C25DB99745EE2EE13ED026D2F19E99E63AA +  uid           [ultimate] Danger Mouse <[email protected]> +  ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13] + +  bash-4.4$ +#+END_SRC  ** User IDs @@ -1227,38 +1568,38 @@      :CUSTOM_ID: keygen-uids-add      :END: -    By comparison to creating primary keys and subkeys, adding a new -    user ID to an existing key is much simpler.  The method used to do -    this is =key_add_uid= and the only arguments it takes are for the -    =key= and the new =uid=. +By comparison to creating primary keys and subkeys, adding a new user +ID to an existing key is much simpler.  The method used to do this is +=key_add_uid= and the only arguments it takes are for the =key= and +the new =uid=. -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      c = gpg.Context() -      c.home_dir = "~/.gnupg-dm" +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" -      dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -      key = c.get_key(dmfpr, secret=True) -      uid = "Danger Mouse <[email protected]>" +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <[email protected]>" -      c.key_add_uid(key, uid) -    #+end_src +c.key_add_uid(key, uid) +#+END_SRC -    Unsurprisingly the result of this is: +Unsurprisingly the result of this is: -    #+begin_src shell -      bash-4.4$ gpg --homedir ~/.gnupg-dm -K -      ~/.gnupg-dm/pubring.kbx -      ---------------------- -      sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15] -	    177B7C25DB99745EE2EE13ED026D2F19E99E63AA -      uid           [ultimate] Danger Mouse <[email protected]> -      uid           [ultimate] Danger Mouse <[email protected]> -      ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13] +#+BEGIN_SRC shell +  bash-4.4$ gpg --homedir ~/.gnupg-dm -K +  ~/.gnupg-dm/pubring.kbx +  ---------------------- +  sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15] +	177B7C25DB99745EE2EE13ED026D2F19E99E63AA +  uid           [ultimate] Danger Mouse <[email protected]> +  uid           [ultimate] Danger Mouse <[email protected]> +  ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13] -      bash-4.4$ -    #+end_src +  bash-4.4$ +#+END_SRC  *** Revokinging User IDs @@ -1266,21 +1607,21 @@      :CUSTOM_ID: keygen-uids-revoke      :END: -    Revoking a user ID is a fairly similar process, except that it -    uses the =key_revoke_uid= method. +Revoking a user ID is a fairly similar process, except that it uses +the =key_revoke_uid= method. -    #+begin_src python -      import gpg +#+BEGIN_SRC python -i +import gpg -      c = gpg.Context() -      c.home_dir = "~/.gnupg-dm" +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" -      dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -      key = c.get_key(dmfpr, secret=True) -      uid = "Danger Mouse <[email protected]>" +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <[email protected]>" -      c.key_revoke_uid(key, uid) -    #+end_src +c.key_revoke_uid(key, uid) +#+END_SRC  ** Key certification @@ -1288,37 +1629,37 @@     :CUSTOM_ID: key-sign     :END: -   Since key certification is more frequently referred to as key -   signing, the method used to perform this function is =key_sign=. +Since key certification is more frequently referred to as key signing, +the method used to perform this function is =key_sign=. -   The =key_sign= method takes four arguments: =key=, =uids=, -   =expires_in= and =local=.  The default value of =uids= is =None= -   and which results in all user IDs being selected.  The default -   value of both =expires_in= and =local= is =False=; which results in -   the signature never expiring and being able to be exported. +The =key_sign= method takes four arguments: =key=, =uids=, +=expires_in= and =local=.  The default value of =uids= is =None= and +which results in all user IDs being selected.  The default value of +both =expires_in= and =local= is =False=; which results in the +signature never expiring and being able to be exported. -   The =key= is the key being signed rather than the key doing the -   signing.  To change the key doing the signing refer to the signing -   key selection above for signing messages and files. +The =key= is the key being signed rather than the key doing the +signing.  To change the key doing the signing refer to the signing key +selection above for signing messages and files. -   If the =uids= value is not =None= then it must either be a string -   to match a single user ID or a list of strings to match multiple -   user IDs.  In this case the matching of those strings must be -   precise and it is case sensitive. +If the =uids= value is not =None= then it must either be a string to +match a single user ID or a list of strings to match multiple user +IDs.  In this case the matching of those strings must be precise and +it is case sensitive. -   To sign Danger Mouse's key for just the initial user ID with a -   signature which will last a little over a month, do this: +To sign Danger Mouse's key for just the initial user ID with a +signature which will last a little over a month, do this: -   #+begin_src python -     import gpg +#+BEGIN_SRC python -i +import gpg -     c = gpg.Context() -     uid = "Danger Mouse <[email protected]>" +c = gpg.Context() +uid = "Danger Mouse <[email protected]>" -     dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -     key = c.get_key(dmfpr, secret=True) -     c.key_sign(key, uids=uid, expires_in=2764800) -   #+end_src +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +c.key_sign(key, uids=uid, expires_in=2764800) +#+END_SRC  * Miscellaneous work-arounds @@ -1332,44 +1673,52 @@     :CUSTOM_ID: group-lines     :END: -   There is not yet an easy way to access groups configured in the -   gpg.conf file from within GPGME.  As a consequence these central -   groupings of keys cannot be shared amongst multiple programs, such -   as MUAs readily. +There is not yet an easy way to access groups configured in the +gpg.conf file from within GPGME.  As a consequence these central +groupings of keys cannot be shared amongst multiple programs, such as +MUAs readily. + +The following code, however, provides a work-around for obtaining this +information in Python. -   The following code, however, provides a work-around for obtaining -   this information in Python. +#+BEGIN_SRC python -i +import subprocess -   #+begin_src python -     import subprocess +lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() -     lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() +for i in range(len(lines)): +    if lines[i].startswith("group") is True: +        line = lines[i] +    else: +        pass -     for i in range(len(lines)): -	 if lines[i].startswith("group") is True: -	     line = lines[i] -	 else: -	     pass +groups = line.split(":")[-1].replace('"', '').split(',') -     groups = line.split(":")[-1].replace('"', '').split(',') +group_lines = [] +group_lists = [] -     group_lines = groups -     for i in range(len(group_lines)): -	 group_lines[i] = group_lines[i].split("=") +for i in range(len(groups)): +    group_lines.append(groups[i].split("=")) +    group_lists.append(groups[i].split("=")) -     group_lists = group_lines -     for i in range(len(group_lists)): -	 group_lists[i][1] = group_lists[i][1].split() -   #+end_src +for i in range(len(group_lists)): +    group_lists[i][1] = group_lists[i][1].split() +#+END_SRC -   The result of that code is that =group_lines= is a list of lists -   where =group_lines[i][0]= is the name of the group and -   =group_lines[i][1]= is the key IDs of the group as a string. +The result of that code is that =group_lines= is a list of lists where +=group_lines[i][0]= is the name of the group and =group_lines[i][1]= +is the key IDs of the group as a string. -   The =group_lists= result is very similar in that it is a list of -   lists.  The first part, =group_lists[i][0]= matches -   =group_lines[i][0]= as the name of the group, but -   =group_lists[i][1]= is the key IDs of the group as a string. +The =group_lists= result is very similar in that it is a list of +lists.  The first part, =group_lists[i][0]= matches +=group_lines[i][0]= as the name of the group, but =group_lists[i][1]= +is the key IDs of the group as a string. + +A demonstration of using the =groups.py= module is also available in +the form of the executable =mutt-groups.py= script.  This second +script reads all the group entries in a user's =gpg.conf= file and +converts them into crypt-hooks suitable for use with the Mutt and +Neomutt mail clients.  * Copyright and Licensing @@ -1383,7 +1732,7 @@     :CUSTOM_ID: copyright     :END: -   Copyright © The GnuPG Project, 2018. +Copyright © The GnuPG Project, 2018.  ** License GPL compatible @@ -1391,14 +1740,14 @@     :CUSTOM_ID: license     :END: -   This file is free software; as a special exception the author gives -   unlimited permission to copy and/or distribute it, with or without -   modifications, as long as this notice is preserved. +This file is free software; as a special exception the author gives +unlimited permission to copy and/or distribute it, with or without +modifications, as long as this notice is preserved. -   This file is distributed in the hope that it will be useful, but -   WITHOUT ANY WARRANTY, to the extent permitted by law; without even -   the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -   PURPOSE. +This file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE.  * Footnotes @@ -1411,3 +1760,7 @@  keyservers for "gnupg.org" produces over 400 results, the majority of  which aren't actually at the gnupg.org domain, but just included a  comment regarding the project in their key somewhere. + +[fn:4] As Python 3.7 is a very recent release, it is not given +priority over 3.6 yet, but will probably be prioritised by the release +of Python 3.7.2. diff --git a/lang/python/examples/assuan.py b/lang/python/examples/assuan.py index dd42ad40..6784c9eb 100644 --- a/lang/python/examples/assuan.py +++ b/lang/python/examples/assuan.py @@ -14,14 +14,14 @@  #  # You should have received a copy of the GNU General Public License  # along with this program; if not, see <http://www.gnu.org/licenses/>. -  """Demonstrate the use of the Assuan protocol engine"""  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import gpg +del absolute_import, print_function, unicode_literals +  with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:      # Invoke the pinentry to get a confirmation.      err = c.assuan_transact(['GET_CONFIRMATION', 'Hello there']) diff --git a/lang/python/examples/decryption-filter.py b/lang/python/examples/decryption-filter.py index 987dfd13..4d99330b 100644 --- a/lang/python/examples/decryption-filter.py +++ b/lang/python/examples/decryption-filter.py @@ -1,6 +1,6 @@  #!/usr/bin/env python  # -# Copyright (C) 2016 g10 Code GmbH +# Copyright (C) 2016, 2018 g10 Code GmbH  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License as published by @@ -14,7 +14,6 @@  #  # You should have received a copy of the GNU General Public License  # along with this program; if not, see <http://www.gnu.org/licenses/>. -  """A decryption filter  This demonstrates decryption using gpg3 in three lines of code.  To @@ -25,8 +24,10 @@ be used like this:  """  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import sys  import gpg + +del absolute_import, print_function, unicode_literals +  gpg.Context().decrypt(sys.stdin, sink=sys.stdout) diff --git a/lang/python/examples/delkey.py b/lang/python/examples/delkey.py index 12510f3e..30b3145a 100755 --- a/lang/python/examples/delkey.py +++ b/lang/python/examples/delkey.py @@ -20,10 +20,11 @@  # It deletes keys for [email protected] generated by genkey.py script  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import gpg +del absolute_import, print_function, unicode_literals +  with gpg.Context() as c:      # Note: We must not modify the key store during iteration,      # therefore, we explicitly make a list. diff --git a/lang/python/examples/exportimport.py b/lang/python/examples/exportimport.py index d84a01c3..36ced579 100755 --- a/lang/python/examples/exportimport.py +++ b/lang/python/examples/exportimport.py @@ -20,12 +20,13 @@  # It uses keys for [email protected] generated by genkey.py script  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import sys  import os  import gpg +del absolute_import, print_function, unicode_literals +  user = "[email protected]"  with gpg.Context(armor=True) as c, gpg.Data() as expkey: diff --git a/lang/python/examples/genkey.py b/lang/python/examples/genkey.py index a043500e..710a530a 100755 --- a/lang/python/examples/genkey.py +++ b/lang/python/examples/genkey.py @@ -18,10 +18,11 @@  # 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 +del absolute_import, print_function, unicode_literals +  # This is the example from the GPGME manual.  parms = """<GnupgKeyParms format="internal"> diff --git a/lang/python/examples/howto/decrypt-file.py b/lang/python/examples/howto/decrypt-file.py index b38acc79..2fe37f27 100755 --- a/lang/python/examples/howto/decrypt-file.py +++ b/lang/python/examples/howto/decrypt-file.py @@ -32,10 +32,10 @@ if len(sys.argv) == 3:      newfile = sys.argv[2]  elif len(sys.argv) == 2:      ciphertext = sys.argv[1] -    newfile = input("Enter path and filename of file to save decrypted data to: ") +    newfile = input("Enter path and filename to save decrypted data to: ")  else:      ciphertext = input("Enter path and filename of encrypted file: ") -    newfile = input("Enter path and filename of file to save decrypted data to: ") +    newfile = input("Enter path and filename to save decrypted data to: ")  with open(ciphertext, "rb") as cfile:      try: diff --git a/lang/python/examples/howto/encrypt-file.py b/lang/python/examples/howto/encrypt-file.py index ad4e1cef..7c84a6f9 100755 --- a/lang/python/examples/howto/encrypt-file.py +++ b/lang/python/examples/howto/encrypt-file.py @@ -3,6 +3,9 @@  from __future__ import absolute_import, division, unicode_literals +import gpg +import sys +  # Copyright (C) 2018 Ben McGinnes <[email protected]>  #  # This program is free software; you can redistribute it and/or modify it under @@ -24,9 +27,6 @@ from __future__ import absolute_import, division, unicode_literals  # Lesser General Public along with this program; if not, see  # <http://www.gnu.org/licenses/>. -import gpg -import sys -  """  Encrypts a file to a specified key.  If entering both the key and the filename  on the command line, the key must be entered first. @@ -55,7 +55,7 @@ with open(filename, "rb") as f:  with gpg.Context(armor=True) as ca:      try:          ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey, -                                                    sign=False) +                                                     sign=False)          with open("{0}.asc".format(filename), "wb") as fa:              fa.write(ciphertext)      except gpg.errors.InvalidRecipients as e: @@ -64,7 +64,7 @@ with gpg.Context(armor=True) as ca:  with gpg.Context() as cg:      try:          ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey, -                                                    sign=False) +                                                     sign=False)          with open("{0}.gpg".format(filename), "wb") as fg:              fg.write(ciphertext)      except gpg.errors.InvalidRecipients as e: diff --git a/lang/python/examples/howto/encrypt-sign-file.py b/lang/python/examples/howto/encrypt-sign-file.py index 41aaac86..a08176b7 100755 --- a/lang/python/examples/howto/encrypt-sign-file.py +++ b/lang/python/examples/howto/encrypt-sign-file.py @@ -3,6 +3,9 @@  from __future__ import absolute_import, division, unicode_literals +import gpg +import sys +  # Copyright (C) 2018 Ben McGinnes <[email protected]>  #  # This program is free software; you can redistribute it and/or modify it under @@ -24,9 +27,6 @@ from __future__ import absolute_import, division, unicode_literals  # Lesser General Public along with this program; if not, see  # <http://www.gnu.org/licenses/>. -import gpg -import sys -  """  Signs and encrypts a file to a specified key.  If entering both the key and the  filename on the command line, the key must be entered first. @@ -58,13 +58,13 @@ with open(filename, "rb") as f:  with gpg.Context(armor=True) as ca:      ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey,                                                   always_trust=True, -                                                     add_encrypt_to=True) +                                                 add_encrypt_to=True)      with open("{0}.asc".format(filename), "wb") as fa:          fa.write(ciphertext)  with gpg.Context() as cg:      ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey,                                                   always_trust=True, -                                                     add_encrypt_to=True) +                                                 add_encrypt_to=True)      with open("{0}.gpg".format(filename), "wb") as fg:          fg.write(ciphertext) diff --git a/lang/python/examples/howto/encrypt-to-group-gullible.py b/lang/python/examples/howto/encrypt-to-group-gullible.py new file mode 100755 index 00000000..c96e8294 --- /dev/null +++ b/lang/python/examples/howto/encrypt-to-group-gullible.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import sys +from groups import group_lists + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +""" +Uses the groups module to encrypt to multiple recipients. + +""" + +c = gpg.Context(armor=True) + +if len(sys.argv) > 3: +    group_id = sys.argv[1] +    filepath = sys.argv[2:] +elif len(sys.argv) == 3: +    group_id = sys.argv[1] +    filepath = sys.argv[2] +elif len(sys.argv) == 2: +    group_id = sys.argv[1] +    filepath = input("Enter the filename to encrypt: ") +else: +    group_id = input("Enter the group name to encrypt to: ") +    filepath = input("Enter the filename to encrypt: ") + +with open(filepath, "rb") as f: +    text = f.read() + +for i in range(len(group_lists)): +    if group_lists[i][0] == group_id: +        klist = group_lists[i][1] +    else: +        klist = None + +logrus = [] + +if klist is not None: +    for i in range(len(klist)): +        apattern = list(c.keylist(pattern=klist[i], secret=False)) +        if apattern[0].can_encrypt == 1: +            logrus.append(apattern[0]) +        else: +            pass +    try: +        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, +                                                    add_encrypt_to=True) +    except: +        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, +                                                    add_encrypt_to=True, +                                                    always_trust=True) +    with open("{0}.asc".format(filepath), "wb") as f: +        f.write(ciphertext) +else: +    pass + +# EOF diff --git a/lang/python/examples/howto/encrypt-to-group-trustno1.py b/lang/python/examples/howto/encrypt-to-group-trustno1.py new file mode 100755 index 00000000..da0376b5 --- /dev/null +++ b/lang/python/examples/howto/encrypt-to-group-trustno1.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import sys +from groups import group_lists + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +""" +Uses the groups module to encrypt to multiple recipients. + +""" + +c = gpg.Context(armor=True) + +if len(sys.argv) > 3: +    group_id = sys.argv[1] +    filepath = sys.argv[2:] +elif len(sys.argv) == 3: +    group_id = sys.argv[1] +    filepath = sys.argv[2] +elif len(sys.argv) == 2: +    group_id = sys.argv[1] +    filepath = input("Enter the filename to encrypt: ") +else: +    group_id = input("Enter the group name to encrypt to: ") +    filepath = input("Enter the filename to encrypt: ") + +with open(filepath, "rb") as f: +    text = f.read() + +for i in range(len(group_lists)): +    if group_lists[i][0] == group_id: +        klist = group_lists[i][1] +    else: +        klist = None + +logrus = [] + +if klist is not None: +    for i in range(len(klist)): +        apattern = list(c.keylist(pattern=klist[i], secret=False)) +        if apattern[0].can_encrypt == 1: +            logrus.append(apattern[0]) +        else: +            pass +    try: +        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, +                                                    add_encrypt_to=True) +    except gpg.errors.InvalidRecipients as e: +        for i in range(len(e.recipients)): +            for n in range(len(logrus)): +                if logrus[n].fpr == e.recipients[i].fpr: +                    logrus.remove(logrus[n]) +                else: +                    pass +        try: +            ciphertext, result, sign_result = c.encrypt(text, +                                                        recipients=logrus, +                                                        add_encrypt_to=True) +        except: +            pass +    with open("{0}.asc".format(filepath), "wb") as f: +        f.write(ciphertext) +else: +    pass + +# EOF diff --git a/lang/python/examples/howto/encrypt-to-group.py b/lang/python/examples/howto/encrypt-to-group.py new file mode 100755 index 00000000..d4cb0745 --- /dev/null +++ b/lang/python/examples/howto/encrypt-to-group.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import sys +from groups import group_lists + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +""" +Uses the groups module to encrypt to multiple recipients. + +""" + +c = gpg.Context(armor=True) + +if len(sys.argv) > 3: +    group_id = sys.argv[1] +    filepath = sys.argv[2:] +elif len(sys.argv) == 3: +    group_id = sys.argv[1] +    filepath = sys.argv[2] +elif len(sys.argv) == 2: +    group_id = sys.argv[1] +    filepath = input("Enter the filename to encrypt: ") +else: +    group_id = input("Enter the group name to encrypt to: ") +    filepath = input("Enter the filename to encrypt: ") + +with open(filepath, "rb") as f: +    text = f.read() + +for i in range(len(group_lists)): +    if group_lists[i][0] == group_id: +        klist = group_lists[i][1] +    else: +        klist = None + +logrus = [] + +if klist is not None: +    for i in range(len(klist)): +        apattern = list(c.keylist(pattern=klist[i], secret=False)) +        if apattern[0].can_encrypt == 1: +            logrus.append(apattern[0]) +        else: +            pass +    try: +        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, +                                                    add_encrypt_to=True) +    except gpg.errors.InvalidRecipients as e: +        for i in range(len(e.recipients)): +            for n in range(len(logrus)): +                if logrus[n].fpr == e.recipients[i].fpr: +                    logrus.remove(logrus[n]) +                else: +                    pass +        try: +            ciphertext, result, sign_result = c.encrypt(text, +                                                        recipients=logrus, +                                                        add_encrypt_to=True, +                                                        always_trust=True) +        except: +            pass +    with open("{0}.asc".format(filepath), "wb") as f: +        f.write(ciphertext) +else: +    pass + +# EOF diff --git a/lang/python/examples/howto/export-key.py b/lang/python/examples/howto/export-key.py new file mode 100755 index 00000000..913bfce7 --- /dev/null +++ b/lang/python/examples/howto/export-key.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import os.path +import sys + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script exports one or more public keys. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the path and filename to save the key(s) to: ") +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +try: +    result = c.key_export(pattern=logrus) +except: +    result = c.key_export(pattern=None) + +if result is not None: +    with open(keyfile, "wb") as f: +        f.write(result) +else: +    pass diff --git a/lang/python/examples/howto/export-minimised-key.py b/lang/python/examples/howto/export-minimised-key.py new file mode 100755 index 00000000..3889adcd --- /dev/null +++ b/lang/python/examples/howto/export-minimised-key.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import os.path +import sys + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script exports one or more public keys in minimised form. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the path and filename to save the key(s) to: ") +    logrus = input("Enter the UID matching the key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +try: +    result = c.key_export_minimal(pattern=logrus) +except: +    result = c.key_export_minimal(pattern=None) + +if result is not None: +    with open(keyfile, "wb") as f: +        f.write(result) +else: +    pass diff --git a/lang/python/examples/howto/export-secret-key.py b/lang/python/examples/howto/export-secret-key.py new file mode 100755 index 00000000..e9c53fe5 --- /dev/null +++ b/lang/python/examples/howto/export-secret-key.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import os +import os.path +import sys + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script exports one or more secret keys. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the path and filename to save the secret key to: ") +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +try: +    result = c.key_export_secret(pattern=logrus) +except: +    result = c.key_export_secret(pattern=None) + +if result is not None: +    with open(keyfile, "wb") as f: +        f.write(result) +    os.chmod(keyfile, 0o600) +else: +    pass diff --git a/lang/python/examples/howto/export-secret-keys.py b/lang/python/examples/howto/export-secret-keys.py new file mode 100755 index 00000000..f0a791ef --- /dev/null +++ b/lang/python/examples/howto/export-secret-keys.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import os +import os.path +import subprocess +import sys + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script exports one or more secret keys as both ASCII armored and binary +file formats, saved in files within the user's GPG home directory. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +if sys.platform == "win32": +    gpgconfcmd = "gpgconf.exe --list-dirs homedir" +else: +    gpgconfcmd = "gpgconf --list-dirs homedir" + +a = gpg.Context(armor=True) +b = gpg.Context() +c = gpg.Context() + +if len(sys.argv) >= 4: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = sys.argv[3] +elif len(sys.argv) == 3: +    keyfile = sys.argv[1] +    logrus = sys.argv[2] +    homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the filename to save the secret key to: ") +    logrus = input("Enter the UID matching the secret key(s) to export: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +if c.home_dir is not None: +    if c.home_dir.endswith("/"): +        gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile) +        ascfile = "{0}{1}.asc".format(c.home_dir, keyfile) +    else: +        gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile) +        ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile) +else: +    if os.path.exists(os.environ["GNUPGHOME"]) is True: +        hd = os.environ["GNUPGHOME"] +    else: +        hd = subprocess.getoutput(gpgconfcmd) +    gpgfile = "{0}/{1}.gpg".format(hd, keyfile) +    ascfile = "{0}/{1}.asc".format(hd, keyfile) + +try: +    a_result = a.key_export_secret(pattern=logrus) +    b_result = b.key_export_secret(pattern=logrus) +except: +    a_result = a.key_export_secret(pattern=None) +    b_result = b.key_export_secret(pattern=None) + +if a_result is not None: +    with open(ascfile, "wb") as f: +        f.write(a_result) +    os.chmod(ascfile, 0o600) +else: +    pass + +if b_result is not None: +    with open(gpgfile, "wb") as f: +        f.write(b_result) +    os.chmod(gpgfile, 0o600) +else: +    pass diff --git a/lang/python/examples/howto/groups.py b/lang/python/examples/howto/groups.py index 5e7fdf60..b8317b69 100644 --- a/lang/python/examples/howto/groups.py +++ b/lang/python/examples/howto/groups.py @@ -24,6 +24,7 @@ from __future__ import absolute_import, division, unicode_literals  # <http://www.gnu.org/licenses/>.  import subprocess +import sys  """  Intended for use with other scripts. @@ -31,7 +32,12 @@ Intended for use with other scripts.  Usage: from groups import group_lists  """ -lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() +if sys.platform == "win32": +    gpgconfcmd = "gpgconf.exe --list-options gpg" +else: +    gpgconfcmd = "gpgconf --list-options gpg" + +lines = subprocess.getoutput(gpgconfcmd).splitlines()  for i in range(len(lines)):      if lines[i].startswith("group") is True: @@ -41,10 +47,12 @@ for i in range(len(lines)):  groups = line.split(":")[-1].replace('"', '').split(',') -group_lines = groups -for i in range(len(group_lines)): -    group_lines[i] = group_lines[i].split("=") +group_lines = [] +group_lists = [] + +for i in range(len(groups)): +    group_lines.append(groups[i].split("=")) +    group_lists.append(groups[i].split("=")) -group_lists = group_lines  for i in range(len(group_lists)):      group_lists[i][1] = group_lists[i][1].split() diff --git a/lang/python/examples/howto/import-key.py b/lang/python/examples/howto/import-key.py new file mode 100755 index 00000000..25913785 --- /dev/null +++ b/lang/python/examples/howto/import-key.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import os.path +import sys + +del absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script imports one or more public keys from a single file. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 3: +    keyfile = sys.argv[1] +    homedir = sys.argv[2] +elif len(sys.argv) == 2: +    keyfile = sys.argv[1] +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyfile = input("Enter the path and filename to import the key(s) from: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +if os.path.isfile(keyfile) is True: +    with open(keyfile, "rb") as f: +        incoming = f.read() +    result = c.key_import(incoming) +else: +    result = None + +if result is not None and hasattr(result, "considered") is False: +    print(result) +elif result is not None and hasattr(result, "considered") is True: +    num_keys = len(result.imports) +    new_revs = result.new_revocations +    new_sigs = result.new_signatures +    new_subs = result.new_sub_keys +    new_uids = result.new_user_ids +    new_scrt = result.secret_imported +    nochange = result.unchanged +    print(""" +The total number of keys considered for import was:  {0} + +   Number of keys revoked:  {1} + Number of new signatures:  {2} +    Number of new subkeys:  {3} +   Number of new user IDs:  {4} +Number of new secret keys:  {5} + Number of unchanged keys:  {6} + +The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, +           nochange)) +    for i in range(num_keys): +        print(result.imports[i].fpr) +    print("") +elif result is None: +    print("You must specify a key file to import.") diff --git a/lang/python/examples/howto/import-keys.py b/lang/python/examples/howto/import-keys.py new file mode 100755 index 00000000..bdc15a68 --- /dev/null +++ b/lang/python/examples/howto/import-keys.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import os.path +import requests + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script imports one or more public keys from the SKS keyservers. +""") + +c = gpg.Context() +url = "https://sks-keyservers.net/pks/lookup" +pattern = input("Enter the pattern to search for key or user IDs: ") +payload = {"op": "get", "search": pattern} + +r = requests.get(url, verify=True, params=payload) +result = c.key_import(r.content) + +if result is not None and hasattr(result, "considered") is False: +    print(result) +elif result is not None and hasattr(result, "considered") is True: +    num_keys = len(result.imports) +    new_revs = result.new_revocations +    new_sigs = result.new_signatures +    new_subs = result.new_sub_keys +    new_uids = result.new_user_ids +    new_scrt = result.secret_imported +    nochange = result.unchanged +    print(""" +The total number of keys considered for import was:  {0} + +   Number of keys revoked:  {1} + Number of new signatures:  {2} +    Number of new subkeys:  {3} +   Number of new user IDs:  {4} +Number of new secret keys:  {5} + Number of unchanged keys:  {6} + +The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, +           nochange)) +    for i in range(num_keys): +        print(result.imports[i].fpr) +    print("") +else: +    pass diff --git a/lang/python/examples/howto/mutt-groups.py b/lang/python/examples/howto/mutt-groups.py new file mode 100755 index 00000000..c0b515a7 --- /dev/null +++ b/lang/python/examples/howto/mutt-groups.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +import sys +from groups import group_lists + +""" +Uses the groups module to generate Mutt crypt-hooks from gpg.conf. + +""" + +if len(sys.argv) >= 2: +    hook_file = sys.argv[1] +else: +    hook_file = input("Enter the filename to save the crypt-hooks in: ") + +with open(hook_file, "w") as f: +    f.write("""# Change settings based upon message recipient +# +#	send-hook [!]<pattern> <command> +# +# <command> is executed when sending mail to an address matching <pattern> +# +# crypt-hook regexp key-id +#     The crypt-hook command provides a method by which you can +#     specify the ID of the public key to be used when encrypting +#     messages to a certain recipient.  The meaning of "key ID" is to +#     be taken broadly: This can be a different e-mail address, a +#     numerical key ID, or even just an arbitrary search string.  You +#     may use multiple crypt-hooks with the same regexp; multiple +#     matching crypt-hooks result in the use of multiple key-ids for a +#     recipient. +""") + +for n in range(len(group_lists)): +    rule = group_lists[n][0].replace(".", "\\\\.") +    with open(hook_file, "a") as f: +        f.write("\n") +        f.write("# {0}\n".format(group_lists[n][0])) +        for i in range(len(group_lists[n][1])): +            f.write("crypt-hook {0} {1}\n".format(rule, group_lists[n][1][i])) diff --git a/lang/python/examples/howto/pmkey-import-alt.py b/lang/python/examples/howto/pmkey-import-alt.py new file mode 100755 index 00000000..e9521b7f --- /dev/null +++ b/lang/python/examples/howto/pmkey-import-alt.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import os.path +import requests +import sys + +del absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it.  Optionally enables specifying a different GnuPG home directory. +""") + +c = gpg.Context(armor=True) +url = "https://api.protonmail.ch/pks/lookup" +ksearch = [] + +if len(sys.argv) >= 3: +    keyterm = sys.argv[1] +    homedir = sys.argv[2] +elif len(sys.argv) == 2: +    keyterm = sys.argv[1] +    homedir = input("Enter the GPG configuration directory path (optional): ") +else: +    keyterm = input("Enter the key ID, UID or search string: ") +    homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): +    if os.path.exists(os.path.expanduser(homedir)) is True: +        c.home_dir = os.path.expanduser(homedir) +    else: +        pass +elif os.path.exists(homedir) is True: +    c.home_dir = homedir +else: +    pass + +if keyterm.count("@") == 2 and keyterm.startswith("@") is True: +    ksearch.append(keyterm[1:]) +    ksearch.append(keyterm[1:]) +    ksearch.append(keyterm[1:]) +elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: +    ksearch.append("{0}@protonmail.com".format(keyterm[1:])) +    ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) +    ksearch.append("{0}@pm.me".format(keyterm[1:])) +elif keyterm.count("@") == 0: +    ksearch.append("{0}@protonmail.com".format(keyterm)) +    ksearch.append("{0}@protonmail.ch".format(keyterm)) +    ksearch.append("{0}@pm.me".format(keyterm)) +elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: +    uidlist = keyterm.split("@") +    for uid in uidlist: +        ksearch.append("{0}@protonmail.com".format(uid)) +        ksearch.append("{0}@protonmail.ch".format(uid)) +        ksearch.append("{0}@pm.me".format(uid)) +elif keyterm.count("@") > 2: +    uidlist = keyterm.split("@") +    for uid in uidlist: +        ksearch.append("{0}@protonmail.com".format(uid)) +        ksearch.append("{0}@protonmail.ch".format(uid)) +        ksearch.append("{0}@pm.me".format(uid)) +else: +    ksearch.append(keyterm) + +for k in ksearch: +    payload = {"op": "get", "search": k} +    try: +        r = requests.get(url, verify=True, params=payload) +        if r.ok is True: +            result = c.key_import(r.content) +        elif r.ok is False: +            result = r.content +    except Exception as e: +        result = None + +    if result is not None and hasattr(result, "considered") is False: +        print("{0} for {1}".format(result.decode(), k)) +    elif result is not None and hasattr(result, "considered") is True: +        num_keys = len(result.imports) +        new_revs = result.new_revocations +        new_sigs = result.new_signatures +        new_subs = result.new_sub_keys +        new_uids = result.new_user_ids +        new_scrt = result.secret_imported +        nochange = result.unchanged +        print(""" +The total number of keys considered for import was:  {0} + +With UIDs wholely or partially matching the following string: + +        {1} + +   Number of keys revoked:  {2} + Number of new signatures:  {3} +    Number of new subkeys:  {4} +   Number of new user IDs:  {5} +Number of new secret keys:  {6} + Number of unchanged keys:  {7} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, +           nochange)) +        for i in range(num_keys): +            print(result.imports[i].fpr) +        print("") +    elif result is None: +        print(e) diff --git a/lang/python/examples/howto/pmkey-import.py b/lang/python/examples/howto/pmkey-import.py new file mode 100755 index 00000000..edbd18e8 --- /dev/null +++ b/lang/python/examples/howto/pmkey-import.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import requests +import sys + +del absolute_import, division, unicode_literals + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. +""") + +c = gpg.Context(armor=True) +url = "https://api.protonmail.ch/pks/lookup" +ksearch = [] + +if len(sys.argv) >= 2: +    keyterm = sys.argv[1] +else: +    keyterm = input("Enter the key ID, UID or search string: ") + +if keyterm.count("@") == 2 and keyterm.startswith("@") is True: +    ksearch.append(keyterm[1:]) +    ksearch.append(keyterm[1:]) +    ksearch.append(keyterm[1:]) +elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: +    ksearch.append("{0}@protonmail.com".format(keyterm[1:])) +    ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) +    ksearch.append("{0}@pm.me".format(keyterm[1:])) +elif keyterm.count("@") == 0: +    ksearch.append("{0}@protonmail.com".format(keyterm)) +    ksearch.append("{0}@protonmail.ch".format(keyterm)) +    ksearch.append("{0}@pm.me".format(keyterm)) +elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: +    uidlist = keyterm.split("@") +    for uid in uidlist: +        ksearch.append("{0}@protonmail.com".format(uid)) +        ksearch.append("{0}@protonmail.ch".format(uid)) +        ksearch.append("{0}@pm.me".format(uid)) +elif keyterm.count("@") > 2: +    uidlist = keyterm.split("@") +    for uid in uidlist: +        ksearch.append("{0}@protonmail.com".format(uid)) +        ksearch.append("{0}@protonmail.ch".format(uid)) +        ksearch.append("{0}@pm.me".format(uid)) +else: +    ksearch.append(keyterm) + +for k in ksearch: +    payload = {"op": "get", "search": k} +    try: +        r = requests.get(url, verify=True, params=payload) +        if r.ok is True: +            result = c.key_import(r.content) +        elif r.ok is False: +            result = r.content +    except Exception as e: +        result = None + +    if result is not None and hasattr(result, "considered") is False: +        print("{0} for {1}".format(result.decode(), k)) +    elif result is not None and hasattr(result, "considered") is True: +        num_keys = len(result.imports) +        new_revs = result.new_revocations +        new_sigs = result.new_signatures +        new_subs = result.new_sub_keys +        new_uids = result.new_user_ids +        new_scrt = result.secret_imported +        nochange = result.unchanged +        print(""" +The total number of keys considered for import was:  {0} + +With UIDs wholely or partially matching the following string: + +        {1} + +   Number of keys revoked:  {2} + Number of new signatures:  {3} +    Number of new subkeys:  {4} +   Number of new user IDs:  {5} +Number of new secret keys:  {6} + Number of unchanged keys:  {7} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, +           nochange)) +        for i in range(num_keys): +            print(result.imports[i].fpr) +        print("") +    elif result is None: +        print(e) diff --git a/lang/python/examples/howto/symcrypt-file.py b/lang/python/examples/howto/symcrypt-file.py new file mode 100755 index 00000000..785a4d04 --- /dev/null +++ b/lang/python/examples/howto/symcrypt-file.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, unicode_literals + +import gpg +import sys + +# Copyright (C) 2018 Ben McGinnes <[email protected]> +# +# This program 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. +# +# This program 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. +# +# This program 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 and the GNU +# Lesser General Public Licensefor more details. +# +# You should have received a copy of the GNU General Public License and the GNU +# Lesser General Public along with this program; if not, see +# <http://www.gnu.org/licenses/>. + +""" +Symmetrically encrypts a file.  Passphrase will be prompted for via Pinentry. + +Will produce both an ASCII armoured and GPG binary format copy of the encrypted +file. +""" + +if len(sys.argv) > 2: +    filename = " ".join(sys.argv[1:]) +elif len(sys.argv) == 2: +    filename = sys.argv[1] +else: +    filename = input("Enter the path and filename to encrypt: ") + +with open(filename, "rb") as f: +    text = f.read() + +with gpg.Context(armor=True) as ca: +    try: +        ciphertext, result, sign_result = ca.encrypt(text, passphrase=None, +                                                     sign=False) +        with open("{0}.asc".format(filename), "wb") as fa: +            fa.write(ciphertext) +    except gpg.errors.GPGMEError as e: +        print(e) + +with gpg.Context() as cg: +    try: +        ciphertext, result, sign_result = cg.encrypt(text, passphrase=None, +                                                     sign=False) +        with open("{0}.gpg".format(filename), "wb") as fg: +            fg.write(ciphertext) +    except gpg.errors.GPGMEError as e: +        print(e) diff --git a/lang/python/examples/howto/temp-homedir-config.py b/lang/python/examples/howto/temp-homedir-config.py index ddd79327..1111fe21 100755 --- a/lang/python/examples/howto/temp-homedir-config.py +++ b/lang/python/examples/howto/temp-homedir-config.py @@ -3,6 +3,10 @@  from __future__ import absolute_import, division, unicode_literals +import os +import os.path +import sys +  # Copyright (C) 2018 Ben McGinnes <[email protected]>  #  # This program is free software; you can redistribute it and/or modify it under @@ -24,10 +28,6 @@ from __future__ import absolute_import, division, unicode_literals  # Lesser General Public along with this program; if not, see  # <http://www.gnu.org/licenses/>. -import os -import os.path -import sys -  intro = """  This script creates a temporary directory to use as a homedir for  testing key generation tasks with the correct permissions, along @@ -54,6 +54,13 @@ message telling you to specify a new directory name.  There is no  default directory name.  """ +ciphers256 = "TWOFISH CAMELLIA256 AES256" +ciphers192 = "CAMELLIA192 AES192" +ciphers128 = "CAMELLIA128 AES" +ciphersBad = "BLOWFISH IDEA CAST5 3DES" +digests = "SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1" +compress = "ZLIB BZIP2 ZIP Uncompressed" +  gpgconf = """# gpg.conf settings for key generation:  expert  allow-freeform-uid @@ -63,11 +70,11 @@ tofu-default-policy unknown  enable-large-rsa  enable-dsa2  cert-digest-algo SHA512 -default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed -personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES -personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 -personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed -""" +default-preference-list {0} {1} {2} {3} {4} {5} +personal-cipher-preferences {0} {1} {2} {3} +personal-digest-preferences {4} +personal-compress-preferences {5} +""".format(ciphers256, ciphers192, ciphers128, ciphersBad, digests, compress)  agentconf = """# gpg-agent.conf settings for key generation:  default-cache-ttl 300 @@ -84,17 +91,17 @@ else:  userdir = os.path.expanduser("~")  if new_homedir.startswith("~"): -    new_homdir.replace("~", "") +    new_homedir.replace("~", "")  else:      pass  if new_homedir.startswith("/"): -    new_homdir.replace("/", "") +    new_homedir.replace("/", "")  else:      pass  if new_homedir.startswith("."): -    new_homdir.replace(".", "_") +    new_homedir.replace(".", "_")  else:      pass diff --git a/lang/python/examples/inter-edit.py b/lang/python/examples/inter-edit.py index ed0d8c42..5b58c97b 100644 --- a/lang/python/examples/inter-edit.py +++ b/lang/python/examples/inter-edit.py @@ -15,15 +15,15 @@  #  # You should have received a copy of the GNU General Public License  # along with this program; if not, see <http://www.gnu.org/licenses/>. -  """Simple interactive editor to test editor scripts"""  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import sys  import gpg +del absolute_import, print_function, unicode_literals +  if len(sys.argv) != 2:      sys.exit("Usage: %s <Gpg key pattern>\n" % sys.argv[0]) @@ -40,10 +40,12 @@ with gpg.Context() as c:      print("Editing key {} ({}):".format(key.uids[0].uid, key.subkeys[0].fpr))      def edit_fnc(keyword, args): -        print("Status: {}, args: {} > ".format( -            keyword, args), end='', flush=True) +        print( +            "Status: {}, args: {} > ".format(keyword, args), +            end='', +            flush=True) -        if not 'GET' in keyword: +        if 'GET' not in keyword:              # no prompt              print()              return None diff --git a/lang/python/examples/low_level-encrypt_to_all.py b/lang/python/examples/low_level-encrypt_to_all.py index bad4220c..5c10d3df 100755 --- a/lang/python/examples/low_level-encrypt_to_all.py +++ b/lang/python/examples/low_level-encrypt_to_all.py @@ -16,18 +16,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program; if not, see <http://www.gnu.org/licenses/>. -  """  This program will try to encrypt a simple message to each key on your  keyring.  If your keyring has any invalid keys on it, those keys will  be skipped and it will re-try the encryption."""  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import sys  import gpg +del absolute_import, print_function, unicode_literals +  with gpg.Context(armor=True) as c:      recipients = list()      for key in c.keylist(): @@ -40,14 +40,15 @@ with gpg.Context(armor=True) as c:      while not ciphertext:          print("Encrypting to %d recipients" % len(recipients))          try: -            ciphertext, _, _ = c.encrypt(b'This is my message.', -                                         recipients=recipients) +            ciphertext, _, _ = c.encrypt( +                b'This is my message.', recipients=recipients)          except gpg.errors.InvalidRecipients as e:              print("Encryption failed for these keys:\n{0!s}".format(e))              # filter out the bad keys              bad_keys = {bad.fpr for bad in e.recipients} -            recipients = [r for r in recipients -                          if not r.subkeys[0].fpr in bad_keys] +            recipients = [ +                r for r in recipients if not r.subkeys[0].fpr in bad_keys +            ]      sys.stdout.buffer.write(ciphertext) diff --git a/lang/python/examples/sign.py b/lang/python/examples/sign.py index 16c2256a..5b90b4b8 100755 --- a/lang/python/examples/sign.py +++ b/lang/python/examples/sign.py @@ -17,12 +17,13 @@  # 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 sys  import gpg  from gpg.constants.sig import mode +del absolute_import, print_function, unicode_literals +  with gpg.Context() as c:      signed, _ = c.sign(b"Test message", mode=mode.CLEAR)      sys.stdout.buffer.write(signed) diff --git a/lang/python/examples/signverify.py b/lang/python/examples/signverify.py index 5870ca95..2df72758 100755 --- a/lang/python/examples/signverify.py +++ b/lang/python/examples/signverify.py @@ -20,12 +20,13 @@  # It uses keys for [email protected] generated by genkey.py script  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import sys  import gpg  from gpg.constants.sig import mode +del absolute_import, print_function, unicode_literals +  user = "joe+gpg"  with gpg.Context(pinentry_mode=gpg.constants.PINENTRY_MODE_LOOPBACK) as c: diff --git a/lang/python/examples/simple.py b/lang/python/examples/simple.py index 8f451d7c..17c3eba0 100755 --- a/lang/python/examples/simple.py +++ b/lang/python/examples/simple.py @@ -18,11 +18,12 @@  # 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 sys  import gpg +del absolute_import, print_function, unicode_literals +  with gpg.Context(armor=True) as c:      recipients = []      print("Enter name of your recipient(s), end with a blank line.") @@ -40,8 +41,8 @@ with gpg.Context(armor=True) as c:      if not recipients:          sys.exit("No recipients.") -    print("Encrypting for {}.".format(", ".join(k.uids[0].name -                                                for k in recipients))) +    print("Encrypting for {}.".format(", ".join( +        k.uids[0].name for k in recipients)))      ciphertext, _, _ = c.encrypt(b"This is my message,", recipients)      sys.stdout.buffer.write(ciphertext) diff --git a/lang/python/examples/testCMSgetkey.py b/lang/python/examples/testCMSgetkey.py index d4c08840..f1cdb2ce 100644 --- a/lang/python/examples/testCMSgetkey.py +++ b/lang/python/examples/testCMSgetkey.py @@ -15,15 +15,15 @@  #  # You should have received a copy of the GNU General Public License  # along with this program; if not, see <http://www.gnu.org/licenses/>. -  """A test applicaton for the CMS protocol."""  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import sys  import gpg +del absolute_import, print_function, unicode_literals +  if len(sys.argv) != 2:      sys.exit("fingerprint or unique key ID for gpgme_get_key()") diff --git a/lang/python/examples/verifydetails.py b/lang/python/examples/verifydetails.py index b3ca1339..dc0e7d38 100755 --- a/lang/python/examples/verifydetails.py +++ b/lang/python/examples/verifydetails.py @@ -18,11 +18,13 @@  # 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 sys  import gpg +del absolute_import, print_function, unicode_literals + +  def print_engine_infos():      print("gpgme version:", gpg.core.check_version(None))      print("engines:") @@ -31,8 +33,9 @@ def print_engine_infos():          print(engine.file_name, engine.version)      for proto in [gpg.constants.protocol.OpenPGP, gpg.constants.protocol.CMS]: -        print("Have {}? {}".format(gpg.core.get_protocol_name(proto), -                                   gpg.core.engine_check_version(proto))) +        print("Have {}? {}".format( +            gpg.core.get_protocol_name(proto), +            gpg.core.engine_check_version(proto)))  def verifyprintdetails(filename, detached_sig_filename=None): @@ -40,9 +43,9 @@ def verifyprintdetails(filename, detached_sig_filename=None):      with gpg.Context() as c:          # Verify. -        data, result = c.verify(open(filename), -                                open(detached_sig_filename) -                                if detached_sig_filename else None) +        data, result = c.verify( +            open(filename), +            open(detached_sig_filename) if detached_sig_filename else None)          # List results for all signatures. Status equal 0 means "Ok".          for index, sign in enumerate(result.signatures): @@ -57,15 +60,15 @@ def verifyprintdetails(filename, detached_sig_filename=None):      if data:          sys.stdout.buffer.write(data) +  def main():      print_engine_infos()      print()      argc = len(sys.argv)      if argc < 2 or argc > 3: -        sys.exit( -            "Usage: {} <filename>[ <detached_signature_filename>]".format( -                sys.argv[0])) +        sys.exit("Usage: {} <filename>[ <detached_signature_filename>]".format( +            sys.argv[0]))      if argc == 2:          print("trying to verify file {}.".format(sys.argv[1])) @@ -74,5 +77,6 @@ def main():          print("trying to verify signature {1} for file {0}.".format(*sys.argv))          verifyprintdetails(sys.argv[1], sys.argv[2]) +  if __name__ == "__main__":      main() diff --git a/lang/python/setup.py.in b/lang/python/setup.py.in index 2595073f..65a4be05 100755 --- a/lang/python/setup.py.in +++ b/lang/python/setup.py.in @@ -1,6 +1,6 @@  #!/usr/bin/env python -# Copyright (C) 2016-2017 g10 Code GmbH +# Copyright (C) 2016-2018 g10 Code GmbH  # Copyright (C) 2004,2008 Igor Belyi <[email protected]>  # Copyright (C) 2002 John Goerzen <[email protected]>  # @@ -19,11 +19,15 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from distutils.core import setup, Extension -import os, os.path, sys +from distutils.command.build import build +  import glob +import os +import os.path  import re  import shutil  import subprocess +import sys  # Out-of-tree build of the gpg bindings.  gpg_error_config = ["gpg-error-config"] @@ -40,9 +44,11 @@ top_builddir = os.environ.get("top_builddir")  if top_builddir:      # In-tree build.      in_tree = True -    gpgme_config = [os.path.join(top_builddir, "src/gpgme-config")] + gpgme_config_flags +    gpgme_config = [os.path.join(top_builddir, "src/gpgme-config") +                    ] + gpgme_config_flags      gpgme_h = os.path.join(top_builddir, "src/gpgme.h") -    library_dirs = [os.path.join(top_builddir, "src/.libs")] # XXX uses libtool internals +    library_dirs = [os.path.join(top_builddir, +                                 "src/.libs")]  # XXX uses libtool internals      extra_macros.update(          HAVE_CONFIG_H=1,          HAVE_DATA_H=1, @@ -55,17 +61,18 @@ else:      devnull = open(os.devnull, "w")  try: -    subprocess.check_call(gpgme_config + ['--version'], -                          stdout=devnull) +    subprocess.check_call(gpgme_config + ['--version'], stdout=devnull)  except:      sys.exit("Could not find gpgme-config.  " +               "Please install the libgpgme development package.") +  def getconfig(what, config=gpgme_config): -    confdata = subprocess.Popen(config + ["--%s" % what], -                                stdout=subprocess.PIPE).communicate()[0] +    confdata = subprocess.Popen( +        config + ["--%s" % what], stdout=subprocess.PIPE).communicate()[0]      return [x for x in confdata.decode('utf-8').split() if x != ''] +  version = version_raw = getconfig("version")[0]  if '-' in version:      version = version.split('-')[0] @@ -90,7 +97,7 @@ for item in getconfig('cflags'):          include_dirs.append(item[2:])      elif item.startswith("-D"):          defitem = item[2:].split("=", 1) -        if len(defitem)==2: +        if len(defitem) == 2:              define_macros.append((defitem[0], defitem[1]))          else:              define_macros.append((defitem[0], None)) @@ -98,49 +105,67 @@ for item in getconfig('cflags'):  # Adjust include and library locations in case of win32  uname_s = os.popen("uname -s").read()  if uname_s.startswith("MINGW32"): -   mnts = [x.split()[0:3:2] for x in os.popen("mount").read().split("\n") if x] -   tmplist = sorted([(len(x[1]), x[1], x[0]) for x in mnts]) -   tmplist.reverse() -   extra_dirs = [] -   for item in include_dirs: -       for ln, mnt, tgt in tmplist: -           if item.startswith(mnt): -               item = os.path.normpath(item[ln:]) -               while item[0] == os.path.sep: -                   item = item[1:] -               extra_dirs.append(os.path.join(tgt, item)) -               break -   include_dirs += extra_dirs -   for item in [x[2:] for x in libs if x.startswith("-L")]: -       for ln, mnt, tgt in tmplist: -           if item.startswith(mnt): -               item = os.path.normpath(item[ln:]) -               while item[0] == os.path.sep: -                   item = item[1:] -               library_dirs.append(os.path.join(tgt, item)) -               break +    mnts = [ +        x.split()[0:3:2] for x in os.popen("mount").read().split("\n") if x +    ] +    tmplist = sorted([(len(x[1]), x[1], x[0]) for x in mnts]) +    tmplist.reverse() +    extra_dirs = [] +    for item in include_dirs: +        for ln, mnt, tgt in tmplist: +            if item.startswith(mnt): +                item = os.path.normpath(item[ln:]) +                while item[0] == os.path.sep: +                    item = item[1:] +                extra_dirs.append(os.path.join(tgt, item)) +                break +    include_dirs += extra_dirs +    for item in [x[2:] for x in libs if x.startswith("-L")]: +        for ln, mnt, tgt in tmplist: +            if item.startswith(mnt): +                item = os.path.normpath(item[ln:]) +                while item[0] == os.path.sep: +                    item = item[1:] +                library_dirs.append(os.path.join(tgt, item)) +                break +  def in_srcdir(name):      return os.path.join(os.environ.get("srcdir", ""), name) + +  def up_to_date(source, target): -    return (os.path.exists(target) -            and os.path.getmtime(source) <= os.path.getmtime(target)) +    return (os.path.exists(target) and +            os.path.getmtime(source) <= os.path.getmtime(target)) +  # We build an Extension using SWIG, which generates a Python module.  # By default, the 'build_py' step is run before 'build_ext', and  # therefore the generated Python module is not copied into the build  # directory. -# Bug: http://bugs.python.org/issue1016626 +# Bugs: https://bugs.python.org/issue1016626 +#       https://bugs.python.org/issue2624  # Workaround: -# http://stackoverflow.com/questions/12491328/python-distutils-not-include-the-swig-generated-module -from distutils.command.build import build -class BuildExtFirstHack(build): +# https://stackoverflow.com/questions/12491328/python-distutils-not-include-the-swig-generated-module +# +# To install to multiple Python installations or to alternate ones run the +# following three commands (yes, run the build one twice): +# +# /path/to/pythonX.Y setup.py build +# /path/to/pythonX.Y setup.py build +# /path/to/pythonX.Y setup.py install +# +# It is highly likely that this will need to be run as root or with sudo (or +# sudo -H).  It may or may not work with venv. and outside a virtualenv +class BuildExtFirstHack(build):      def _read_header(self, header, cflags):          tmp_include = self._in_build_base("include1.h")          with open(tmp_include, 'w') as f:              f.write("#include <%s>" % header) -        return subprocess.check_output(os.environ.get('CPP', 'cc -E').split() + cflags + [tmp_include]).decode('utf-8') +        return subprocess.check_output( +            os.environ.get('CPP', 'cc -E').split() + cflags + +            [tmp_include]).decode('utf-8')      def _write_if_unchanged(self, target, content):          if os.path.exists(target): @@ -158,13 +183,14 @@ class BuildExtFirstHack(build):      def _generate_errors_i(self):          try: -            subprocess.check_call(gpg_error_config + ['--version'], -                                  stdout=devnull) +            subprocess.check_call( +                gpg_error_config + ['--version'], stdout=devnull)          except:              sys.exit("Could not find gpg-error-config.  " +                       "Please install the libgpg-error development package.") -        gpg_error_content = self._read_header("gpg-error.h", getconfig("cflags", config=gpg_error_config)) +        gpg_error_content = self._read_header( +            "gpg-error.h", getconfig("cflags", config=gpg_error_config))          filter_re = re.compile(r'GPG_ERR_[^ ]* =')          rewrite_re = re.compile(r' *(.*) = .*') @@ -173,9 +199,11 @@ class BuildExtFirstHack(build):          for line in gpg_error_content.splitlines():              if not filter_re.search(line):                  continue -            errors_i_content += rewrite_re.sub(r'%constant long \1 = \1;'+'\n', line.strip()) +            errors_i_content += rewrite_re.sub( +                r'%constant long \1 = \1;' + '\n', line.strip()) -        self._write_if_unchanged(self._in_build_base("errors.i"), errors_i_content) +        self._write_if_unchanged( +            self._in_build_base("errors.i"), errors_i_content)      def _in_build_base(self, name):          return os.path.join(self.build_base, name) @@ -191,7 +219,8 @@ class BuildExtFirstHack(build):          # Copy due to http://bugs.python.org/issue2624          # Avoid creating in srcdir          for source, target in ((in_srcdir(n), self._in_build_base(n)) -                               for n in ('gpgme.i', 'helpers.c', 'private.h', 'helpers.h')): +                               for n in ('gpgme.i', 'helpers.c', 'private.h', +                                         'helpers.h')):              if not up_to_date(source, target):                  shutil.copy2(source, target) @@ -203,52 +232,60 @@ class BuildExtFirstHack(build):      def run(self):          self._generate() -        swig_sources.extend((self._in_build_base('gpgme.i'), self._in_build_base('helpers.c'))) -        swig_opts.extend(['-I' + self.build_base, -                          '-outdir', os.path.join(self.build_lib, 'gpg')]) +        swig_sources.extend((self._in_build_base('gpgme.i'), +                             self._in_build_base('helpers.c'))) +        swig_opts.extend([ +            '-I' + self.build_base, '-outdir', +            os.path.join(self.build_lib, 'gpg') +        ])          include_dirs.insert(0, self.build_base)          self.run_command('build_ext')          build.run(self) +  py3 = [] if sys.version_info.major < 3 else ['-py3']  swig_sources = []  swig_opts = ['-threads'] + py3 + extra_swig_opts -swige = Extension("gpg._gpgme", -                  sources = swig_sources, -                  swig_opts = swig_opts, -                  include_dirs = include_dirs, -                  define_macros = define_macros, -                  library_dirs = library_dirs, -                  extra_link_args = libs) - -setup(name="gpg", -      cmdclass={'build': BuildExtFirstHack}, -      version="@VERSION@", -      description='Python bindings for GPGME GnuPG cryptography library', -      # XXX add a long description -      #long_description=long_description, -      author='The GnuPG hackers', -      author_email='[email protected]', -      url='https://www.gnupg.org', -      ext_modules=[swige], -      packages = ['gpg', 'gpg.constants', 'gpg.constants.data', -                  'gpg.constants.keylist', 'gpg.constants.sig', -                  'gpg.constants.tofu'], -      license="LGPL2.1+ (the library), GPL2+ (tests and examples)", -      classifiers=[ -          'Development Status :: 4 - Beta', -          'Intended Audience :: Developers', -          'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', -          'Programming Language :: Python :: 2', -          'Programming Language :: Python :: 2.7', -          'Programming Language :: Python :: 3', -          'Programming Language :: Python :: 3.4', -          'Programming Language :: Python :: 3.5', -          'Programming Language :: Python :: 3.6', -          'Operating System :: POSIX', -          'Operating System :: Microsoft :: Windows', -          'Topic :: Communications :: Email', -          'Topic :: Security :: Cryptography', -      ], +swige = Extension( +    "gpg._gpgme", +    sources=swig_sources, +    swig_opts=swig_opts, +    include_dirs=include_dirs, +    define_macros=define_macros, +    library_dirs=library_dirs, +    extra_link_args=libs) + +setup( +    name="gpg", +    cmdclass={'build': BuildExtFirstHack}, +    version="@VERSION@", +    description='Python bindings for GPGME GnuPG cryptography library', +    # TODO: add a long description +    # long_description=long_description, +    author='The GnuPG hackers', +    author_email='[email protected]', +    url='https://www.gnupg.org', +    ext_modules=[swige], +    packages=[ +        'gpg', 'gpg.constants', 'gpg.constants.data', 'gpg.constants.keylist', +        'gpg.constants.sig', 'gpg.constants.tofu' +    ], +    license="LGPL2.1+ (the library), GPL2+ (tests and examples)", +    classifiers=[ +        'Development Status :: 4 - Beta', +        'Intended Audience :: Developers', +        'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', +        'Programming Language :: Python :: 2', +        'Programming Language :: Python :: 2.7', +        'Programming Language :: Python :: 3', +        'Programming Language :: Python :: 3.4', +        'Programming Language :: Python :: 3.5', +        'Programming Language :: Python :: 3.6', +        'Programming Language :: Python :: 3.7', +        'Operating System :: POSIX', +        'Operating System :: Microsoft :: Windows', +        'Topic :: Communications :: Email', +        'Topic :: Security :: Cryptography', +    ],  ) diff --git a/lang/python/src/__init__.py b/lang/python/src/__init__.py index 385b17e3..30e638c4 100644 --- a/lang/python/src/__init__.py +++ b/lang/python/src/__init__.py @@ -15,7 +15,6 @@  # You should have received a copy of the GNU Lesser General Public  # License along with this library; if not, write to the Free Software  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA -  """gpg: GnuPG Interface for Python (GPGME bindings)  Welcome to gpg, the GnuPG Interface for Python. @@ -96,7 +95,6 @@ GPGME documentation: https://www.gnupg.org/documentation/manuals/gpgme/  """  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from . import core  from . import errors @@ -107,6 +105,8 @@ from . import version  from .core import Context  from .core import Data +del absolute_import, print_function, unicode_literals +  # Interface hygiene.  # Drop the low-level gpgme that creeps in for some reason. @@ -117,5 +117,7 @@ del gpgme  _ = [Context, Data, core, errors, constants, util, callbacks, version]  del _ -__all__ = ["Context", "Data", -           "core", "errors", "constants", "util", "callbacks", "version"] +__all__ = [ +    "Context", "Data", "core", "errors", "constants", "util", "callbacks", +    "version" +] diff --git a/lang/python/src/callbacks.py b/lang/python/src/callbacks.py index b25a9a74..9aacf566 100644 --- a/lang/python/src/callbacks.py +++ b/lang/python/src/callbacks.py @@ -16,26 +16,30 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from getpass import getpass +del absolute_import, print_function, unicode_literals + +  def passphrase_stdin(hint, desc, prev_bad, hook=None):      """This is a sample callback that will read a passphrase from      the terminal.  The hook here, if present, will be used to describe      why the passphrase is needed."""      why = '' -    if hook != None: +    if hook is not None:          why = ' ' + hook      if prev_bad:          why += ' (again)'      print("Please supply %s' password%s:" % (hint, why))      return getpass() +  def progress_stdout(what, type, current, total, hook=None): -    print("PROGRESS UPDATE: what = %s, type = %d, current = %d, total = %d" %\ +    print("PROGRESS UPDATE: what = %s, type = %d, current = %d, total = %d" %            (what, type, current, total)) +  def readcb_fh(count, hook):      """A callback for data.  hook should be a Python file-like object."""      if count: diff --git a/lang/python/src/constants/__init__.py b/lang/python/src/constants/__init__.py index 484ffd29..7a953aab 100644 --- a/lang/python/src/constants/__init__.py +++ b/lang/python/src/constants/__init__.py @@ -18,23 +18,29 @@  # 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 +# Globals may need to be set prior to module import, if so this prevents PEP8 +# compliance, but better that than code breakage.  util.process_constants('GPGME_', globals()) -del util  # For convenience, we import the modules here. -from . import data, keylist, sig, tofu # The subdirs. -from . import create, event, keysign, md, pk, protocol, sigsum, status, validity +from . import data, keylist, sig, tofu  # The subdirs. +# The remaining modules can no longer fit on one line. +from . import create, event, keysign, md, pk, protocol, sigsum, status +from . import validity + +del absolute_import, print_function, unicode_literals, util  # A complication arises because 'import' is a reserved keyword.  # Import it as 'Import' instead. -globals()['Import'] = getattr(__import__('', globals(), locals(), -                                         [str('import')], 1), "import") +globals()['Import'] = getattr( +    __import__('', globals(), locals(), [str('import')], 1), "import") -__all__ = ['data', 'event', 'import', 'keysign', 'keylist', 'md', 'pk', -           'protocol', 'sig', 'sigsum', 'status', 'tofu', 'validity', 'create'] +__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/src/constants/create.py b/lang/python/src/constants/create.py index 132e96d4..382dad92 100644 --- a/lang/python/src/constants/create.py +++ b/lang/python/src/constants/create.py @@ -18,8 +18,7 @@  # 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 +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/data/__init__.py b/lang/python/src/constants/data/__init__.py index 8274ab91..c0856679 100644 --- a/lang/python/src/constants/data/__init__.py +++ b/lang/python/src/constants/data/__init__.py @@ -1,6 +1,6 @@ -  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from . import encoding  __all__ = ['encoding'] + +del absolute_import, print_function, unicode_literals diff --git a/lang/python/src/constants/data/encoding.py b/lang/python/src/constants/data/encoding.py index e76a22ee..9afa7323 100644 --- a/lang/python/src/constants/data/encoding.py +++ b/lang/python/src/constants/data/encoding.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_DATA_ENCODING_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/event.py b/lang/python/src/constants/event.py index 1b14d1d1..9f9273da 100644 --- a/lang/python/src/constants/event.py +++ b/lang/python/src/constants/event.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_EVENT_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/import.py b/lang/python/src/constants/import.py index 47c296cb..e477eb25 100644 --- a/lang/python/src/constants/import.py +++ b/lang/python/src/constants/import.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_IMPORT_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/keylist/__init__.py b/lang/python/src/constants/keylist/__init__.py index 2ce0edfd..fa8f7f0b 100644 --- a/lang/python/src/constants/keylist/__init__.py +++ b/lang/python/src/constants/keylist/__init__.py @@ -1,6 +1,6 @@ -  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from . import mode  __all__ = ['mode'] + +del absolute_import, print_function, unicode_literals diff --git a/lang/python/src/constants/keylist/mode.py b/lang/python/src/constants/keylist/mode.py index 39e1819d..bda7710e 100644 --- a/lang/python/src/constants/keylist/mode.py +++ b/lang/python/src/constants/keylist/mode.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_KEYLIST_MODE_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/keysign.py b/lang/python/src/constants/keysign.py index fccdbc42..328dfb91 100644 --- a/lang/python/src/constants/keysign.py +++ b/lang/python/src/constants/keysign.py @@ -18,8 +18,7 @@  # 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 +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/md.py b/lang/python/src/constants/md.py index f3e8bbdb..068b31d2 100644 --- a/lang/python/src/constants/md.py +++ b/lang/python/src/constants/md.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_MD_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/pk.py b/lang/python/src/constants/pk.py index 6bf2a215..3a826d12 100644 --- a/lang/python/src/constants/pk.py +++ b/lang/python/src/constants/pk.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_PK_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/protocol.py b/lang/python/src/constants/protocol.py index d086bbde..cc9ca079 100644 --- a/lang/python/src/constants/protocol.py +++ b/lang/python/src/constants/protocol.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_PROTOCOL_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/sig/__init__.py b/lang/python/src/constants/sig/__init__.py index 39d4e6e1..f45af004 100644 --- a/lang/python/src/constants/sig/__init__.py +++ b/lang/python/src/constants/sig/__init__.py @@ -1,6 +1,6 @@ -  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from . import mode, notation  __all__ = ['mode', 'notation'] + +del absolute_import, print_function, unicode_literals diff --git a/lang/python/src/constants/sig/mode.py b/lang/python/src/constants/sig/mode.py index 0f4f0efc..3a2d17a3 100644 --- a/lang/python/src/constants/sig/mode.py +++ b/lang/python/src/constants/sig/mode.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_SIG_MODE_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/sig/notation.py b/lang/python/src/constants/sig/notation.py index 9a79e014..9e56be39 100644 --- a/lang/python/src/constants/sig/notation.py +++ b/lang/python/src/constants/sig/notation.py @@ -18,8 +18,7 @@  # 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_SIG_NOTATION_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/sigsum.py b/lang/python/src/constants/sigsum.py index 09ef9d78..0fe0e77e 100644 --- a/lang/python/src/constants/sigsum.py +++ b/lang/python/src/constants/sigsum.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_SIGSUM_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/tofu/__init__.py b/lang/python/src/constants/tofu/__init__.py index 819a58bb..5e58a6a8 100644 --- a/lang/python/src/constants/tofu/__init__.py +++ b/lang/python/src/constants/tofu/__init__.py @@ -18,7 +18,8 @@  # 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'] + +del absolute_import, print_function, unicode_literals diff --git a/lang/python/src/constants/tofu/policy.py b/lang/python/src/constants/tofu/policy.py index 5a61f067..53d853de 100644 --- a/lang/python/src/constants/tofu/policy.py +++ b/lang/python/src/constants/tofu/policy.py @@ -18,8 +18,7 @@  # 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 +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/constants/validity.py b/lang/python/src/constants/validity.py index d3c53458..4ecf4ec3 100644 --- a/lang/python/src/constants/validity.py +++ b/lang/python/src/constants/validity.py @@ -16,8 +16,7 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from gpg import util  util.process_constants('GPGME_VALIDITY_', globals()) -del util +del absolute_import, print_function, unicode_literals, util diff --git a/lang/python/src/core.py b/lang/python/src/core.py index bd95d231..d4711317 100644 --- a/lang/python/src/core.py +++ b/lang/python/src/core.py @@ -1,5 +1,22 @@ -# Copyright (C) 2016-2017 g10 Code GmbH -# Copyright (C) 2004,2008 Igor Belyi <[email protected]> +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, print_function, unicode_literals + +import re +import os +import warnings +import weakref + +from . import gpgme +from .errors import errorcheck, GPGMEError +from . import constants +from . import errors +from . import util + +del absolute_import, print_function, unicode_literals + +# Copyright (C) 2016-2018 g10 Code GmbH +# Copyright (C) 2004, 2008 Igor Belyi <[email protected]>  # Copyright (C) 2002 John Goerzen <[email protected]>  #  #    This library is free software; you can redistribute it and/or @@ -15,7 +32,6 @@  #    You should have received a copy of the GNU Lesser General Public  #    License along with this library; if not, write to the Free Software  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA -  """Core functionality  Core functionality of GPGME wrapped in a object-oriented fashion. @@ -24,18 +40,6 @@ and the 'Data' class describing buffers of data.  """ -from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals - -import re -import os -import warnings -import weakref -from . import gpgme -from .errors import errorcheck, GPGMEError -from . import constants -from . import errors -from . import util  class GpgmeWrapper(object):      """Base wrapper class @@ -49,8 +53,8 @@ class GpgmeWrapper(object):          self.wrapped = wrapped      def __repr__(self): -        return '<{}/{!r}>'.format(super(GpgmeWrapper, self).__repr__(), -                                  self.wrapped) +        return '<{}/{!r}>'.format( +            super(GpgmeWrapper, self).__repr__(), self.wrapped)      def __str__(self):          acc = ['{}.{}'.format(__name__, self.__class__.__name__)] @@ -64,7 +68,7 @@ class GpgmeWrapper(object):          return hash(repr(self.wrapped))      def __eq__(self, other): -        if other == None: +        if other is None:              return False          else:              return repr(self.wrapped) == repr(other.wrapped) @@ -98,12 +102,12 @@ class GpgmeWrapper(object):      _boolean_properties = set()      def __wrap_boolean_property(self, key, do_set=False, value=None): -        get_func = getattr(gpgme, -                           "{}get_{}".format(self._cprefix, key)) -        set_func = getattr(gpgme, -                           "{}set_{}".format(self._cprefix, key)) +        get_func = getattr(gpgme, "{}get_{}".format(self._cprefix, key)) +        set_func = getattr(gpgme, "{}set_{}".format(self._cprefix, key)) +          def get(slf):              return bool(get_func(slf.wrapped)) +          def set_(slf, value):              set_func(slf.wrapped, bool(value)) @@ -116,9 +120,10 @@ class GpgmeWrapper(object):              return get(self)      _munge_docstring = re.compile(r'gpgme_([^(]*)\(([^,]*), (.*\) -> .*)') +      def __getattr__(self, key):          """On-the-fly generation of wrapper methods and properties""" -        if key[0] == '_' or self._cprefix == None: +        if key[0] == '_' or self._cprefix is None:              return None          if key in self._boolean_properties: @@ -128,12 +133,14 @@ class GpgmeWrapper(object):          func = getattr(gpgme, name)          if self._errorcheck(name): +              def _funcwrap(slf, *args):                  result = func(slf.wrapped, *args)                  if slf._callback_excinfo:                      gpgme.gpg_raise_callback_exception(slf)                  return errorcheck(result, name)          else: +              def _funcwrap(slf, *args):                  result = func(slf.wrapped, *args)                  if slf._callback_excinfo: @@ -149,6 +156,7 @@ class GpgmeWrapper(object):          # Bind the method to 'self'.          def wrapper(*args):              return _funcwrap(self, *args) +          wrapper.__doc__ = doc          return wrapper @@ -160,6 +168,7 @@ class GpgmeWrapper(object):          else:              super(GpgmeWrapper, self).__setattr__(key, value) +  class Context(GpgmeWrapper):      """Context for cryptographic operations @@ -173,10 +182,15 @@ class Context(GpgmeWrapper):      """ -    def __init__(self, armor=False, textmode=False, offline=False, -                 signers=[], pinentry_mode=constants.PINENTRY_MODE_DEFAULT, +    def __init__(self, +                 armor=False, +                 textmode=False, +                 offline=False, +                 signers=[], +                 pinentry_mode=constants.PINENTRY_MODE_DEFAULT,                   protocol=constants.PROTOCOL_OpenPGP, -                 wrapped=None, home_dir=None): +                 wrapped=None, +                 home_dir=None):          """Construct a context object          Keyword arguments: @@ -212,22 +226,29 @@ class Context(GpgmeWrapper):          Helper function to retrieve the results of an operation, or          None if SINK is given.          """ -        if sink or data == None: +        if sink or data is None:              return None          data.seek(0, os.SEEK_SET)          return data.read()      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, -                prepare=False, expect_sign=False, compress=True): +        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, +                prepare=False, +                expect_sign=False, +                compress=True):          """Encrypt data          Encrypt the given plaintext for the given recipients.  If the @@ -267,12 +288,14 @@ class Context(GpgmeWrapper):          flags |= expect_sign * constants.ENCRYPT_EXPECT_SIGN          flags |= (not compress) * constants.ENCRYPT_NO_COMPRESS -        if passphrase != None: +        if passphrase is not None:              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: @@ -283,25 +306,26 @@ class Context(GpgmeWrapper):          except errors.GPGMEError as e:              result = self.op_encrypt_result()              sig_result = self.op_sign_result() if sign else None -            results = (self.__read__(sink, ciphertext), -                       result, sig_result) +            results = (self.__read__(sink, ciphertext), result, sig_result)              if e.getcode() == errors.UNUSABLE_PUBKEY:                  if result.invalid_recipients: -                    raise errors.InvalidRecipients(result.invalid_recipients, -                                                   error=e.error, -                                                   results=results) +                    raise errors.InvalidRecipients( +                        result.invalid_recipients, +                        error=e.error, +                        results=results)              if e.getcode() == errors.UNUSABLE_SECKEY:                  sig_result = self.op_sign_result()                  if sig_result.invalid_signers: -                    raise errors.InvalidSigners(sig_result.invalid_signers, -                                                error=e.error, -                                                results=results) +                    raise errors.InvalidSigners( +                        sig_result.invalid_signers, +                        error=e.error, +                        results=results)              # Otherwise, just raise the error, but attach the results              # first.              e.results = results              raise e          finally: -            if passphrase != None: +            if passphrase is not None:                  self.pinentry_mode = old_pinentry_mode                  if old_passphrase_cb:                      self.set_passphrase_cb(*old_passphrase_cb[1:]) @@ -344,12 +368,14 @@ class Context(GpgmeWrapper):          """          plaintext = sink if sink else Data() -        if passphrase != None: +        if passphrase is not None:              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: @@ -361,11 +387,10 @@ class Context(GpgmeWrapper):              result = self.op_decrypt_result()              verify_result = self.op_verify_result() if verify else None              # Just raise the error, but attach the results first. -            e.results = (self.__read__(sink, plaintext), -                         result, verify_result) +            e.results = (self.__read__(sink, plaintext), result, verify_result)              raise e          finally: -            if passphrase != None: +            if passphrase is not None:                  self.pinentry_mode = old_pinentry_mode                  if old_passphrase_cb:                      self.set_passphrase_cb(*old_passphrase_cb[1:]) @@ -374,15 +399,15 @@ class Context(GpgmeWrapper):          verify_result = self.op_verify_result() if verify else None          results = (self.__read__(sink, plaintext), result, verify_result)          if result.unsupported_algorithm: -            raise errors.UnsupportedAlgorithm(result.unsupported_algorithm, -                                              results=results) +            raise errors.UnsupportedAlgorithm( +                result.unsupported_algorithm, results=results)          if verify:              if any(s.status != errors.NO_ERROR                     for s in verify_result.signatures):                  raise errors.BadSignatures(verify_result, results=results) -        if verify and verify != True: +        if not verify:  # was: if verify and verify != True:              missing = list()              for key in verify:                  ok = False @@ -398,8 +423,8 @@ class Context(GpgmeWrapper):                  if not ok:                      missing.append(key)              if missing: -                raise errors.MissingSignatures(verify_result, missing, -                                               results=results) +                raise errors.MissingSignatures( +                    verify_result, missing, results=results)          return results @@ -431,13 +456,13 @@ class Context(GpgmeWrapper):          try:              self.op_sign(data, signeddata, mode)          except errors.GPGMEError as e: -            results = (self.__read__(sink, signeddata), -                       self.op_sign_result()) +            results = (self.__read__(sink, signeddata), self.op_sign_result())              if e.getcode() == errors.UNUSABLE_SECKEY:                  if results[1].invalid_signers: -                    raise errors.InvalidSigners(results[1].invalid_signers, -                                                error=e.error, -                                                results=results) +                    raise errors.InvalidSigners( +                        results[1].invalid_signers, +                        error=e.error, +                        results=results)              e.results = results              raise e @@ -481,8 +506,7 @@ class Context(GpgmeWrapper):                  self.op_verify(signed_data, None, data)          except errors.GPGMEError as e:              # Just raise the error, but attach the results first. -            e.results = (self.__read__(sink, data), -                         self.op_verify_result()) +            e.results = (self.__read__(sink, data), self.op_verify_result())              raise e          results = (self.__read__(sink, data), self.op_verify_result()) @@ -504,12 +528,171 @@ class Context(GpgmeWrapper):              if not ok:                  missing.append(key)          if missing: -            raise errors.MissingSignatures(results[1], missing, -                                           results=results) +            raise errors.MissingSignatures( +                results[1], missing, results=results)          return results -    def keylist(self, pattern=None, secret=False, +    def key_import(self, data): +        """Import data + +        Imports the given data into the Context. + +        Returns: +                -- an object describing the results of imported or updated +                   keys + +        Raises: +        TypeError      -- Very rarely. +        GPGMEError     -- as signaled by the underlying library: + +                          Import status errors, when they occur, will usually +                          be of NODATA.  NO_PUBKEY indicates something +                          managed to run the function without any +                          arguments, while an argument of None triggers +                          the first NODATA of errors.GPGME in the +                          exception. +        """ +        try: +            self.op_import(data) +            result = self.op_import_result() +            if result.considered == 0: +                status = constants.STATUS_IMPORT_PROBLEM +            else: +                status = constants.STATUS_KEY_CONSIDERED +        except Exception as e: +            if e == errors.GPGMEError: +                if e.code_str == "No data": +                    status = constants.STATUS_NODATA +                else: +                    status = constants.STATUS_FILE_ERROR +            elif e == TypeError and hasattr(data, "decode") is True: +                status = constants.STATUS_NO_PUBKEY +            elif e == TypeError and hasattr(data, "encode") is True: +                status = constants.STATUS_FILE_ERROR +            else: +                status = constants.STATUS_ERROR + +        if status == constants.STATUS_KEY_CONSIDERED: +            import_result = result +        else: +            import_result = status + +        return import_result + +    def key_export(self, pattern=None): +        """Export keys. + +        Exports public keys matching the pattern specified.  If no +        pattern is specified then exports all available keys. + +        Keyword arguments: +        pattern	-- return keys matching pattern (default: all keys) + +        Returns: +                -- A key block containing one or more OpenPGP keys in +                   either ASCII armoured or binary format as determined +                   by the Context().  If there are no matching keys it +                   returns None. + +        Raises: +        GPGMEError     -- as signaled by the underlying library. +        """ +        data = Data() +        mode = 0 +        try: +            self.op_export(pattern, mode, data) +            data.seek(0, os.SEEK_SET) +            pk_result = data.read() +        except GPGMEError as e: +            pk_result = e + +        if len(pk_result) > 0: +            result = pk_result +        else: +            result = None + +        return result + +    def key_export_minimal(self, pattern=None): +        """Export keys. + +        Exports public keys matching the pattern specified in a +        minimised format.  If no pattern is specified then exports all +        available keys. + +        Keyword arguments: +        pattern	-- return keys matching pattern (default: all keys) + +        Returns: +                -- A key block containing one or more minimised OpenPGP +                   keys in either ASCII armoured or binary format as +                   determined by the Context().  If there are no matching +                   keys it returns None. + +        Raises: +        GPGMEError     -- as signaled by the underlying library. +        """ +        data = Data() +        mode = gpgme.GPGME_EXPORT_MODE_MINIMAL +        try: +            self.op_export(pattern, mode, data) +            data.seek(0, os.SEEK_SET) +            pk_result = data.read() +        except GPGMEError as e: +            pk_result = e + +        if len(pk_result) > 0: +            result = pk_result +        else: +            result = None + +        return result + +    def key_export_secret(self, pattern=None): +        """Export secret keys. + +        Exports secret keys matching the pattern specified.  If no +        pattern is specified then exports or attempts to export all +        available secret keys. + +        IMPORTANT: Each secret key to be exported will prompt for its +        passphrase via an invocation of pinentry and gpg-agent.  If the +        passphrase is not entered or does not match then no data will be +        exported.  This is the same result as when specifying a pattern +        that is not matched by the available keys. + +        Keyword arguments: +        pattern	-- return keys matching pattern (default: all keys) + +        Returns: +                -- On success a key block containing one or more OpenPGP +                   secret keys in either ASCII armoured or binary format +                   as determined by the Context(). +                -- On failure while not raising an exception, returns None. + +        Raises: +        GPGMEError     -- as signaled by the underlying library. +        """ +        data = Data() +        mode = gpgme.GPGME_EXPORT_MODE_SECRET +        try: +            self.op_export(pattern, mode, data) +            data.seek(0, os.SEEK_SET) +            sk_result = data.read() +        except GPGMEError as e: +            sk_result = e + +        if len(sk_result) > 0: +            result = sk_result +        else: +            result = None + +        return result + +    def keylist(self, +                pattern=None, +                secret=False,                  mode=constants.keylist.mode.LOCAL,                  source=None):          """List keys @@ -544,9 +727,17 @@ class Context(GpgmeWrapper):              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): +    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. @@ -583,9 +774,10 @@ class Context(GpgmeWrapper):          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) +        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 @@ -598,22 +790,26 @@ class Context(GpgmeWrapper):              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))) +            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 is 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 @@ -622,8 +818,15 @@ class Context(GpgmeWrapper):          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): +    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 @@ -659,7 +862,8 @@ class Context(GpgmeWrapper):          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) +        passphrase   -- protect the subkey with a passphrase (default: no +                        passphrase)          Returns:                       -- an object describing the result of the subkey creation @@ -672,20 +876,23 @@ class Context(GpgmeWrapper):              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))) +            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 is None else 0) | +                 (0 if expires else constants.create.NOEXPIRE)))          finally:              if util.is_a_string(passphrase):                  self.pinentry_mode = old_pinentry_mode @@ -745,8 +952,8 @@ class Context(GpgmeWrapper):          """          flags = 0 -        if uids == None or util.is_a_string(uids): -            pass#through unchanged +        if uids is None or util.is_a_string(uids): +            pass  # through unchanged          else:              flags |= constants.keysign.LFSEP              uids = "\n".join(uids) @@ -771,8 +978,11 @@ class Context(GpgmeWrapper):          """          self.op_tofu_policy(key, policy) -    def assuan_transact(self, command, -                        data_cb=None, inquire_cb=None, status_cb=None): +    def assuan_transact(self, +                        command, +                        data_cb=None, +                        inquire_cb=None, +                        status_cb=None):          """Issue a raw assuan command          This function can be used to issue a raw assuan command to the @@ -803,12 +1013,10 @@ class Context(GpgmeWrapper):          errptr = gpgme.new_gpgme_error_t_p()          err = gpgme.gpgme_op_assuan_transact_ext( -            self.wrapped, -            cmd, -            (weakref.ref(self), data_cb) if data_cb else None, -            (weakref.ref(self), inquire_cb) if inquire_cb else None, -            (weakref.ref(self), status_cb) if status_cb else None, -            errptr) +            self.wrapped, cmd, (weakref.ref(self), data_cb) +            if data_cb else None, (weakref.ref(self), inquire_cb) +            if inquire_cb else None, (weakref.ref(self), status_cb) +            if status_cb else None, errptr)          if self._callback_excinfo:              gpgme.gpg_raise_callback_exception(self) @@ -836,10 +1044,10 @@ class Context(GpgmeWrapper):          GPGMEError	-- as signaled by the underlying library          """ -        if key == None: +        if key is None:              raise ValueError("First argument cannot be None") -        if sink == None: +        if sink is None:              sink = Data()          if fnc_value: @@ -847,8 +1055,8 @@ class Context(GpgmeWrapper):          else:              opaquedata = (weakref.ref(self), func) -        result = gpgme.gpgme_op_interact(self.wrapped, key, flags, -                                         opaquedata, sink) +        result = gpgme.gpgme_op_interact(self.wrapped, key, flags, opaquedata, +                                         sink)          if self._callback_excinfo:              gpgme.gpg_raise_callback_exception(self)          errorcheck(result) @@ -857,6 +1065,7 @@ class Context(GpgmeWrapper):      def signers(self):          """Keys used for signing"""          return [self.signers_enum(i) for i in range(self.signers_count())] +      @signers.setter      def signers(self, signers):          old = self.signers @@ -872,6 +1081,7 @@ class Context(GpgmeWrapper):      def pinentry_mode(self):          """Pinentry mode"""          return self.get_pinentry_mode() +      @pinentry_mode.setter      def pinentry_mode(self, value):          self.set_pinentry_mode(value) @@ -880,6 +1090,7 @@ class Context(GpgmeWrapper):      def protocol(self):          """Protocol to use"""          return self.get_protocol() +      @protocol.setter      def protocol(self, value):          errorcheck(gpgme.gpgme_engine_check_version(value)) @@ -889,6 +1100,7 @@ class Context(GpgmeWrapper):      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) @@ -901,24 +1113,15 @@ class Context(GpgmeWrapper):          # 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_ctx_set_engine_info', -                    'gpgme_signers_add', -                    'gpgme_sig_notation_add', -                    'gpgme_set_sender', -                    'gpgme_cancel', -                    'gpgme_cancel_async', -                    'gpgme_get_key', +        # | 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_ctx_set_engine_info', 'gpgme_signers_add', +                    'gpgme_sig_notation_add', 'gpgme_set_sender', +                    'gpgme_cancel', 'gpgme_cancel_async', 'gpgme_get_key'                  })      _boolean_properties = {'armor', 'textmode', 'offline'} @@ -938,6 +1141,7 @@ class Context(GpgmeWrapper):      # Implement the context manager protocol.      def __enter__(self):          return self +      def __exit__(self, type, value, tb):          self.__del__() @@ -1032,10 +1236,10 @@ class Context(GpgmeWrapper):          Please see the GPGME manual for more information.          """ -        if func == None: +        if func is None:              hookdata = None          else: -            if hook == None: +            if hook is None:                  hookdata = (weakref.ref(self), func)              else:                  hookdata = (weakref.ref(self), func, hook) @@ -1057,10 +1261,10 @@ class Context(GpgmeWrapper):          Please see the GPGME manual for more information.          """ -        if func == None: +        if func is None:              hookdata = None          else: -            if hook == None: +            if hook is None:                  hookdata = (weakref.ref(self), func)              else:                  hookdata = (weakref.ref(self), func, hook) @@ -1081,10 +1285,10 @@ class Context(GpgmeWrapper):          Please see the GPGME manual for more information.          """ -        if func == None: +        if func is None:              hookdata = None          else: -            if hook == None: +            if hook is None:                  hookdata = (weakref.ref(self), func)              else:                  hookdata = (weakref.ref(self), func, hook) @@ -1152,8 +1356,8 @@ class Context(GpgmeWrapper):          magic numbers will break as a result.          """ -        warnings.warn("Call to deprecated method op_edit.", -                      category=DeprecationWarning) +        warnings.warn( +            "Call to deprecated method op_edit.", category=DeprecationWarning)          return self.interact(key, func, sink=out, fnc_value=fnc_value) @@ -1182,7 +1386,8 @@ class Data(GpgmeWrapper):          # 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/'\\*/'/" +        #   | awk "/\(gpgme_data_t/ { printf (\"'%s',\\n\", \$2) } " \ +        #   | sed "s/'\\*/'/"          return name not in {              'gpgme_data_read',              'gpgme_data_write', @@ -1194,8 +1399,13 @@ class Data(GpgmeWrapper):              'gpgme_data_identify',          } -    def __init__(self, string=None, file=None, offset=None, -                 length=None, cbs=None, copy=True): +    def __init__(self, +                 string=None, +                 file=None, +                 offset=None, +                 length=None, +                 cbs=None, +                 copy=True):          """Initialize a new gpgme_data_t object.          If no args are specified, make it an empty object. @@ -1239,13 +1449,13 @@ class Data(GpgmeWrapper):          super(Data, self).__init__(None)          self.data_cbs = None -        if cbs != None: +        if cbs is not None:              self.new_from_cbs(*cbs) -        elif string != None: +        elif string is not None:              self.new_from_mem(string, copy) -        elif file != None and offset != None and length != None: +        elif file is not None and offset is not None and length is not None:              self.new_from_filepart(file, offset, length) -        elif file != None: +        elif file is not None:              if util.is_a_string(file):                  self.new_from_file(file, copy)              else: @@ -1258,7 +1468,7 @@ class Data(GpgmeWrapper):              # At interpreter shutdown, gpgme is set to NONE.              return -        if self.wrapped != None and gpgme.gpgme_data_release: +        if self.wrapped is not None and gpgme.gpgme_data_release:              gpgme.gpgme_data_release(self.wrapped)              if self._callback_excinfo:                  gpgme.gpg_raise_callback_exception(self) @@ -1268,6 +1478,7 @@ class Data(GpgmeWrapper):      # Implement the context manager protocol.      def __enter__(self):          return self +      def __exit__(self, type, value, tb):          self.__del__() @@ -1282,7 +1493,8 @@ class Data(GpgmeWrapper):      def new_from_mem(self, string, copy=True):          tmp = gpgme.new_gpgme_data_t_p() -        errorcheck(gpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy)) +        errorcheck( +            gpgme.gpgme_data_new_from_mem(tmp, string, len(string), copy))          self.wrapped = gpgme.gpgme_data_t_p_value(tmp)          gpgme.delete_gpgme_data_t_p(tmp) @@ -1300,12 +1512,12 @@ class Data(GpgmeWrapper):      def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):          tmp = gpgme.new_gpgme_data_t_p() -        if hook != None: -            hookdata = (weakref.ref(self), -                        read_cb, write_cb, seek_cb, release_cb, hook) +        if hook is not None: +            hookdata = (weakref.ref(self), read_cb, write_cb, seek_cb, +                        release_cb, hook)          else: -            hookdata = (weakref.ref(self), -                        read_cb, write_cb, seek_cb, release_cb) +            hookdata = (weakref.ref(self), read_cb, write_cb, seek_cb, +                        release_cb)          gpgme.gpg_data_new_from_cbs(self, hookdata, tmp)          self.wrapped = gpgme.gpgme_data_t_p_value(tmp)          gpgme.delete_gpgme_data_t_p(tmp) @@ -1327,12 +1539,13 @@ class Data(GpgmeWrapper):              filename = file          else:              fp = gpgme.fdopen(file.fileno(), file.mode) -            if fp == None: -                raise ValueError("Failed to open file from %s arg %s" % \ -                      (str(type(file)), str(file))) +            if fp is None: +                raise ValueError("Failed to open file from %s arg %s" % (str( +                    type(file)), str(file))) -        errorcheck(gpgme.gpgme_data_new_from_filepart(tmp, filename, fp, -                                                      offset, length)) +        errorcheck( +            gpgme.gpgme_data_new_from_filepart(tmp, filename, fp, offset, +                                               length))          self.wrapped = gpgme.gpgme_data_t_p_value(tmp)          gpgme.delete_gpgme_data_t_p(tmp) @@ -1365,7 +1578,7 @@ class Data(GpgmeWrapper):                  raise GPGMEError.fromSyserror()          return written -    def read(self, size = -1): +    def read(self, size=-1):          """Read at most size bytes, returned as bytes.          If the size argument is negative or omitted, read until EOF is reached. @@ -1400,6 +1613,7 @@ class Data(GpgmeWrapper):                  chunks.append(result)              return b''.join(chunks) +  def pubkey_algo_string(subkey):      """Return short algorithm string @@ -1412,6 +1626,7 @@ def pubkey_algo_string(subkey):      """      return gpgme.gpgme_pubkey_algo_string(subkey) +  def pubkey_algo_name(algo):      """Return name of public key algorithm @@ -1424,6 +1639,7 @@ def pubkey_algo_name(algo):      """      return gpgme.gpgme_pubkey_algo_name(algo) +  def hash_algo_name(algo):      """Return name of hash algorithm @@ -1436,6 +1652,7 @@ def hash_algo_name(algo):      """      return gpgme.gpgme_hash_algo_name(algo) +  def get_protocol_name(proto):      """Get protocol description @@ -1447,6 +1664,7 @@ def get_protocol_name(proto):      """      return gpgme.gpgme_get_protocol_name(proto) +  def addrspec_from_uid(uid):      """Return the address spec @@ -1458,22 +1676,26 @@ def addrspec_from_uid(uid):      """      return gpgme.gpgme_addrspec_from_uid(uid) +  def check_version(version=None):      return gpgme.gpgme_check_version(version) +  # check_version also makes sure that several subsystems are properly  # initialized, and it must be run at least once before invoking any  # other function.  We do it here so that the user does not have to do  # it unless she really wants to check for a certain version.  check_version() -def engine_check_version (proto): + +def engine_check_version(proto):      try:          errorcheck(gpgme.gpgme_engine_check_version(proto))          return True      except errors.GPGMEError:          return False +  def get_engine_info():      ptr = gpgme.new_gpgme_engine_info_t_p()      try: @@ -1484,6 +1706,7 @@ def get_engine_info():      gpgme.delete_gpgme_engine_info_t_p(ptr)      return info +  def set_engine_info(proto, file_name, home_dir=None):      """Changes the default configuration of the crypto engine implementing      the protocol 'proto'. 'file_name' is the file name of @@ -1492,10 +1715,12 @@ def set_engine_info(proto, file_name, home_dir=None):      used if omitted)."""      errorcheck(gpgme.gpgme_set_engine_info(proto, file_name, home_dir)) +  def set_locale(category, value):      """Sets the default locale used by contexts"""      errorcheck(gpgme.gpgme_set_locale(None, category, value)) +  def wait(hang):      """Wait for asynchronous call on any Context  to finish.      Wait forever if hang is True. @@ -1509,7 +1734,7 @@ def wait(hang):      context = gpgme.gpgme_wait(None, ptr, hang)      status = gpgme.gpgme_error_t_p_value(ptr)      gpgme.delete_gpgme_error_t_p(ptr) -    if context == None: +    if context is None:          errorcheck(status)      else:          context = Context(context) diff --git a/lang/python/src/errors.py b/lang/python/src/errors.py index c41ac692..9c7f0378 100644 --- a/lang/python/src/errors.py +++ b/lang/python/src/errors.py @@ -17,11 +17,12 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  from . import gpgme  from . import util +del absolute_import, print_function, unicode_literals +  # To appease static analysis tools, we define some constants here.  # They are overwritten with the proper values by process_constants.  NO_ERROR = None @@ -30,6 +31,7 @@ EOF = None  util.process_constants('GPG_ERR_', globals())  del util +  class GpgError(Exception):      """A GPG Error @@ -55,6 +57,7 @@ class GpgError(Exception):      exception objects.      """ +      def __init__(self, error=None, context=None, results=None):          self.error = error          self.context = context @@ -62,37 +65,38 @@ class GpgError(Exception):      @property      def code(self): -        if self.error == None: +        if self.error is None:              return None          return gpgme.gpgme_err_code(self.error)      @property      def code_str(self): -        if self.error == None: +        if self.error is None:              return None          return gpgme.gpgme_strerror(self.error)      @property      def source(self): -        if self.error == None: +        if self.error is None:              return None          return gpgme.gpgme_err_source(self.error)      @property      def source_str(self): -        if self.error == None: +        if self.error is None:              return None          return gpgme.gpgme_strsource(self.error)      def __str__(self):          msgs = [] -        if self.context != None: +        if self.context is not None:              msgs.append(self.context) -        if self.error != None: +        if self.error is not None:              msgs.append(self.source_str)              msgs.append(self.code_str)          return ': '.join(msgs) +  class GPGMEError(GpgError):      '''Generic error @@ -101,24 +105,30 @@ class GPGMEError(GpgError):      returns an error.  This is the error that was used in PyME.      ''' +      @classmethod      def fromSyserror(cls):          return cls(gpgme.gpgme_err_code_from_syserror()) +      @property      def message(self):          return self.context +      def getstring(self):          return str(self) +      def getcode(self):          return self.code +      def getsource(self):          return self.source -def errorcheck(retval, extradata = None): +def errorcheck(retval, extradata=None):      if retval:          raise GPGMEError(retval, extradata) +  class KeyNotFound(GPGMEError, KeyError):      """Raised if a key was not found @@ -127,63 +137,76 @@ class KeyNotFound(GPGMEError, KeyError):      indicating EOF, and a KeyError.      """ +      def __init__(self, keystr):          self.keystr = keystr          GPGMEError.__init__(self, EOF) +      def __str__(self):          return self.keystr +  # These errors are raised in the idiomatic interface code. +  class EncryptionError(GpgError):      pass +  class InvalidRecipients(EncryptionError):      def __init__(self, recipients, **kwargs):          EncryptionError.__init__(self, **kwargs)          self.recipients = recipients +      def __str__(self): -        return ", ".join("{}: {}".format(r.fpr, -                                         gpgme.gpgme_strerror(r.reason)) +        return ", ".join("{}: {}".format(r.fpr, gpgme.gpgme_strerror(r.reason))                           for r in self.recipients) +  class DeryptionError(GpgError):      pass +  class UnsupportedAlgorithm(DeryptionError):      def __init__(self, algorithm, **kwargs):          DeryptionError.__init__(self, **kwargs)          self.algorithm = algorithm +      def __str__(self):          return self.algorithm +  class SigningError(GpgError):      pass +  class InvalidSigners(SigningError):      def __init__(self, signers, **kwargs):          SigningError.__init__(self, **kwargs)          self.signers = signers +      def __str__(self): -        return ", ".join("{}: {}".format(s.fpr, -                                         gpgme.gpgme_strerror(s.reason)) +        return ", ".join("{}: {}".format(s.fpr, gpgme.gpgme_strerror(s.reason))                           for s in self.signers) +  class VerificationError(GpgError):      def __init__(self, result, **kwargs):          GpgError.__init__(self, **kwargs)          self.result = result +  class BadSignatures(VerificationError):      def __str__(self): -        return ", ".join("{}: {}".format(s.fpr, -                                         gpgme.gpgme_strerror(s.status)) +        return ", ".join("{}: {}".format(s.fpr, gpgme.gpgme_strerror(s.status))                           for s in self.result.signatures                           if s.status != NO_ERROR) +  class MissingSignatures(VerificationError):      def __init__(self, result, missing, **kwargs):          VerificationError.__init__(self, result, **kwargs)          self.missing = missing +      def __str__(self):          return ", ".join(k.subkeys[0].fpr for k in self.missing) diff --git a/lang/python/src/results.py b/lang/python/src/results.py index bfd0f683..6b5f63c2 100644 --- a/lang/python/src/results.py +++ b/lang/python/src/results.py @@ -19,7 +19,6 @@  from __future__ import absolute_import, print_function, unicode_literals  del absolute_import, print_function, unicode_literals -  """Robust result objects  Results returned by the underlying library are fragile, i.e. they are @@ -30,23 +29,28 @@ therefore create deep copies of the results.  """ +  class Result(object):      """Result object      Describes the result of an operation.      """ -      """Convert to types"""      _type = {} -      """Map functions over list attributes"""      _map = {} -      """Automatically copy unless blacklisted"""      _blacklist = { -        'acquire', 'append', 'disown', 'next', 'own', 'this', 'thisown', +        'acquire', +        'append', +        'disown', +        'next', +        'own', +        'this', +        'thisown',      } +      def __init__(self, fragile):          for key, func in self._type.items():              if hasattr(fragile, key): @@ -67,52 +71,67 @@ class Result(object):      def __repr__(self):          return '{}({})'.format(              self.__class__.__name__, -            ', '.join('{}={!r}'.format(k, getattr(self, k)) -                      for k in dir(self) if not k.startswith('_'))) +            ', '.join('{}={!r}'.format(k, getattr(self, k)) for k in dir(self) +                      if not k.startswith('_'))) +  class InvalidKey(Result):      pass +  class EncryptResult(Result):      _map = dict(invalid_recipients=InvalidKey) +  class Recipient(Result):      pass +  class DecryptResult(Result):      _type = dict(wrong_key_usage=bool, is_de_vs=bool)      _map = dict(recipients=Recipient) +  class NewSignature(Result):      pass +  class SignResult(Result):      _map = dict(invalid_signers=InvalidKey, signatures=NewSignature) +  class Notation(Result):      pass +  class Signature(Result):      _type = dict(wrong_key_usage=bool, chain_model=bool, is_de_vs=bool)      _map = dict(notations=Notation) +  class VerifyResult(Result):      _map = dict(signatures=Signature) +  class ImportStatus(Result):      pass +  class ImportResult(Result):      _map = dict(imports=ImportStatus) +  class GenkeyResult(Result):      _type = dict(primary=bool, sub=bool) +  class KeylistResult(Result):      _type = dict(truncated=bool) +  class VFSMountResult(Result):      pass +  class EngineInfo(Result):      pass diff --git a/lang/python/src/util.py b/lang/python/src/util.py index e4fca4c1..320a823e 100644 --- a/lang/python/src/util.py +++ b/lang/python/src/util.py @@ -17,10 +17,12 @@  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function, unicode_literals -del absolute_import, print_function, unicode_literals  import sys +del absolute_import, print_function, unicode_literals + +  def process_constants(prefix, scope):      """Called by the constant modules to load up the constants from the C      library starting with PREFIX.  Matching constants will be inserted @@ -30,17 +32,19 @@ def process_constants(prefix, scope):      """      from . import gpgme      index = len(prefix) -    constants = {identifier[index:]: getattr(gpgme, identifier) -                 for identifier in dir(gpgme) -                 if identifier.startswith(prefix)} +    constants = { +        identifier[index:]: getattr(gpgme, identifier) +        for identifier in dir(gpgme) if identifier.startswith(prefix) +    }      scope.update(constants)      return list(constants.keys()) +  def percent_escape(s): -    return ''.join( -        '%{0:2x}'.format(ord(c)) -        if c == '+' or c == '"' or c == '%' or ord(c) <= 0x20 else c -        for c in s) +    return ''.join('%{0:2x}'.format(ord(c)) +                   if c == '+' or c == '"' or c == '%' or ord(c) <= 0x20 else c +                   for c in s) +  # Python2/3 compatibility  if sys.version_info[0] == 3: diff --git a/lang/python/tests/Makefile.am b/lang/python/tests/Makefile.am index 3864f8ba..62970765 100644 --- a/lang/python/tests/Makefile.am +++ b/lang/python/tests/Makefile.am @@ -21,7 +21,8 @@ GPG_AGENT = gpg-agent  test_srcdir = $(top_srcdir)/tests/gpg -TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) \ +GNUPGHOME=$(abs_builddir) \ +TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) \  	LC_ALL=C GPG_AGENT_INFO= \  	top_srcdir=$(top_srcdir) \  	srcdir=$(srcdir) \ diff --git a/lang/python/tests/final.py b/lang/python/tests/final.py index 65375cb8..d0d52dc4 100755 --- a/lang/python/tests/final.py +++ b/lang/python/tests/final.py @@ -18,12 +18,15 @@  # 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 subprocess  import support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals -subprocess.check_call([os.path.join(os.getenv('top_srcdir'), -                                    "tests", "start-stop-agent"), "--stop"]) +subprocess.check_call([ +    os.path.join(os.getenv('top_srcdir'), "tests", "start-stop-agent"), +    "--stop" +]) diff --git a/lang/python/tests/initial.py b/lang/python/tests/initial.py index 49e4f82e..30a8de7a 100755 --- a/lang/python/tests/initial.py +++ b/lang/python/tests/initial.py @@ -18,17 +18,20 @@  # 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 subprocess  import gpg  import support +del absolute_import, print_function, unicode_literals +  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"]) +subprocess.check_call([ +    os.path.join(os.getenv('top_srcdir'), "tests", "start-stop-agent"), +    "--start" +])  with gpg.Context() as c:      alpha = c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False) diff --git a/lang/python/tests/run-tests.py b/lang/python/tests/run-tests.py index 95df1978..cec13b5e 100644 --- a/lang/python/tests/run-tests.py +++ b/lang/python/tests/run-tests.py @@ -17,10 +17,8 @@  # 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 -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals +from __future__ import absolute_import, division +from __future__ import print_function, unicode_literals  import argparse  import glob @@ -28,34 +26,50 @@ import os  import subprocess  import sys +del absolute_import, division, print_function, unicode_literals + +  class SplitAndAccumulate(argparse.Action):      def __call__(self, parser, namespace, values, option_string=None):          current = getattr(namespace, self.dest, list())          current.extend(values.split())          setattr(namespace, self.dest, current) +  parser = argparse.ArgumentParser(description='Run tests.') -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, ' + -                    'separated by spaces.') -parser.add_argument('--srcdir', type=str, -                    default=os.environ.get("srcdir", ""), -                    help='Location of the tests.') -parser.add_argument('--builddir', type=str, -                    default=os.environ.get("abs_builddir", ""), -                    help='Location of the tests.') -parser.add_argument('--python-libdir', type=str, -                    default=None, -                    help='Optional location of the in-tree module lib directory.') -parser.add_argument('--parallel', action="store_true", default=False, -                    help='Ignored.  For compatibility with run-tests.scm.') +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, ' + 'separated by spaces.') +parser.add_argument( +    '--srcdir', +    type=str, +    default=os.environ.get("srcdir", ""), +    help='Location of the tests.') +parser.add_argument( +    '--builddir', +    type=str, +    default=os.environ.get("abs_builddir", ""), +    help='Location of the tests.') +parser.add_argument( +    '--python-libdir', +    type=str, +    default=None, +    help='Optional location of the in-tree module lib directory.') +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: @@ -64,26 +78,31 @@ if not args.interpreters:  out = sys.stdout if args.verbose else None  err = sys.stderr if args.verbose else None +  def status_to_str(code):      return {0: "PASS", 77: "SKIP", 99: "ERROR"}.get(code, "FAIL") +  results = list()  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() +    version = subprocess.check_output([ +        interpreter, "-c", +        "import sys; print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" +    ]).strip().decode()      if args.python_libdir:          python_libdir = args.python_libdir      else: -        pattern = os.path.join(args.builddir, "..", -                               "{0}-gpg".format(os.path.basename(interpreter)), -                               "lib*") +        pattern = os.path.join(args.builddir, "..", "{0}-gpg".format( +            os.path.basename(interpreter)), "lib*")          libdirs = glob.glob(pattern)          if len(libdirs) == 0: -            sys.exit("Build directory matching {0!r} not found.".format(pattern)) +            sys.exit( +                "Build directory matching {0!r} not found.".format(pattern))          elif len(libdirs) > 1: -            sys.exit("Multiple build directories matching {0!r} found: {1}".format( -                pattern, libdirs)) +            sys.exit( +                "Multiple build directories matching {0!r} found: {1}".format( +                    pattern, libdirs))          python_libdir = libdirs[0]      env = dict(os.environ) @@ -95,16 +114,22 @@ for interpreter in args.interpreters:      for test in args.tests:          status = subprocess.call(              [interpreter, os.path.join(args.srcdir, test)], -            env=env, stdout=out, stderr=err) +            env=env, +            stdout=out, +            stderr=err)          if not args.quiet:              print("{0}: {1}".format(status_to_str(status), test))          results.append(status) +  def count(status):      return len(list(filter(lambda x: x == status, results))) + +  def failed():      return len(list(filter(lambda x: x not in (0, 77, 99), results))) +  if not args.quiet:      print("{0} tests run, {1} succeeded, {2} failed, {3} skipped.".format(          len(results), count(0), failed(), count(77))) diff --git a/lang/python/tests/support.py b/lang/python/tests/support.py index efccf315..e6b3d8bb 100644 --- a/lang/python/tests/support.py +++ b/lang/python/tests/support.py @@ -16,7 +16,6 @@  # 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 contextlib  import shutil @@ -27,20 +26,28 @@ import tempfile  import time  import gpg +del absolute_import, print_function, unicode_literals + +  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) +        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) +  def have_tofu_support(ctx, some_uid): -    keys = list(ctx.keylist(some_uid, -                            mode=(gpg.constants.keylist.mode.LOCAL -                                  |gpg.constants.keylist.mode.WITH_TOFU))) +    keys = list( +        ctx.keylist( +            some_uid, +            mode=(gpg.constants.keylist.mode.LOCAL | +                  gpg.constants.keylist.mode.WITH_TOFU)))      return len(keys) > 0 +  # 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. @@ -53,13 +60,18 @@ encrypt_only = "F52770D5C4DB41408D918C9F920572769B9FE19C"  sign_only = "7CCA20CCDE5394CEE71C9F0BFED153F12F18F45D"  no_such_key = "A" * 40 +  def make_filename(name):      return os.path.join(os.environ['top_srcdir'], 'tests', 'gpg', name) +  def in_srcdir(name):      return os.path.join(os.environ['srcdir'], name) +  verbose = int(os.environ.get('verbose', 0)) > 1 + +  def print_data(data):      if verbose:          try: @@ -75,10 +87,12 @@ def print_data(data):          else:              sys.stdout.write(data) +  def mark_key_trusted(ctx, key):      class Editor(object):          def __init__(self):              self.steps = ["trust", "save"] +          def edit(self, status, args, out):              if args == "keyedit.prompt":                  result = self.steps.pop(0) @@ -91,6 +105,7 @@ def mark_key_trusted(ctx, key):              else:                  result = None              return result +      with gpg.Data() as sink:          ctx.op_edit(key, Editor().edit, sink, sink) @@ -103,9 +118,11 @@ 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: @@ -124,7 +141,7 @@ def EphemeralContext():                  ctx.assuan_transact(["KILLAGENT"])              except gpg.errors.GPGMEError as e:                  if e.getcode() == gpg.errors.ASS_CONNECT_FAILED: -                    pass # the agent was not running +                    pass  # the agent was not running                  else:                      raise diff --git a/lang/python/tests/t-callbacks.py b/lang/python/tests/t-callbacks.py index b311e3d4..25a1c238 100755 --- a/lang/python/tests/t-callbacks.py +++ b/lang/python/tests/t-callbacks.py @@ -18,12 +18,13 @@  # 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 # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  c = gpg.Context()  c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK) @@ -33,6 +34,7 @@ sink = gpg.Data()  # Valid passphrases, both as string and bytes.  for passphrase in ('foo', b'foo'): +      def passphrase_cb(hint, desc, prev_bad, hook=None):          assert hook == passphrase          return hook @@ -40,10 +42,12 @@ for passphrase in ('foo', b'foo'):      c.set_passphrase_cb(passphrase_cb, passphrase)      c.op_encrypt([], 0, source, sink) +  # Returning an invalid type.  def passphrase_cb(hint, desc, prev_bad, hook=None):      return 0 +  c.set_passphrase_cb(passphrase_cb, None)  try:      c.op_encrypt([], 0, source, sink) @@ -55,9 +59,12 @@ else:  # Raising an exception inside callback.  myException = Exception() + +  def passphrase_cb(hint, desc, prev_bad, hook=None):      raise myException +  c.set_passphrase_cb(passphrase_cb, None)  try:      c.op_encrypt([], 0, source, sink) @@ -66,10 +73,12 @@ except Exception as e:  else:      assert False, "Expected an error, got none" +  # Wrong kind of callback function.  def bad_passphrase_cb():      pass +  c.set_passphrase_cb(bad_passphrase_cb, None)  try:      c.op_encrypt([], 0, source, sink) @@ -78,8 +87,6 @@ except Exception as e:  else:      assert False, "Expected an error, got none" - -  # Test the progress callback.  parms = """<GnupgKeyParms format="internal">  Key-Type: RSA @@ -93,21 +100,26 @@ Expire-Date: 2099-12-31  """  messages = [] + +  def progress_cb(what, typ, current, total, hook=None):      assert hook == messages      messages.append(          "PROGRESS UPDATE: what = {}, type = {}, current = {}, total = {}"          .format(what, typ, current, total)) +  c = gpg.Context()  c.set_progress_cb(progress_cb, messages)  c.op_genkey(parms, None, None)  assert len(messages) > 0 +  # Test exception handling.  def progress_cb(what, typ, current, total, hook=None):      raise myException +  c = gpg.Context()  c.set_progress_cb(progress_cb, None)  try: @@ -117,7 +129,6 @@ except Exception as e:  else:      assert False, "Expected an error, got none" -  # Test the edit callback.  c = gpg.Context()  c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK) @@ -127,11 +138,15 @@ alpha = c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False)  cookie = object()  edit_cb_called = False + +  def edit_cb(status, args, hook):      global edit_cb_called      edit_cb_called = True      assert hook == cookie      return "quit" if args == "keyedit.prompt" else None + +  c.op_edit(alpha, edit_cb, cookie, sink)  assert edit_cb_called @@ -141,8 +156,11 @@ c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)  c.set_passphrase_cb(lambda *args: "abc")  sink = gpg.Data() +  def edit_cb(status, args):      raise myException + +  try:      c.op_edit(alpha, edit_cb, None, sink)  except Exception as e: @@ -150,18 +168,19 @@ except Exception as e:  else:      assert False, "Expected an error, got none" - -  # Test the status callback.  source = gpg.Data("Hallo Leute\n")  sink = gpg.Data()  status_cb_called = False + +  def status_cb(keyword, args, hook=None):      global status_cb_called      status_cb_called = True      assert hook == cookie +  c = gpg.Context()  c.set_status_cb(status_cb, cookie)  c.set_ctx_flag("full-status", "1") @@ -172,9 +191,11 @@ assert status_cb_called  source = gpg.Data("Hallo Leute\n")  sink = gpg.Data() +  def status_cb(keyword, args):      raise myException +  c = gpg.Context()  c.set_status_cb(status_cb, None)  c.set_ctx_flag("full-status", "1") @@ -186,13 +207,16 @@ else:      assert False, "Expected an error, got none" -  # Test the data callbacks.  def read_cb(amount, hook=None):      assert hook == cookie      return 0 + +  def release_cb(hook=None):      assert hook == cookie + +  data = gpg.Data(cbs=(read_cb, None, None, release_cb, cookie))  try:      data.read() @@ -201,8 +225,11 @@ except Exception as e:  else:      assert False, "Expected an error, got none" +  def read_cb(amount):      raise myException + +  data = gpg.Data(cbs=(read_cb, None, None, lambda: None))  try:      data.read() @@ -215,6 +242,8 @@ else:  def write_cb(what, hook=None):      assert hook == cookie      return "wrong type" + +  data = gpg.Data(cbs=(None, write_cb, None, release_cb, cookie))  try:      data.write(b'stuff') @@ -223,8 +252,11 @@ except Exception as e:  else:      assert False, "Expected an error, got none" +  def write_cb(what):      raise myException + +  data = gpg.Data(cbs=(None, write_cb, None, lambda: None))  try:      data.write(b'stuff') @@ -237,6 +269,8 @@ else:  def seek_cb(offset, whence, hook=None):      assert hook == cookie      return "wrong type" + +  data = gpg.Data(cbs=(None, None, seek_cb, release_cb, cookie))  try:      data.seek(0, os.SEEK_SET) @@ -245,8 +279,11 @@ except Exception as e:  else:      assert False, "Expected an error, got none" +  def seek_cb(offset, whence):      raise myException + +  data = gpg.Data(cbs=(None, None, seek_cb, lambda: None))  try:      data.seek(0, os.SEEK_SET) diff --git a/lang/python/tests/t-data.py b/lang/python/tests/t-data.py index 5cf074c5..006c11f4 100755 --- a/lang/python/tests/t-data.py +++ b/lang/python/tests/t-data.py @@ -18,14 +18,15 @@  # 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 io  import os  import tempfile  import gpg  import support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  data = gpg.Data('Hello world!')  assert data.read() == b'Hello world!' @@ -94,7 +95,8 @@ with tempfile.NamedTemporaryFile() as tmp:      # Open using name, offset, and length.      data = gpg.Data(file=tmp.name, offset=23, length=42) -    assert data.read() == binjunk[23:23+42] +    assert data.read() == binjunk[23:23 + 42] +  # Test callbacks.  class DataObject(object): @@ -118,6 +120,7 @@ class DataObject(object):          assert not self.released          self.released = True +  do = DataObject()  cookie = object()  data = gpg.Data(cbs=(do.read, do.write, do.seek, do.release, cookie)) diff --git a/lang/python/tests/t-decrypt-verify.py b/lang/python/tests/t-decrypt-verify.py index 03bbc4b5..fcaa1346 100755 --- a/lang/python/tests/t-decrypt-verify.py +++ b/lang/python/tests/t-decrypt-verify.py @@ -18,11 +18,13 @@  # 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 support +del absolute_import, print_function, unicode_literals + +  def check_verify_result(result, summary, fpr, status):      assert len(result.signatures) == 1, "Unexpected number of signatures"      sig = result.signatures[0] @@ -32,7 +34,9 @@ def check_verify_result(result, summary, fpr, status):      assert len(sig.notations) == 0      assert not sig.wrong_key_usage      assert sig.validity == gpg.constants.validity.FULL -    assert gpg.errors.GPGMEError(sig.validity_reason).getcode() == gpg.errors.NO_ERROR +    assert gpg.errors.GPGMEError( +        sig.validity_reason).getcode() == gpg.errors.NO_ERROR +  c = gpg.Context() @@ -47,10 +51,9 @@ assert not result.unsupported_algorithm, \  support.print_data(sink)  verify_result = c.op_verify_result() -check_verify_result(verify_result, -                    gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, -                    "A0FF4590BB6122EDEF6E3C542D727CC768697734", -                    gpg.errors.NO_ERROR) +check_verify_result( +    verify_result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, +    "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR)  # Idiomatic interface.  with gpg.Context() as c: @@ -60,14 +63,13 @@ with gpg.Context() as c:          c.decrypt(open(support.make_filename("cipher-2.asc")), verify=[alpha])      assert plaintext.find(b'Wenn Sie dies lesen k') >= 0, \          'Plaintext not found' -    check_verify_result(verify_result, -                        gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, -                        "A0FF4590BB6122EDEF6E3C542D727CC768697734", -                        gpg.errors.NO_ERROR) +    check_verify_result( +        verify_result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, +        "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR)      try: -        c.decrypt(open(support.make_filename("cipher-2.asc")), -                  verify=[alpha, bob]) +        c.decrypt( +            open(support.make_filename("cipher-2.asc")), verify=[alpha, bob])      except gpg.errors.MissingSignatures as e:          assert len(e.missing) == 1          assert e.missing[0] == bob diff --git a/lang/python/tests/t-decrypt.py b/lang/python/tests/t-decrypt.py index 05b6d8b0..f2417c9a 100755 --- a/lang/python/tests/t-decrypt.py +++ b/lang/python/tests/t-decrypt.py @@ -18,11 +18,12 @@  # 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 support +del absolute_import, print_function, unicode_literals +  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 b1075a96..cbc17d95 100755 --- a/lang/python/tests/t-edit.py +++ b/lang/python/tests/t-edit.py @@ -19,13 +19,15 @@  # 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 sys  import os  import gpg  import support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals +  class KeyEditor(object):      def __init__(self): @@ -47,11 +49,12 @@ class KeyEditor(object):              result = None          if self.verbose: -            sys.stderr.write("Code: {}, args: {!r}, Returning: {!r}\n" -                             .format(status, args, result)) +            sys.stderr.write("Code: {}, args: {!r}, Returning: {!r}\n".format( +                status, args, result))          return result +  c = gpg.Context()  c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)  c.set_passphrase_cb(lambda *args: "abc") @@ -59,13 +62,15 @@ c.set_armor(True)  # The deprecated interface.  editor = KeyEditor() -c.interact(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False), -           editor.edit_fnc) +c.interact( +    c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False), +    editor.edit_fnc)  assert editor.done  # The deprecated interface.  sink = gpg.Data()  editor = KeyEditor() -c.op_edit(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False), -          editor.edit_fnc, sink, sink) +c.op_edit( +    c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False), +    editor.edit_fnc, sink, sink)  assert editor.done diff --git a/lang/python/tests/t-encrypt-large.py b/lang/python/tests/t-encrypt-large.py index 56460851..18576ac3 100755 --- a/lang/python/tests/t-encrypt-large.py +++ b/lang/python/tests/t-encrypt-large.py @@ -18,13 +18,14 @@  # 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 sys  import random  import gpg  import support +del absolute_import, print_function, unicode_literals +  if len(sys.argv) == 2:      nbytes = int(sys.argv[1])  else: @@ -33,6 +34,8 @@ else:  c = gpg.Context()  ntoread = nbytes + +  def read_cb(amount):      global ntoread      chunk = ntoread if ntoread < amount else amount @@ -41,12 +44,16 @@ def read_cb(amount):      assert chunk >= 0      return bytes(bytearray(random.randrange(256) for i in range(chunk))) +  nwritten = 0 + +  def write_cb(data):      global nwritten      nwritten += len(data)      return len(data) +  source = gpg.Data(cbs=(read_cb, None, None, lambda: None))  sink = gpg.Data(cbs=(None, write_cb, None, lambda: None)) @@ -61,5 +68,5 @@ assert not result.invalid_recipients, \  assert ntoread == 0  if support.verbose: -    sys.stderr.write( -        "plaintext={} bytes, ciphertext={} bytes\n".format(nbytes, nwritten)) +    sys.stderr.write("plaintext={} bytes, ciphertext={} bytes\n".format( +        nbytes, nwritten)) diff --git a/lang/python/tests/t-encrypt-sign.py b/lang/python/tests/t-encrypt-sign.py index f04783f4..84d1abb4 100755 --- a/lang/python/tests/t-encrypt-sign.py +++ b/lang/python/tests/t-encrypt-sign.py @@ -18,15 +18,17 @@  # 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 sys  import gpg  import support +del absolute_import, print_function, unicode_literals +  c = gpg.Context()  c.set_armor(True) +  def check_result(r, typ):      if r.invalid_signers:          sys.exit("Invalid signer found: {}".format(r.invalid_signers.fpr)) @@ -42,7 +44,8 @@ def check_result(r, typ):          sys.exit("Wrong pubkey algorithm reported: {}".format(              signature.pubkey_algo)) -    if signature.hash_algo not in (gpg.constants.md.SHA1, gpg.constants.md.RMD160): +    if signature.hash_algo not in (gpg.constants.md.SHA1, +                                   gpg.constants.md.RMD160):          sys.exit("Wrong hash algorithm reported: {}".format(              signature.hash_algo)) @@ -53,6 +56,7 @@ def check_result(r, typ):      if signature.fpr != "A0FF4590BB6122EDEF6E3C542D727CC768697734":          sys.exit("Wrong fingerprint reported: {}".format(signature.fpr)) +  keys = []  keys.append(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False))  keys.append(c.get_key("D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", False)) @@ -61,7 +65,8 @@ for recipients in (keys, []):      source = gpg.Data("Hallo Leute\n")      sink = gpg.Data() -    c.op_encrypt_sign(recipients, gpg.constants.ENCRYPT_ALWAYS_TRUST, source, sink) +    c.op_encrypt_sign(recipients, gpg.constants.ENCRYPT_ALWAYS_TRUST, source, +                      sink)      result = c.op_encrypt_result()      assert not result.invalid_recipients, \          "Invalid recipient encountered: {}".format( @@ -72,13 +77,11 @@ for recipients in (keys, []):      support.print_data(sink) -  # Idiomatic interface.  with gpg.Context(armor=True) as c:      message = "Hallo Leute\n".encode() -    ciphertext, _, sig_result = c.encrypt(message, -                                          recipients=keys, -                                          always_trust=True) +    ciphertext, _, sig_result = c.encrypt( +        message, recipients=keys, always_trust=True)      assert len(ciphertext) > 0      assert ciphertext.find(b'BEGIN PGP MESSAGE') > 0, 'Marker not found'      check_result(sig_result, gpg.constants.sig.mode.NORMAL) diff --git a/lang/python/tests/t-encrypt-sym.py b/lang/python/tests/t-encrypt-sym.py index 82992934..9b099fe0 100755 --- a/lang/python/tests/t-encrypt-sym.py +++ b/lang/python/tests/t-encrypt-sym.py @@ -18,12 +18,13 @@  # 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 # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  for passphrase in ("abc", b"abc"):      c = gpg.Context() @@ -34,6 +35,7 @@ for passphrase in ("abc", b"abc"):      cipher = gpg.Data()      passphrase_cb_called = 0 +      def passphrase_cb(hint, desc, prev_bad, hook=None):          global passphrase_cb_called          passphrase_cb_called += 1 @@ -55,7 +57,7 @@ for passphrase in ("abc", b"abc"):      c.op_decrypt(cipher, plain)      # Seems like the passphrase is cached. -    #assert passphrase_cb_called == 2, \ +    # assert passphrase_cb_called == 2, \      #    "Callback called {} times".format(passphrase_cb_called)      support.print_data(plain) @@ -70,12 +72,12 @@ for passphrase in ("abc", b"abc"):          # Check that the passphrase callback is not altered.          def f(*args):              assert False +          c.set_passphrase_cb(f)          message = "Hallo Leute\n".encode() -        ciphertext, _, _ = c.encrypt(message, -                                     passphrase=passphrase, -                                     sign=False) +        ciphertext, _, _ = c.encrypt( +            message, passphrase=passphrase, sign=False)          assert ciphertext.find(b'BEGIN PGP MESSAGE') > 0, 'Marker not found'          plaintext, _, _ = c.decrypt(ciphertext, passphrase=passphrase) diff --git a/lang/python/tests/t-encrypt.py b/lang/python/tests/t-encrypt.py index 921502a7..e702daa6 100755 --- a/lang/python/tests/t-encrypt.py +++ b/lang/python/tests/t-encrypt.py @@ -18,11 +18,12 @@  # 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 support +del absolute_import, print_function, unicode_literals +  c = gpg.Context()  c.set_armor(True) @@ -41,36 +42,37 @@ support.print_data(sink)  # Idiomatic interface.  with gpg.Context(armor=True) as c: -    ciphertext, _, _ = c.encrypt("Hallo Leute\n".encode(), -                                 recipients=keys, -                                 sign=False, -                                 always_trust=True) +    ciphertext, _, _ = c.encrypt( +        "Hallo Leute\n".encode(), +        recipients=keys, +        sign=False, +        always_trust=True)      assert len(ciphertext) > 0      assert ciphertext.find(b'BEGIN PGP MESSAGE') > 0, 'Marker not found' -    c.encrypt("Hallo Leute\n".encode(), -              recipients=[c.get_key(support.encrypt_only, False)], -              sign=False, always_trust=True) +    c.encrypt( +        "Hallo Leute\n".encode(), +        recipients=[c.get_key(support.encrypt_only, False)], +        sign=False, +        always_trust=True)      try: -        c.encrypt("Hallo Leute\n".encode(), -                  recipients=[c.get_key(support.sign_only, False)], -                  sign=False, always_trust=True) +        c.encrypt( +            "Hallo Leute\n".encode(), +            recipients=[c.get_key(support.sign_only, False)], +            sign=False, +            always_trust=True)      except gpg.errors.InvalidRecipients as e:          assert len(e.recipients) == 1          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) +        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 diff --git a/lang/python/tests/t-export.py b/lang/python/tests/t-export.py index b9d52048..6d771dd4 100755 --- a/lang/python/tests/t-export.py +++ b/lang/python/tests/t-export.py @@ -18,11 +18,12 @@  # 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 support +del absolute_import, print_function, unicode_literals +  c = gpg.Context()  c.set_armor(True) @@ -32,8 +33,8 @@ support.print_data(sink)  # Again. Now using a key array.  keys = [] -keys.append(c.get_key("0x68697734", False)) # Alpha -keys.append(c.get_key("0xA9E3B0B2", False)) # Bob +keys.append(c.get_key("0x68697734", False))  # Alpha +keys.append(c.get_key("0xA9E3B0B2", False))  # Bob  sink = gpg.Data()  c.op_export_keys(keys, 0, sink)  support.print_data(sink) diff --git a/lang/python/tests/t-file-name.py b/lang/python/tests/t-file-name.py index 32fe84a0..d9c226fa 100755 --- a/lang/python/tests/t-file-name.py +++ b/lang/python/tests/t-file-name.py @@ -18,12 +18,13 @@  # 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 # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  testname = "abcde12345" diff --git a/lang/python/tests/t-idiomatic.py b/lang/python/tests/t-idiomatic.py index b7ae4eb9..238bbf31 100755 --- a/lang/python/tests/t-idiomatic.py +++ b/lang/python/tests/t-idiomatic.py @@ -18,7 +18,6 @@  # 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 sys  import io @@ -26,7 +25,9 @@ import os  import tempfile  import gpg  import support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  # Both Context and Data can be used as context manager:  with gpg.Context() as c, gpg.Data() as d: @@ -34,8 +35,9 @@ with gpg.Context() as c, gpg.Data() as d:      d.write(b"Halloechen")      leak_c = c      leak_d = d -assert leak_c.wrapped == None -assert leak_d.wrapped == None +assert leak_c.wrapped is None +assert leak_d.wrapped is None +  def sign_and_verify(source, signed, sink):      with gpg.Context() as c: @@ -53,6 +55,7 @@ def sign_and_verify(source, signed, sink):      sink.seek(0, os.SEEK_SET)      assert sink.read() == b"Hallo Leute\n" +  # Demonstrate automatic wrapping of file-like objects with 'fileno'  # method.  with tempfile.TemporaryFile() as source, \ @@ -73,7 +76,7 @@ if sys.version_info[0] == 3:      bio.truncate(1)      if len(bio.getvalue()) != 1:          # This version of Python is affected, preallocate buffer. -        preallocate = 128*b'\x00' +        preallocate = 128 * b'\x00'      else:          preallocate = b'' diff --git a/lang/python/tests/t-import.py b/lang/python/tests/t-import.py index e2edf5a2..8d8a6998 100755 --- a/lang/python/tests/t-import.py +++ b/lang/python/tests/t-import.py @@ -1,6 +1,6 @@  #!/usr/bin/env python -# Copyright (C) 2016 g10 Code GmbH +# Copyright (C) 2016 Tobias Mueller <muelli at cryptobitch.de>  #  # This file is part of GPGME.  # @@ -18,45 +18,47 @@  # 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 support +del absolute_import, print_function, unicode_literals + +  def check_result(result, fpr, secret):      assert result.considered == 1 or (secret and result.considered == 3)      assert result.no_user_id == 0 -    assert not ((secret and result.imported != 0) -                or (not secret and (result.imported != 0 -                                    and result.imported != 1))) +    assert not ((secret and result.imported != 0) or +                (not secret and +                 (result.imported != 0 and result.imported != 1)))      assert result.imported_rsa == 0 -    assert not ((secret and (result.unchanged != 0 and result.unchanged != 1)) -                or (not secret and ((result.imported == 0 -                                     and result.unchanged != 1) -                                 or (result.imported == 1 -                                     and result.unchanged != 0)))) +    assert not ((secret and +                 (result.unchanged != 0 and result.unchanged != 1)) or +                (not secret and +                 ((result.imported == 0 and result.unchanged != 1) or +                  (result.imported == 1 and result.unchanged != 0))))      assert result.new_user_ids == 0      assert result.new_sub_keys == 0 -    assert not ((secret -                 and ((result.secret_imported == 0 -                       and result.new_signatures != 0) -                      or (result.secret_imported == 1 -                          and result.new_signatures > 1))) -                or (not secret and result.new_signatures != 0)) +    assert not ((secret and ( +        (result.secret_imported == 0 and result.new_signatures != 0) or +        (result.secret_imported == 1 and result.new_signatures > 1))) or +                (not secret and result.new_signatures != 0))      assert result.new_revocations == 0 -    assert not ((secret and result.secret_read != 1 and result.secret_read != 3) -                or (not secret and result.secret_read != 0)) -    assert not ((secret and result.secret_imported != 0 -                 and result.secret_imported != 1 -                 and result.secret_imported != 2) -                or (not secret and result.secret_imported != 0)) -    assert not ((secret -                 and ((result.secret_imported == 0 -                       and result.secret_unchanged != 1 -                       and result.secret_unchanged != 2) -                      or (result.secret_imported == 1 -                          and result.secret_unchanged != 0))) -                or (not secret and result.secret_unchanged != 0)) +    assert not ( +        (secret and result.secret_read != 1 and result.secret_read != 3) or +        (not secret and result.secret_read != 0)) +    assert not ( +        (secret and result.secret_imported != 0 and result. +            secret_imported != 1 and result. +            secret_imported != 2) or (not secret and result. +                                      secret_imported != 0)) +    assert not ((secret and +                 ((result.secret_imported == 0 and result. +                   secret_unchanged != 1 and result. +                   secret_unchanged != 2) or (result. +                                              secret_imported == 1 and result. +                                              secret_unchanged != 0))) or +                (not secret and result.secret_unchanged != 0))      assert result.not_imported == 0      if secret:          assert not (len(result.imports) in (0, 3)) @@ -67,12 +69,17 @@ def check_result(result, fpr, secret):      assert len(result.imports) == 1 or fpr == result.imports[1].fpr      assert result.imports[0].result == 0 +  c = gpg.Context() -c.op_import(gpg.Data(file=support.make_filename("pubkey-1.asc"))) -result = c.op_import_result() +result = c.key_import(open(support.make_filename("pubkey-1.asc"), 'rb').read())  check_result(result, "ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55", False) -c.op_import(gpg.Data(file=support.make_filename("seckey-1.asc"))) -result = c.op_import_result() +result = c.key_import(open(support.make_filename("seckey-1.asc"), 'rb').read())  check_result(result, "ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55", True) + +try: +    result = c.key_import(b"thisisnotakey") +except ValueError: +    pass +assert result.considered == 0 diff --git a/lang/python/tests/t-keylist-from-data.py b/lang/python/tests/t-keylist-from-data.py index 6503eb7a..f82ca842 100755 --- a/lang/python/tests/t-keylist-from-data.py +++ b/lang/python/tests/t-keylist-from-data.py @@ -18,87 +18,142 @@  # 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 support +del absolute_import, print_function, unicode_literals +  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] +    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) -  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", "[email protected]" ], -        [ "Alpha Test", "demo key", "[email protected]" ], -	[ "Alice", "demo key", "" ] ], 1 ], -    [ "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F", -      [ [ "Bob", "demo key", "" ], -	[ "Bravo Test", "demo key", "[email protected]" ] ], 1 ], -    [ "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60", -      [ [ "Charlie Test", "demo key", "[email protected]" ] ], 1 ], -    [ "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424", -      [ [ "Delta Test", "demo key", "[email protected]" ] ], 1 ], -    [ "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D", -      [ [ "Echelon", "demo key", "" ], -	[ "Echo Test", "demo key", "[email protected]" ], -	[ "Eve", "demo key", "" ] ], 1 ], -    [ "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E", -      [ [ "Foxtrot Test", "demo key", "[email protected]" ] ], 1 ], -    [ "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354", -      [ [ "Golf Test", "demo key", "[email protected]" ] ], 1 ], -    [ "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A", -      [ [ "Hotel Test", "demo key", "[email protected]" ] ], 1 ], -    [ "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73", -      [ [ "India Test", "demo key", "[email protected]" ] ], 1 ], -    [ "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136", -      [ [ "Juliet Test", "demo key", "[email protected]" ] ], 1 ], -    [ "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02", -      [ [ "Kilo Test", "demo key", "[email protected]" ] ], 1 ], -    [ "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C", -      [ [ "Lima Test", "demo key", "[email protected]" ] ], 1 ], -    [ "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8", -      [ [ "Mallory", "demo key", "" ], -	[ "Mike Test", "demo key", "[email protected]" ] ], 1 ], -    [ "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472", -      [ [ "November Test", "demo key", "[email protected]" ] ], 1 ], -    [ "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F", -      [ [ "Oscar Test", "demo key", "[email protected]" ] ], 1 ], -    [ "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C", -      [ [ "Papa test", "demo key", "[email protected]" ] ], 1 ], -    [ "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4", -      [ [ "Quebec Test", "demo key", "[email protected]" ] ], 1 ], -    [ "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA", -      [ [ "Romeo Test", "demo key", "[email protected]" ] ], 1 ], -    [ "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4", -      [ [ "Sierra Test", "demo key", "[email protected]" ] ], 1 ], -    [ "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402", -      [ [ "Tango Test", "demo key", "[email protected]" ] ], 1 ], -    [ "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9", -      [ [ "Uniform Test", "demo key", "[email protected]" ] ], 1 ], -    [ "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134", -      [ [ "Victor Test", "demo key", "[email protected]" ] ], 1 ], -    [ "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6", -      [ [ "Whisky Test", "demo key", "[email protected]" ] ], 3, -      check_whisky ], -    [ "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE", -      [ [ "XRay Test", "demo key", "[email protected]" ] ], 1 ], -    [ "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD", -      [ [ "Yankee Test", "demo key", "[email protected]" ] ], 1 ], -    [ "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881", -      [ [ "Zulu Test", "demo key", "[email protected]" ] ], 1 ], +    [ +        "A0FF4590BB6122EDEF6E3C542D727CC768697734", "6AE6D7EE46A871F8", +        [["Alfa Test", "demo key", "[email protected]"], +         ["Alpha Test", "demo key", "[email protected]"], +         ["Alice", "demo key", ""]], 1 +    ], +    [ +        "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F", +        [["Bob", "demo key", ""], +         ["Bravo Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60", +        [["Charlie Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424", +        [["Delta Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D", +        [["Echelon", "demo key", +          ""], ["Echo Test", "demo key", "[email protected]"], +         ["Eve", "demo key", ""]], 1 +    ], +    [ +        "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E", +        [["Foxtrot Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354", +        [["Golf Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A", +        [["Hotel Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73", +        [["India Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136", +        [["Juliet Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02", +        [["Kilo Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C", +        [["Lima Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8", +        [["Mallory", "demo key", ""], +         ["Mike Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472", +        [["November Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F", +        [["Oscar Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C", +        [["Papa test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4", +        [["Quebec Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA", +        [["Romeo Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4", +        [["Sierra Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402", +        [["Tango Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9", +        [["Uniform Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134", +        [["Victor Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6", +        [["Whisky Test", "demo key", "[email protected]"]], 3, check_whisky +    ], +    [ +        "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE", +        [["XRay Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD", +        [["Yankee Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881", +        [["Zulu Test", "demo key", "[email protected]"]], 1 +    ],  ] +  def check_global(key, uids, n_subkeys):      assert not key.revoked, "Key unexpectedly revoked"      assert not key.expired, "Key unexpectedly expired" @@ -107,18 +162,18 @@ def check_global(key, uids, n_subkeys):      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]) +    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): @@ -128,54 +183,55 @@ def check_subkey(fpr, which, subkey):      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" +        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 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) +    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 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) +    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) +    c.op_export_keys([c.get_key(k[0]) for k in keys], 0, key_data)  # ... rewind the tape...  key_data.rewind() @@ -201,11 +257,11 @@ with support.EphemeralContext() as c:          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]) +            check_uid("Second", uids[1], key.uids[1])          if len(key.uids) > 2: -          check_uid("Third", uids[2], key.uids[2]) +            check_uid("Third", uids[2], key.uids[2])          if misc_check: -            misc_check (uids[0][0], key) +            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 4505d3c9..b725fc36 100755 --- a/lang/python/tests/t-keylist.py +++ b/lang/python/tests/t-keylist.py @@ -18,87 +18,142 @@  # 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 support +del absolute_import, print_function, unicode_literals +  c = gpg.Context() +  # 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] +    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) -  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", "[email protected]" ], -        [ "Alpha Test", "demo key", "[email protected]" ], -	[ "Alice", "demo key", "" ] ], 1 ], -    [ "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F", -      [ [ "Bob", "demo key", "" ], -	[ "Bravo Test", "demo key", "[email protected]" ] ], 1 ], -    [ "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60", -      [ [ "Charlie Test", "demo key", "[email protected]" ] ], 1 ], -    [ "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424", -      [ [ "Delta Test", "demo key", "[email protected]" ] ], 1 ], -    [ "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D", -      [ [ "Echelon", "demo key", "" ], -	[ "Echo Test", "demo key", "[email protected]" ], -	[ "Eve", "demo key", "" ] ], 1 ], -    [ "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E", -      [ [ "Foxtrot Test", "demo key", "[email protected]" ] ], 1 ], -    [ "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354", -      [ [ "Golf Test", "demo key", "[email protected]" ] ], 1 ], -    [ "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A", -      [ [ "Hotel Test", "demo key", "[email protected]" ] ], 1 ], -    [ "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73", -      [ [ "India Test", "demo key", "[email protected]" ] ], 1 ], -    [ "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136", -      [ [ "Juliet Test", "demo key", "[email protected]" ] ], 1 ], -    [ "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02", -      [ [ "Kilo Test", "demo key", "[email protected]" ] ], 1 ], -    [ "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C", -      [ [ "Lima Test", "demo key", "[email protected]" ] ], 1 ], -    [ "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8", -      [ [ "Mallory", "demo key", "" ], -	[ "Mike Test", "demo key", "[email protected]" ] ], 1 ], -    [ "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472", -      [ [ "November Test", "demo key", "[email protected]" ] ], 1 ], -    [ "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F", -      [ [ "Oscar Test", "demo key", "[email protected]" ] ], 1 ], -    [ "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C", -      [ [ "Papa test", "demo key", "[email protected]" ] ], 1 ], -    [ "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4", -      [ [ "Quebec Test", "demo key", "[email protected]" ] ], 1 ], -    [ "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA", -      [ [ "Romeo Test", "demo key", "[email protected]" ] ], 1 ], -    [ "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4", -      [ [ "Sierra Test", "demo key", "[email protected]" ] ], 1 ], -    [ "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402", -      [ [ "Tango Test", "demo key", "[email protected]" ] ], 1 ], -    [ "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9", -      [ [ "Uniform Test", "demo key", "[email protected]" ] ], 1 ], -    [ "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134", -      [ [ "Victor Test", "demo key", "[email protected]" ] ], 1 ], -    [ "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6", -      [ [ "Whisky Test", "demo key", "[email protected]" ] ], 3, -      check_whisky ], -    [ "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE", -      [ [ "XRay Test", "demo key", "[email protected]" ] ], 1 ], -    [ "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD", -      [ [ "Yankee Test", "demo key", "[email protected]" ] ], 1 ], -    [ "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881", -      [ [ "Zulu Test", "demo key", "[email protected]" ] ], 1 ], +    [ +        "A0FF4590BB6122EDEF6E3C542D727CC768697734", "6AE6D7EE46A871F8", +        [["Alfa Test", "demo key", +          "[email protected]"], ["Alpha Test", "demo key", "[email protected]"], +         ["Alice", "demo key", ""]], 1 +    ], +    [ +        "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F", +        [["Bob", "demo key", ""], +         ["Bravo Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60", +        [["Charlie Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424", +        [["Delta Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D", +        [["Echelon", "demo key", +          ""], ["Echo Test", "demo key", "[email protected]"], +         ["Eve", "demo key", ""]], 1 +    ], +    [ +        "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E", +        [["Foxtrot Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354", +        [["Golf Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A", +        [["Hotel Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73", +        [["India Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136", +        [["Juliet Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02", +        [["Kilo Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C", +        [["Lima Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8", +        [["Mallory", "demo key", ""], +         ["Mike Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472", +        [["November Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F", +        [["Oscar Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C", +        [["Papa test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4", +        [["Quebec Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA", +        [["Romeo Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4", +        [["Sierra Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402", +        [["Tango Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9", +        [["Uniform Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134", +        [["Victor Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6", +        [["Whisky Test", "demo key", "[email protected]"]], 3, check_whisky +    ], +    [ +        "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE", +        [["XRay Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD", +        [["Yankee Test", "demo key", "[email protected]"]], 1 +    ], +    [ +        "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881", +        [["Zulu Test", "demo key", "[email protected]"]], 1 +    ],  ] +  def check_global(key, uids, n_subkeys):      assert not key.revoked, "Key unexpectedly revoked"      assert not key.expired, "Key unexpectedly expired" @@ -107,25 +162,25 @@ def check_global(key, uids, n_subkeys):      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 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)      # Only key Alfa is trusted -    assert key.uids[0].name == 'Alfa Test' \ -      or key.owner_trust == gpg.constants.validity.UNKNOWN, \ -        "Key has unexpected owner trust: {}".format(key.owner_trust) -    assert key.uids[0].name != 'Alfa Test' \ -      or key.owner_trust == gpg.constants.validity.ULTIMATE, \ -        "Key has unexpected owner trust: {}".format(key.owner_trust) +    assert key.uids[0].name == 'Alfa Test' or +    key.owner_trust == gpg.constants.validity.UNKNOWN, +    "Key has unexpected owner trust: {}".format(key.owner_trust) +    assert key.uids[0].name != 'Alfa Test' or key.owner_trust == gpg.constants. +    validity.ULTIMATE, "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]) +    assert len(key.subkeys) - 1 == n_subkeys, +    "Key `{}' has unexpected number of subkeys".format(uids[0][0])  def check_subkey(fpr, which, subkey): @@ -152,18 +207,19 @@ def check_subkey(fpr, which, subkey):      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) +    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" @@ -171,20 +227,21 @@ def check_uid(which, ref, uid):      assert uid.validity == (gpg.constants.validity.UNKNOWN                              if uid.name.split()[0]                              not in {'Alfa', 'Alpha', 'Alice'} else -                            gpg.constants.validity.ULTIMATE), \ -      which + " user ID has unexpectedly validity: {}".format(uid.validity) +                            gpg.constants.validity.ULTIMATE), +    which + " user ID has unexpectedly 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) +    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) +  i = 0  c.op_keylist_start(None, False) -key = c.op_keylist_next () +key = c.op_keylist_next()  while key:      try:          if len(keys[i]) == 4: @@ -204,20 +261,19 @@ while key:      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]) +        check_uid("Second", uids[1], key.uids[1])      if len(key.uids) > 2: -      check_uid("Third", uids[2], key.uids[2]) +        check_uid("Third", uids[2], key.uids[2])      if misc_check: -        misc_check (uids[0][0], key) -    key = c.op_keylist_next () +        misc_check(uids[0][0], key) +    key = c.op_keylist_next()      i += 1  c.op_keylist_end()  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,\ @@ -226,13 +282,12 @@ assert keyring_length > 1,\  # 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) - +assert len(alpha_keys) == 1, "Expected only one key for 'Alpha', got %r" % len( +    alpha_keys)  # Check negative result.  assert len(list(c.keylist("no such key in sight"))) == 0 -  for i, key in enumerate(c.keylist()):      try:          if len(keys[i]) == 4: @@ -252,31 +307,30 @@ for i, key in enumerate(c.keylist()):      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]) +        check_uid("Second", uids[1], key.uids[1])      if len(key.uids) > 2: -      check_uid("Third", uids[2], key.uids[2]) +        check_uid("Third", uids[2], key.uids[2])      if misc_check: -        misc_check (uids[0][0], key) - +        misc_check(uids[0][0], key)  # check get_key()  with gpg.Context() as c: -  c.get_key(support.alpha) -  c.get_key(support.alpha, secret=True) - -  c.get_key(support.bob) -  try: -    c.get_key(support.bob, secret=True) -  except KeyError: -    pass -  else: -    assert False, "Expected KeyError" - -  # Legacy error -  try: -    c.get_key(support.no_such_key) -  except gpg.errors.GPGMEError: -    pass -  else: -    assert False, "Expected GPGMEError" +    c.get_key(support.alpha) +    c.get_key(support.alpha, secret=True) + +    c.get_key(support.bob) +    try: +        c.get_key(support.bob, secret=True) +    except KeyError: +        pass +    else: +        assert False, "Expected KeyError" + +    # Legacy error +    try: +        c.get_key(support.no_such_key) +    except gpg.errors.GPGMEError: +        pass +    else: +        assert False, "Expected GPGMEError" diff --git a/lang/python/tests/t-protocol-assuan.py b/lang/python/tests/t-protocol-assuan.py index 8da50351..c337c3b7 100755 --- a/lang/python/tests/t-protocol-assuan.py +++ b/lang/python/tests/t-protocol-assuan.py @@ -18,20 +18,21 @@  # 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 support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:      # Do nothing.      err = c.assuan_transact('nop') -    assert err == None +    assert err is None      err = c.assuan_transact(b'NOP') -    assert err == None +    assert err is None      err = c.assuan_transact(['NOP']) -    assert err == None +    assert err is None      err = c.assuan_transact('idontexist')      assert err.getsource() == gpg.errors.SOURCE_GPGAGENT @@ -41,6 +42,7 @@ with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:      c.assuan_transact(['GET_CONFIRMATION', 'Hello there'])      data = [] +      def data_cb(line):          data.append(line) @@ -57,6 +59,7 @@ with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:      # XXX HELP sends status lines if we could use ASSUAN_CONVEY_COMMENTS.      status = [] +      def status_cb(line, args):          status.append((line, args)) diff --git a/lang/python/tests/t-quick-key-creation.py b/lang/python/tests/t-quick-key-creation.py index 8b7372e7..47209288 100755 --- a/lang/python/tests/t-quick-key-creation.py +++ b/lang/python/tests/t-quick-key-creation.py @@ -18,7 +18,6 @@  # 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 @@ -27,6 +26,8 @@ import time  import support  support.assert_gpg_version((2, 1, 2)) +del absolute_import, print_function, unicode_literals +  alpha = "Alpha <[email protected]>"  with support.EphemeralContext() as ctx: @@ -51,14 +52,16 @@ with support.EphemeralContext() as ctx:      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) @@ -77,10 +80,8 @@ with support.EphemeralContext() as ctx:          "Primary keys expiration time is off"      # Check capabilities -    for sign, encrypt, certify, authenticate in itertools.product([False, True], -                                                                  [False, True], -                                                                  [False, True], -                                                                  [False, True]): +    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. @@ -89,9 +90,13 @@ with support.EphemeralContext() as ctx:              # The primary key always certifies.              continue -        res = ctx.create_key(make_uid(), algorithm="rsa", -                             sign=sign, encrypt=encrypt, certify=certify, -                             authenticate=authenticate) +        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, \ @@ -125,13 +130,16 @@ with support.EphemeralContext() as ctx:      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)]) +    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) diff --git a/lang/python/tests/t-quick-key-manipulation.py b/lang/python/tests/t-quick-key-manipulation.py index 37e05b35..ade171e7 100755 --- a/lang/python/tests/t-quick-key-manipulation.py +++ b/lang/python/tests/t-quick-key-manipulation.py @@ -18,7 +18,6 @@  # 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 @@ -27,6 +26,8 @@ import sys  import support  support.assert_gpg_version((2, 1, 14)) +del absolute_import, print_function, unicode_literals +  alpha = "Alpha <[email protected]>"  bravo = "Bravo <[email protected]>" @@ -111,9 +112,11 @@ with support.EphemeralContext() as ctx:          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))) +        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: diff --git a/lang/python/tests/t-quick-key-signing.py b/lang/python/tests/t-quick-key-signing.py index 3d648c5b..6f9b8a72 100755 --- a/lang/python/tests/t-quick-key-signing.py +++ b/lang/python/tests/t-quick-key-signing.py @@ -18,7 +18,6 @@  # 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 @@ -27,8 +26,11 @@ import time  import support  support.assert_gpg_version((2, 1, 1)) +del absolute_import, print_function, unicode_literals +  with support.EphemeralContext() as ctx:      uid_counter = 0 +      def make_uid():          global uid_counter          uid_counter += 1 @@ -43,10 +45,16 @@ with support.EphemeralContext() as ctx:          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))) +        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} +        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: @@ -76,9 +84,12 @@ with support.EphemeralContext() as ctx:          assert s.exportable          assert s.expires == 0 -    check_sigs(key_b, itertools.product(uids_b, [key_b], [exportable_non_expiring])) +    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])) +    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() @@ -89,11 +100,12 @@ with support.EphemeralContext() as ctx:          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]))) +    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() @@ -106,16 +118,16 @@ with support.EphemeralContext() as ctx:          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]))) +    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]))) +    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 index ad4f35c6..30424c19 100755 --- a/lang/python/tests/t-quick-subkey-creation.py +++ b/lang/python/tests/t-quick-subkey-creation.py @@ -18,7 +18,6 @@  # 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 @@ -26,6 +25,8 @@ import time  import support +del absolute_import, print_function, unicode_literals +  alpha = "Alpha <[email protected]>"  bravo = "Bravo <[email protected]>" @@ -59,16 +60,15 @@ with support.EphemeralContext() as ctx:          "subkeys expiration time is off"      # Check capabilities -    for sign, encrypt, authenticate in itertools.product([False, True], -                                                         [False, True], -                                                         [False, True]): +    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) +        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 @@ -92,18 +92,21 @@ with support.EphemeralContext() as ctx:      # 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" +    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)]) +    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) diff --git a/lang/python/tests/t-sig-notation.py b/lang/python/tests/t-sig-notation.py index bc8da2e6..5960f443 100755 --- a/lang/python/tests/t-sig-notation.py +++ b/lang/python/tests/t-sig-notation.py @@ -18,29 +18,30 @@  # 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 # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  expected_notations = { -    "laughing@me": ("Just Squeeze Me", gpg.constants.sig.notation.HUMAN_READABLE), -    "[email protected]": ("pgpmime", -                                         gpg.constants.sig.notation.HUMAN_READABLE -                                         | gpg.constants.sig.notation.CRITICAL), +    "laughing@me": ("Just Squeeze Me", +                    gpg.constants.sig.notation.HUMAN_READABLE), +    "[email protected]": +    ("pgpmime", gpg.constants.sig.notation.HUMAN_READABLE | +     gpg.constants.sig.notation.CRITICAL),      None: ("http://www.gnu.org/policy/", 0),  }  # GnuPG prior to 2.1.13 did not report the critical flag correctly.  with gpg.Context() as c:      version = c.engine_info.version -    have_correct_sig_data = not (version.startswith("1.") -                                 or version.startswith("2.0.") -                                 or version == "2.1.1" -                                 or (version.startswith("2.1.1") -                                     and version[5] < '3')) +    have_correct_sig_data = not ( +        version.startswith("1.") or version.startswith("2.0.") or +        (version.startswith("2.1.") and int(version[4:]) < 13)) +  def check_result(result):      assert len(result.signatures) == 1, "Unexpected number of signatures" @@ -48,8 +49,8 @@ def check_result(result):      assert len(sig.notations) == len(expected_notations)      for r in sig.notations: -        assert not 'name_len' in dir(r) -        assert not 'value_len' in dir(r) +        assert 'name_len' not in dir(r) +        assert 'value_len' not in dir(r)          assert r.name in expected_notations          value, flags = expected_notations.pop(r.name) @@ -63,6 +64,7 @@ def check_result(result):      assert len(expected_notations) == 0 +  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 d3757294..3ad05e8e 100755 --- a/lang/python/tests/t-sign.py +++ b/lang/python/tests/t-sign.py @@ -18,15 +18,18 @@  # 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 +del absolute_import, print_function, unicode_literals + +  def fail(msg):      raise RuntimeError(msg) +  def check_result(r, typ):      if r.invalid_signers:          fail("Invalid signer found: {}".format(r.invalid_signers.fpr)) @@ -43,16 +46,15 @@ def check_result(r, typ):              signature.pubkey_algo))      if signature.hash_algo != gpg.constants.md.SHA1: -        fail("Wrong hash algorithm reported: {}".format( -            signature.hash_algo)) +        fail("Wrong hash algorithm reported: {}".format(signature.hash_algo))      if signature.sig_class != 1: -        fail("Wrong signature class reported: {}".format( -            signature.sig_class)) +        fail("Wrong signature class reported: {}".format(signature.sig_class))      if signature.fpr != "A0FF4590BB6122EDEF6E3C542D727CC768697734":          fail("Wrong fingerprint reported: {}".format(signature.fpr)) +  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 5864ee5f..119ab773 100755 --- a/lang/python/tests/t-signers.py +++ b/lang/python/tests/t-signers.py @@ -18,14 +18,17 @@  # 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 support +del absolute_import, print_function, unicode_literals + +  def fail(msg):      raise RuntimeError(msg) +  def check_result(r, typ):      if r.invalid_signers:          fail("Invalid signer found: {}".format(r.invalid_signers.fpr)) @@ -53,6 +56,7 @@ def check_result(r, typ):                                   "23FD347A419429BACCD5E72D6BC4778054ACD246"):              fail("Wrong fingerprint reported: {}".format(signature.fpr)) +  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 89524bb5..ffa0b96d 100755 --- a/lang/python/tests/t-trustlist.py +++ b/lang/python/tests/t-trustlist.py @@ -18,18 +18,21 @@  # 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 support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  c = gpg.Context() +  def dump_item(item): -    print("l={} k={} t={} o={} v={} u={}".format( -        item.level, item.keyid, item.type, item.owner_trust, -        item.validity, item.name)) +    print("l={} k={} t={} o={} v={} u={}".format(item.level, item.keyid, +                                                 item.type, item.owner_trust, +                                                 item.validity, item.name)) +  c.op_trustlist_start("alice", 0)  while True: diff --git a/lang/python/tests/t-verify.py b/lang/python/tests/t-verify.py index 320dae66..70a6c1cb 100755 --- a/lang/python/tests/t-verify.py +++ b/lang/python/tests/t-verify.py @@ -18,16 +18,17 @@  # 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 sys  import os  import gpg  import support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  test_text1 = b"Just GNU it!\n" -test_text1f= b"Just GNU it?\n" +test_text1f = b"Just GNU it?\n"  test_sig1 = b"""-----BEGIN PGP SIGNATURE-----  iN0EABECAJ0FAjoS+i9FFIAAAAAAAwA5YmFyw7bDpMO8w58gZGFzIHdhcmVuIFVt @@ -60,6 +61,7 @@ UqVooWlGXHwNw/xg/fVzt9VNbtjtJ/fhUqYo0/LyCGEA  -----END PGP MESSAGE-----  """ +  def check_result(result, summary, validity, fpr, status, notation):      assert len(result.signatures) == 1, "Unexpected number of signatures"      sig = result.signatures[0] @@ -76,14 +78,16 @@ def check_result(result, summary, validity, fpr, status, notation):                      if sys.version_info[0] < 3 else                      b"\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f".decode() +                      " das waren Umlaute und jetzt ein prozent%-Zeichen"), -            "foobar.1":  "this is a notation data with 2 lines", -            None: "http://www.gu.org/policy/", +            "foobar.1": +            "this is a notation data with 2 lines", +            None: +            "http://www.gu.org/policy/",          }          assert len(sig.notations) == len(expected_notations)          for r in sig.notations: -            assert not 'name_len' in dir(r) -            assert not 'value_len' in dir(r) +            assert 'name_len' not in dir(r) +            assert 'value_len' not in dir(r)              assert r.name in expected_notations              assert r.value == expected_notations[r.name], \                  "Expected {!r}, got {!r}".format(expected_notations[r.name], @@ -96,7 +100,9 @@ def check_result(result, summary, validity, fpr, status, notation):      assert sig.validity == validity, \          "Unexpected signature validity: {}, want: {}".format(              sig.validity, validity) -    assert gpg.errors.GPGMEError(sig.validity_reason).getcode() == gpg.errors.NO_ERROR +    assert gpg.errors.GPGMEError( +        sig.validity_reason).getcode() == gpg.errors.NO_ERROR +  c = gpg.Context()  c.set_armor(True) @@ -108,9 +114,8 @@ c.op_verify(sig, text, None)  result = c.op_verify_result()  check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,               gpg.constants.validity.FULL, -             "A0FF4590BB6122EDEF6E3C542D727CC768697734", -             gpg.errors.NO_ERROR, True) - +             "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR, +             True)  # Checking a manipulated message.  text = gpg.Data(test_text1f) @@ -127,8 +132,8 @@ c.op_verify(sig, None, text)  result = c.op_verify_result()  check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,               gpg.constants.validity.FULL, -             "A0FF4590BB6122EDEF6E3C542D727CC768697734", -             gpg.errors.NO_ERROR, False) +             "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR, +             False)  # Checking an invalid message.  text = gpg.Data() @@ -141,33 +146,32 @@ except Exception as e:  else:      assert False, "Expected an error but got none." -  # Idiomatic interface.  with gpg.Context(armor=True) as c:      # Checking a valid message.      _, result = c.verify(test_text1, test_sig1) -    check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, -                 gpg.constants.validity.FULL, -                 "A0FF4590BB6122EDEF6E3C542D727CC768697734", -                 gpg.errors.NO_ERROR, True) +    check_result( +        result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, +        gpg.constants.validity.FULL, +        "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR, True)      # Checking a manipulated message.      try:          c.verify(test_text1f, test_sig1)      except gpg.errors.BadSignatures as e:          check_result(e.result, gpg.constants.sigsum.RED, -                     gpg.constants.validity.UNKNOWN, -                     "2D727CC768697734", gpg.errors.BAD_SIGNATURE, False) +                     gpg.constants.validity.UNKNOWN, "2D727CC768697734", +                     gpg.errors.BAD_SIGNATURE, False)      else:          assert False, "Expected an error but got none."      # Checking a normal signature.      sig = gpg.Data(test_sig2)      data, result = c.verify(test_sig2) -    check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, -                 gpg.constants.validity.FULL, -                 "A0FF4590BB6122EDEF6E3C542D727CC768697734", -                 gpg.errors.NO_ERROR, False) +    check_result( +        result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN, +        gpg.constants.validity.FULL, +        "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR, False)      assert data == test_text1      # Checking an invalid message. diff --git a/lang/python/tests/t-wait.py b/lang/python/tests/t-wait.py index 31013011..907f4504 100755 --- a/lang/python/tests/t-wait.py +++ b/lang/python/tests/t-wait.py @@ -18,12 +18,13 @@  # 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 time  import gpg  import support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes. + +del absolute_import, print_function, unicode_literals  c = gpg.Context()  c.set_armor(True) diff --git a/lang/python/tests/t-wrapper.py b/lang/python/tests/t-wrapper.py index 08a320d2..020e71e5 100755 --- a/lang/python/tests/t-wrapper.py +++ b/lang/python/tests/t-wrapper.py @@ -19,9 +19,9 @@  import gpg  import support -_ = support # to appease pyflakes. +_ = support  # to appease pyflakes.  d0 = gpg.Data() -d0.seek # trigger on-demand-wrapping +d0.seek  # trigger on-demand-wrapping  assert d0.seek == d0.seek, "Generated wrapper functions are not cached"  assert hasattr(gpg.Data, 'seek'), "Generated wrapper functions are not shared" diff --git a/lang/python/version.py.in b/lang/python/version.py.in index 1a1baf08..ad76edab 100644 --- a/lang/python/version.py.in +++ b/lang/python/version.py.in @@ -1,4 +1,6 @@ -# Copyright (C) 2016 g10 Code GmbH +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2018 g10 Code GmbH  # Copyright (C) 2015 Ben McGinnes <[email protected]>  # Copyright (C) 2004 Igor Belyi <[email protected]>  # @@ -17,10 +19,11 @@  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  from __future__ import absolute_import, print_function -del absolute_import, print_function  from . import gpgme +del absolute_import, print_function +  productname = 'gpg'  versionstr = "@VERSION@"  gpgme_versionstr = gpgme.GPGME_VERSION @@ -32,8 +35,8 @@ minor = versionlist[1]  patch = versionlist[2]  copyright = """\ -Copyright (C) 2016 g10 Code GmbH -Copyright (C) 2015 Ben McGinnes +Copyright (C) 2016-2018 g10 Code GmbH +Copyright (C) 2015 Benjamin D. McGinnes  Copyright (C) 2014-2015 Martin Albrecht  Copyright (C) 2004-2008 Igor Belyi  Copyright (C) 2002 John Goerzen""" @@ -44,8 +47,8 @@ author_email = "[email protected]"  description = "Python support for GPGME GnuPG cryptography library"  homepage = "https://gnupg.org" -license = """Copyright (C) 2016 g10 Code GmbH -Copyright (C) 2015 Ben McGinnes <[email protected]> +license = """Copyright (C) 2016-2018 g10 Code GmbH +Copyright (C) 2015 Benjamin D. McGinnes <[email protected]>  Copyright (C) 2014, 2015 Martin Albrecht <[email protected]>  Copyright (C) 2004, 2008 Igor Belyi <[email protected]>  Copyright (C) 2002 John Goerzen <[email protected]> diff --git a/lang/qt/src/threadedjobmixin.cpp b/lang/qt/src/threadedjobmixin.cpp index 74755c55..cd7c494f 100644 --- a/lang/qt/src/threadedjobmixin.cpp +++ b/lang/qt/src/threadedjobmixin.cpp @@ -53,7 +53,68 @@  using namespace QGpgME;  using namespace GpgME; -static const unsigned int GetAuditLogFlags = Context::AuditLogWithHelp | Context::HtmlAuditLog; +#ifdef Q_OS_WIN +#include <windows.h> + +static QString fromEncoding (unsigned int src_encoding, const char *data) +{ +    int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0); +    if (n < 0) { +        return QString(); +    } + +    wchar_t *result = (wchar_t *) malloc ((n+1) * sizeof *result); + +    n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n); +    if (n < 0) { +        free(result); +        return QString(); +    } +    const auto ret = QString::fromWCharArray(result, n); +    free(result); +    return ret; +} +#endif + +static QString stringFromGpgOutput(const QByteArray &ba) +{ +#ifdef Q_OS_WIN +    /* Qt on Windows uses GetACP while GnuPG prefers +     * GetConsoleOutputCP. +     * +     * As we are not a console application GetConsoleOutputCP +     * usually returns 0. +     * From experience the closest thing that let's us guess +     * what GetConsoleOutputCP returns for a console application +     * it appears to be the OEMCP. +     */ +    unsigned int cpno = GetConsoleOutputCP (); +    if (!cpno) { +        cpno = GetOEMCP(); +    } +    if (!cpno) { +        cpno = GetACP(); +    } +    if (!cpno) { +        return QString(); +    } + +    return fromEncoding(cpno, ba.constData()); +#else +    return QString::fromLocal8Bit(ba); +#endif +} + +static QString markupDiagnostics(const QString &data) +{ +    // First ensure that we don't have html in the diag. +    QString ret = QStringLiteral("<pre>%1</pre>").arg(data.toHtmlEscaped()); + +    return ret; +} + +static const unsigned int CMSAuditLogFlags = Context::AuditLogWithHelp | Context::HtmlAuditLog; +static const unsigned int OpenPGPAuditLogFlags = Context::DiagnosticAuditLog;  QString _detail::audit_log_as_html(Context *ctx, GpgME::Error &err)  { @@ -61,11 +122,24 @@ QString _detail::audit_log_as_html(Context *ctx, GpgME::Error &err)      QGpgME::QByteArrayDataProvider dp;      Data data(&dp);      assert(!data.isNull()); -    if ((err = ctx->lastError()) || (err = ctx->getAuditLog(data, GetAuditLogFlags))) { -        return QString::fromLocal8Bit(err.asString()); + +    if (ctx->protocol() == OpenPGP) { +        if ((err = ctx->getAuditLog(data, OpenPGPAuditLogFlags))) { +            return QString::fromLocal8Bit(err.asString()); +        } +        const QByteArray ba = dp.data(); +        return markupDiagnostics(stringFromGpgOutput(ba));      } -    const QByteArray ba = dp.data(); -    return QString::fromUtf8(ba.data(), ba.size()); + +    if (ctx->protocol() == CMS) { +        if ((err = ctx->lastError()) || (err = ctx->getAuditLog(data, CMSAuditLogFlags))) { +            return QString::fromLocal8Bit(err.asString()); +        } +        const QByteArray ba = dp.data(); +        return QString::fromUtf8(ba.data(), ba.size()); +    } + +    return QStringLiteral("Unsupported protocol for Audit Log");  }  static QList<QByteArray> from_sl(const QStringList &sl) diff --git a/lang/qt/tests/Makefile.am b/lang/qt/tests/Makefile.am index 104672e4..bfe77ad5 100644 --- a/lang/qt/tests/Makefile.am +++ b/lang/qt/tests/Makefile.am @@ -21,7 +21,8 @@  GPG = gpg -TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) +GNUPGHOME=$(abs_builddir) +TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME)  EXTRA_DIST = initial.test diff --git a/lang/qt/tests/t-various.cpp b/lang/qt/tests/t-various.cpp index 75456281..76e68063 100644 --- a/lang/qt/tests/t-various.cpp +++ b/lang/qt/tests/t-various.cpp @@ -98,6 +98,25 @@ private Q_SLOTS:          QVERIFY(key.primaryFingerprint() == QStringLiteral("7A0904B6950DA998020A1AD4BE41C0C3A5FF1F3C"));      } +    void testDataRewind() +    { +        if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.14") { +            return; +        } +        QGpgME::QByteArrayDataProvider dp(aKey); +        Data data(&dp); +        char buf[20]; +        data.read(buf, 20); + +        auto keys = data.toKeys(); +        QVERIFY(keys.size() == 0); + +        data.rewind(); + +        keys = data.toKeys(); +        QVERIFY(keys.size() == 1); +    } +      void testQuickUid()      {          if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.13") { | 
