diff options
141 files changed, 7328 insertions, 2397 deletions
@@ -24,7 +24,7 @@ List of Copyright holders Copyright (C) 2004-2008 Igor Belyi Copyright (C) 2002 John Goerzen Copyright (C) 2014, 2015 Martin Albrecht - Copyright (C) 2015 Ben McGinnes + Copyright (C) 2015, 2018 Ben McGinnes Copyright (C) 2015-2016 Bundesamt für Sicherheit in der Informationstechnik Copyright (C) 2016 Intevation GmbH @@ -59,6 +59,12 @@ Colin Watson <[email protected]> Tobias Mueller <[email protected]> 2016-11-23:[email protected]: +Ben McGinnes <[email protected]> +2017-12-16:[email protected]: + +Jacob Adams <[email protected]> +2018-06-03:[email protected]: + Copyright 2001, 2002, 2012, 2013 g10 Code GmbH @@ -4,10 +4,27 @@ Noteworthy changes in version 1.11.2 (unreleased) * Even for old versions of gpg a missing MDC will now lead to a decryption failure. + * Added context flag "auto-key-locate" to control the + behavior of GPGME_KEYLIST_MODE_LOCATE. + + * New data function to create a data object from an estream. + * Interface changes relative to the 1.11.1 release: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - cpp: DecryptionResult::sessionKey NEW. - cpp: DecryptionResult::symkeyAlgo NEW. + gpgme_data_new_from_estream NEW. + gpgme_decrypt_result_t EXTENDED: New field legacy_cipher_nomdc. + gpgme_set_ctx_flag EXTENDED: New flag 'ignore-mdc-error'. + GPGME_AUDITLOG_DEFAULT NEW. + GPGME_AUDITLOG_DIAG NEW. + gpgme_set_ctx_flag EXTENDED: New flag 'auto-key-locate'. + cpp: DecryptionResult::sessionKey NEW. + cpp: DecryptionResult::symkeyAlgo NEW. + cpp: DecryptionResult::isLegacyCipherNoMDC New. + cpp: Data::rewind NEW. + cpp: Context::setFlag NEW. + cpp: Context::getFlag NEW. + cpp: Context::createKeyEx NEW. + Noteworthy changes in version 1.11.1 (2018-04-20) ------------------------------------------------- diff --git a/configure.ac b/configure.ac index 65f1ef90..1813cc57 100644 --- a/configure.ac +++ b/configure.ac @@ -537,7 +537,7 @@ AM_CONDITIONAL(RUN_G13_TESTS, test "$run_g13_test" = "yes") # Checks for header files. -AC_CHECK_HEADERS_ONCE([locale.h sys/select.h sys/uio.h argp.h +AC_CHECK_HEADERS_ONCE([locale.h sys/select.h sys/uio.h argp.h stdint.h unistd.h sys/time.h sys/types.h sys/stat.h]) @@ -548,6 +548,15 @@ AC_SYS_LARGEFILE AC_TYPE_OFF_T AC_TYPE_UINTPTR_T +# We require uint64_t +if test "$ac_cv_header_stdint_h" != yes; then + AC_MSG_ERROR([[ +*** +*** No stdint.h and thus no uint64_t type. Can't build this library. +***]]) +fi + + # A simple compile time check in gpgme.h for GNU/Linux systems that # prevents a file offset bits mismatch between gpgme and the application. NEED__FILE_OFFSET_BITS=0 diff --git a/doc/gpgme.texi b/doc/gpgme.texi index c745675b..aff72405 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -1909,6 +1909,25 @@ data object was successfully created, and @code{GPG_ERR_ENOMEM} if not enough memory is available. @end deftypefun +@deftypefun gpgme_error_t gpgme_data_new_from_estream (@w{gpgme_data_t *@var{dh}}, @w{gpgrt_stream_t @var{stream}}) +The function @code{gpgme_data_new_from_estream} creates a new +@code{gpgme_data_t} object and uses the gpgrt stream @var{stream} to read +from (if used as an input data object) and write to (if used as an +output data object). + +When using the data object as an input buffer, the function might read +a bit more from the stream than is actually needed by the crypto +engine in the desired operation because of internal buffering. + +Note that GPGME assumes that the stream is in blocking mode. Errors +during I/O operations, except for EINTR, are usually fatal for crypto +operations. + +The function returns the error code @code{GPG_ERR_NO_ERROR} if the +data object was successfully created, and @code{GPG_ERR_ENOMEM} if not +enough memory is available. +@end deftypefun + @node Callback Based Data Buffers @subsection Callback Based Data Buffers @@ -2426,6 +2445,7 @@ started. In fact, these references are accessed through the * Progress Meter Callback:: Being informed about the progress. * Status Message Callback:: Status messages received from gpg. * Locale:: Setting the locale of a context. +* Additional Logs:: Additional logs of a context. @end menu @@ -2762,6 +2782,8 @@ The @code{GPGME_KEYLIST_MODE_LOCAL} symbol specifies that the local keyring should be searched for keys in the keylisting operation. This is the default. +Using only this option results in a @code{--list-keys}. + @item GPGME_KEYLIST_MODE_EXTERN The @code{GPGME_KEYLIST_MODE_EXTERN} symbol specifies that an external source should be searched for keys in the keylisting operation. The @@ -2769,10 +2791,14 @@ type of external source is dependent on the crypto engine used and whether it is combined with @code{GPGME_KEYLIST_MODE_LOCAL}. For example, it can be a remote keyserver or LDAP certificate server. +Using only this option results in a @code{--search-keys} for +@code{GPGME_PROTOCOL_OpenPGP} and something similar to +@code{--list-external-keys} for @code{GPGME_PROTOCOL_CMS}. + @item GPGME_KEYLIST_MODE_LOCATE This is a shortcut for the combination of -@code{GPGME_KEYLIST_MODE_LOCAL} and @code{GPGME_KEYLIST_MODE_EXTERN} -and convenient when the --locate-key feature of OpenPGP is desired. +@code{GPGME_KEYLIST_MODE_LOCAL} and @code{GPGME_KEYLIST_MODE_EXTERN}, which +results in a @code{--locate-keys} for @code{GPGME_PROTOCOL_OpenPGP}. @item GPGME_KEYLIST_MODE_SIGS The @code{GPGME_KEYLIST_MODE_SIGS} symbol specifies that the key @@ -3078,7 +3104,7 @@ the time when you verified the signature. The string given in @var{value} is passed to the GnuPG engines to request restrictions based on the origin of the request. Valid values are documented in the GnuPG manual and the gpg man page under the -option ``--request-origin''. Requires at least GnuPG 2.2.6 to have an +option @option{--request-origin}. Requires at least GnuPG 2.2.6 to have an effect. @item "no-symkey-cache" @@ -3086,6 +3112,25 @@ For OpenPGP disable the passphrase cache used for symmetrical en- and decryption. This cache is based on the message specific salt value. Requires at least GnuPG 2.2.7 to have an effect. +@item "ignore-mdc-error" +This flag passes the option @option{--ignore-mdc-error} to gpg. This +can be used to force decryption of a message which failed due to a +missing integrity check. This flag must be used with great caution +and only if it is a known non-corrupted old message and the decryption +result of the former try had the decryption result flag +@code{legacy_cipher_nomdc} set. For failsafe reasons this flag is +reset after each operation. + +@item "auto-key-locate" +The string given in @var{value} is passed to gpg. This can be used +to change the behavior of a @code{GPGME_KEYLIST_MODE_LOCATE} keylisting. +Valid values are documented in the GnuPG manual and the gpg man page under +the option @option{--auto-key-locate}. +Requires at least GnuPG 2.1.18. + +Note: Keys retrieved through @code{auto-key-locate} are automatically +imported in the keyring. + @end table This function returns @code{0} on success. @@ -3146,6 +3191,70 @@ The function returns an error if not enough memory is available. @end deftypefun +@node Additional Logs +@subsection Additional Logs +@cindex auditlog, of the engine +@cindex auditlog + +Additional logs can be associated with a context. These logs are +engine specific and can be be obtained with @code{gpgme_op_getauditlog}. + +@deftypefun gpgme_error_t gpgme_op_getauditlog @ + (@w{gpgme_ctx_t @var{ctx}}, @w{gpgme_data_t @var{output}}, @ + @w{unsigned int @var{flags}}) +@since{1.1.1} + +The function @code{gpgme_op_getauditlog} is used to obtain additional +logs as specified by @var{flags} into the @var{output} data. If + +The function returns the error code @code{GPG_ERR_NO_ERROR} if a +log could be queried from the engine, and @code{GPG_ERR_NOT_IMPLEMENTED} +if the log specified in @var{flags} is not available for this engine. +If no log is available @code{GPG_ERR_NO_DATA} is returned. + +The value in @var{flags} is a bitwise-or combination of one or +multiple of the following bit values: + +@table @code +@item GPGME_AUDITLOG_DIAG +@since{1.11.2} + +Obtain diagnostic output which would be written to @code{stderr} in +interactive use of the engine. This can be used to provide additional +diagnostic information in case of errors in other operations. + +Note: If log-file has been set in the configuration the log will +be empty and @code{GPG_ERR_NO_DATA} will be returned. + +Implemented for: @code{GPGME_PROTOCOL_OpenPGP} + +@item GPGME_AUDITLOG_DEFAULT +@since{1.11.2} + +This flag has the value 0 for compatibility reasons. Obtains additional +information from the engine by issuing the @code{GETAUDITLOG} command. +For @code{GPGME_PROTOCOL_CMS} this provides additional information about +the X509 certificate chain. + +Implemented for: @code{GPGME_PROTOCOL_CMS} + +@item GPGME_AUDITLOG_HTML +@since{1.1.1} + +Same as @code{GPGME_AUDITLOG_DEFAULT} but in HTML. + +Implemented for: @code{GPGME_PROTOCOL_CMS} +@end table +@end deftypefun + +@deftypefun gpgme_error_t gpgme_op_getauditlog_start @ + (@w{gpgme_ctx_t @var{ctx}}, @w{gpgme_data_t @var{output}}, @ + @w{unsigned int @var{flags}}) +@since{1.1.1} + +This is the asynchronous variant of @code{gpgme_op_getauditlog}. +@end deftypefun + @node Key Management @section Key Management @cindex key management @@ -5368,7 +5477,7 @@ This is a pointer to a structure used to store the result of a data, you can retrieve the pointer to the result with @code{gpgme_op_decrypt_result}. As with all result structures, it this structure shall be considered read-only and an application must -not allocated such a strucure on its own. The structure contains the +not allocate such a strucure on its own. The structure contains the following members: @table @code @@ -5378,9 +5487,22 @@ algorithm that is not supported. @item unsigned int wrong_key_usage : 1 @since{0.9.0} - This is true if the key was not used according to its policy. +@item unsigned int legacy_cipher_nomdc : 1 +@since{1.11.2} +The message was made by a legacy algorithm without any integrity +protection. This might be an old but legitimate message. + +@item unsigned int is_mime : 1; +@since{1.11.0} +The message claims that the content is a MIME object. + +@item unsigned int is_de_vs : 1; +@since{1.10.0} +The message was encrypted in a VS-NfD compliant way. This is a +specification in Germany for a restricted communication level. + @item gpgme_recipient_t recipients @since{1.1.0} 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") { diff --git a/m4/python.m4 b/m4/python.m4 index 822b2ddf..56220ba2 100644 --- a/m4/python.m4 +++ b/m4/python.m4 @@ -35,12 +35,13 @@ AC_DEFUN([AM_PATH_PYTHON], [ dnl Find a Python interpreter. Python versions prior to 2.0 are not - dnl supported. (2.0 was released on October 16, 2000). + dnl supported. (2.0 was released on October 16, 2000). Python 3.0 + dnl through to Python 3.3 are also not supported. m4_define_default([_AM_PYTHON_INTERPRETER_LIST], [python2 python2.7 dnl python dnl - python3 python3.0 python3.1 python3.2 python3.3 dnl - python3.4 python3.5 python3.6 python3.7 python3.8]) + python3 python3.6 python3.5 python3.4 python3.7 dnl + python3.8]) AC_ARG_VAR([PYTHON], [the Python interpreter]) diff --git a/src/Makefile.am b/src/Makefile.am index 0a196e0c..1394c028 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -70,6 +70,7 @@ main_sources = \ parsetlv.c parsetlv.h \ mbox-util.c mbox-util.h \ data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \ + data-estream.c \ data-compat.c data-identify.c \ signers.c sig-notation.c \ wait.c wait-global.c wait-private.c wait-user.c wait.h \ diff --git a/src/cJSON.c b/src/cJSON.c index cf0cb132..9e53012e 100644 --- a/src/cJSON.c +++ b/src/cJSON.c @@ -22,7 +22,14 @@ * SPDX-License-Identifier: MIT * * Note that this code has been modified from the original code taken - * from cjson-code-58.zip. + * from cjson-code-58.zip before 2014 (my first local commit was in + * 2014 but I may used the code even earlier). Since 2016 the project + * was revived and moved to https://github.com/DaveGamble/cJSON.git. + * It is now a lot more complex and has substantial changes so that it + * is not possible to merge them directly. In any case we only need a + * simple parser and not a complete library. I have looked through + * the commits and fixed a few things which should apply; I also added + * a few references to the upstream code. Regression test are missing! */ #ifdef HAVE_CONFIG_H @@ -38,20 +45,42 @@ #include <ctype.h> #include <errno.h> +#include <gpg-error.h> + #include "cJSON.h" +/* Only use calloc. */ +#define CALLOC_ONLY 1 + +/* To avoid that a compiler optimizes certain memset calls away, these + macros may be used instead. */ +#define wipememory2(_ptr,_set,_len) do { \ + volatile char *_vptr=(volatile char *)(_ptr); \ + size_t _vlen=(_len); \ + while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ + } while(0) +#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) + /* We use malloc function wrappers from gpgrt (aka libgpg-error). */ -#if 1 +#if GPGRT_VERSION_NUMBER >= 0x011c00 /* 1.28 */ # include <gpgrt.h> -# define xtrymalloc(a) gpgrt_malloc ((a)) # define xtrycalloc(a,b) gpgrt_calloc ((a), (b)) # define xtrystrdup(a) gpgrt_strdup ((a)) # define xfree(a) gpgrt_free ((a)) -#else -# define xtrymalloc(a) malloc ((a)) +# if CALLOC_ONLY +# define xtrymalloc(a) gpgrt_calloc (1, (a)) +# else +# define xtrymalloc(a) gpgrt_malloc ((a)) +# endif +#else /* Without gpgrt (aka libgpg-error). */ # define xtrycalloc(a,b) calloc ((a), (b)) # define xtrystrdup(a) strdup ((a)) # define xfree(a) free ((a)) +# if CALLOC_ONLY +# define xtrymalloc(a) calloc (1, (a)) +# else +# define xtrymalloc(a) malloc ((a)) +# endif #endif @@ -94,9 +123,15 @@ cJSON_Delete (cJSON * c) if (!(c->type & cJSON_IsReference) && c->child) cJSON_Delete (c->child); if (!(c->type & cJSON_IsReference) && c->valuestring) - xfree (c->valuestring); + { + wipememory (c->valuestring, strlen (c->valuestring)); + xfree (c->valuestring); + } if (c->string) - xfree (c->string); + { + wipememory (c->string, strlen (c->string)); + xfree (c->string); + } xfree (c); c = next; } @@ -232,6 +267,9 @@ parse_string (cJSON * item, const char *str, const char **ep) char *out; int len = 0; unsigned uc, uc2; + + /* FIXME: We should consider eary failure like it is done with + * commit 8656386c4f4a12f1cf3d6b26158407fd05e65029 in upstream. */ if (*str != '\"') { *ep = str; @@ -239,11 +277,13 @@ parse_string (cJSON * item, const char *str, const char **ep) } /* not a string! */ while (*ptr != '\"' && *ptr && ++len) - if (*ptr++ == '\\') + if (*ptr++ == '\\' && *ptr) ptr++; /* Skip escaped quotes. */ - out = xtrymalloc (len + 1); /* This is how long we need for the - string, roughly. */ + out = xtrymalloc (len + 2); /* This is how long we need for the + * string, roughly. We add one extra + * byte in case the last input + * character is a backslash. */ if (!out) return 0; @@ -256,6 +296,8 @@ parse_string (cJSON * item, const char *str, const char **ep) else { ptr++; + if (!*ptr) + break; switch (*ptr) { case 'b': @@ -275,17 +317,22 @@ parse_string (cJSON * item, const char *str, const char **ep) break; case 'u': /* transcode utf16 to utf8. */ uc = parse_hex4 (ptr + 1); + if (!uc) + break; /* Bad hex; continue right after the 'u'. */ ptr += 4; /* get the unicode char. */ - if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) + if ((uc >= 0xDC00 && uc <= 0xDFFF)) break; /* check for invalid. */ if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ { if (ptr[1] != '\\' || ptr[2] != 'u') break; /* missing second-half of surrogate. */ - uc2 = parse_hex4 (ptr + 3); - ptr += 6; + ptr += 2; + uc2 = parse_hex4 (ptr + 1); + if (!uc2) + break; /* Bad hex; continue right after the 'u'. */ + ptr += 4; if (uc2 < 0xDC00 || uc2 > 0xDFFF) break; /* invalid second-half of surrogate. */ uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); @@ -317,6 +364,8 @@ parse_string (cJSON * item, const char *str, const char **ep) ptr2 += len; break; default: + /* Fixme: Should we fail here: See + * https://github.com/DaveGamble/cJSON/issues/10 */ *ptr2++ = *ptr; break; } @@ -929,9 +978,11 @@ create_reference (cJSON * item) void cJSON_AddItemToArray (cJSON * array, cJSON * item) { - cJSON *c = array->child; - if (!item) + cJSON *c; + + if (!item || !array) return; + c = array->child; if (!c) { array->child = item; @@ -1132,6 +1183,8 @@ cJSON_ReplaceItemInObject (cJSON * object, const char *string, i++, c = c->next; if (c) { + /* FIXME: I guess we should free newitem->string here. See + * upstream commit 0d10e279c8b604f71829b5d49d092719f4ae96b6. */ newitem->string = xtrystrdup (string); cJSON_ReplaceItemInArray (object, i, newitem); } @@ -1393,9 +1446,11 @@ cJSON_Minify (char *json) { if (*json == '\\') *into++ = *json++; - *into++ = *json++; + if (*json) + *into++ = *json++; } - *into++ = *json++; + if (*json) + *into++ = *json++; } /* String literals, which are \" sensitive. */ else *into++ = *json++; /* All other characters. */ diff --git a/src/context.h b/src/context.h index c8e75ba0..1c9379b8 100644 --- a/src/context.h +++ b/src/context.h @@ -124,6 +124,10 @@ struct gpgme_context /* Do not use the symmtric encryption passphrase cache. */ unsigned int no_symkey_cache : 1; + /* Pass --ignore-mdc-error to gpg. Note that this flag is reset + * after the operation. */ + unsigned int ignore_mdc_error : 1; + /* Flags for keylist mode. */ gpgme_keylist_mode_t keylist_mode; @@ -151,6 +155,9 @@ struct gpgme_context /* The optional request origin. */ char *request_origin; + /* The optional auto key locate options. */ + char *auto_key_locate; + /* The locale for the pinentry. */ char *lc_ctype; char *lc_messages; diff --git a/src/data-estream.c b/src/data-estream.c new file mode 100644 index 00000000..34f88a7f --- /dev/null +++ b/src/data-estream.c @@ -0,0 +1,99 @@ +/* data-stream.c - A stream based data object. + * Copyright (C) 2002, 2004, 2018 g10 Code GmbH + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif + +#include "debug.h" +#include "data.h" + + +static gpgme_ssize_t +stream_es_read (gpgme_data_t dh, void *buffer, size_t size) +{ + size_t amt = gpgrt_fread (buffer, 1, size, dh->data.e_stream); + if (amt > 0) + return amt; + return gpgrt_ferror (dh->data.e_stream) ? -1 : 0; +} + + +static gpgme_ssize_t +stream_es_write (gpgme_data_t dh, const void *buffer, size_t size) +{ + size_t amt = gpgrt_fwrite (buffer, 1, size, dh->data.e_stream); + if (amt > 0) + return amt; + return gpgrt_ferror (dh->data.e_stream) ? -1 : 0; +} + + +static gpgme_off_t +stream_es_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) +{ + int err; + + err = gpgrt_fseeko (dh->data.e_stream, offset, whence); + if (err) + return -1; + + return gpgrt_ftello (dh->data.e_stream); +} + + +static int +stream_es_get_fd (gpgme_data_t dh) +{ + gpgrt_fflush (dh->data.e_stream); + return gpgrt_fileno (dh->data.e_stream); +} + + +static struct _gpgme_data_cbs stream_es_cbs = + { + stream_es_read, + stream_es_write, + stream_es_seek, + NULL, + stream_es_get_fd + }; + + + +gpgme_error_t +gpgme_data_new_from_estream (gpgme_data_t *r_dh, gpgrt_stream_t stream) +{ + gpgme_error_t err; + TRACE_BEG1 (DEBUG_DATA, "gpgme_data_new_from_estream", r_dh, "estream=%p", + stream); + + err = _gpgme_data_new (r_dh, &stream_es_cbs); + if (err) + return TRACE_ERR (err); + + (*r_dh)->data.e_stream = stream; + return TRACE_SUC1 ("dh=%p", *r_dh); +} diff --git a/src/data-mem.c b/src/data-mem.c index a498b826..7569f7df 100644 --- a/src/data-mem.c +++ b/src/data-mem.c @@ -224,7 +224,10 @@ gpgme_data_new_from_mem (gpgme_data_t *r_dh, const char *buffer, char * gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) { + gpg_error_t err; char *str = NULL; + size_t len; + int blankout; TRACE_BEG1 (DEBUG_DATA, "gpgme_data_release_and_get_mem", dh, "r_len=%p", r_len); @@ -236,10 +239,22 @@ gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) return NULL; } + err = _gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout); + if (err) + { + gpgme_data_release (dh); + TRACE_ERR (err); + return NULL; + } + str = dh->data.mem.buffer; + len = dh->data.mem.length; + if (blankout && len) + len = 1; + if (!str && dh->data.mem.orig_buffer) { - str = malloc (dh->data.mem.length); + str = malloc (len); if (!str) { int saved_err = gpg_error_from_syserror (); @@ -247,15 +262,22 @@ gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) TRACE_ERR (saved_err); return NULL; } - memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length); + if (blankout) + memset (str, 0, len); + else + memcpy (str, dh->data.mem.orig_buffer, len); } else - /* Prevent mem_release from releasing the buffer memory. We must - not fail from this point. */ - dh->data.mem.buffer = NULL; + { + if (blankout && len) + *str = 0; + /* Prevent mem_release from releasing the buffer memory. We + * must not fail from this point. */ + dh->data.mem.buffer = NULL; + } if (r_len) - *r_len = dh->data.mem.length; + *r_len = len; gpgme_data_release (dh); @@ -28,6 +28,7 @@ #endif #include <errno.h> #include <string.h> +#include <assert.h> #include "gpgme.h" #include "data.h" @@ -36,10 +37,271 @@ #include "priv-io.h" #include "debug.h" + +/* The property table which has an entry for each active data object. + * The data object itself uses an index into this table and the table + * has a pointer back to the data object. All access to that table is + * controlled by the property_table_lock. + * + * We use a separate table instead of linking all data objects + * together for faster locating properties of the data object using + * the data objects serial number. We use 64 bit for the serial + * number which is good enough to create a new data object every + * nanosecond for more than 500 years. Thus no wrap around will ever + * happen. + */ +struct property_s +{ + gpgme_data_t dh; /* The data objcet or NULL if the slot is not used. */ + uint64_t dserial; /* The serial number of the data object. */ + struct { + unsigned int blankout : 1; /* Void the held data. */ + } flags; +}; +typedef struct property_s *property_t; + +static property_t property_table; +static unsigned int property_table_size; +DEFINE_STATIC_LOCK (property_table_lock); +#define PROPERTY_TABLE_ALLOCATION_CHUNK 32 + + + +/* Insert the newly created data object DH into the property table and + * store the index of it at R_IDX. An error code is returned on error + * and the table is not changed. */ +static gpg_error_t +insert_into_property_table (gpgme_data_t dh, unsigned int *r_idx) +{ + static uint64_t last_dserial; + gpg_error_t err; + unsigned int idx; + + LOCK (property_table_lock); + if (!property_table) + { + property_table_size = PROPERTY_TABLE_ALLOCATION_CHUNK; + property_table = calloc (property_table_size, sizeof *property_table); + if (!property_table) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Find an empty slot. */ + for (idx = 0; idx < property_table_size; idx++) + if (!property_table[idx].dh) + break; + if (!(idx < property_table_size)) + { + /* No empty slot found. Enlarge the table. */ + property_t newtbl; + unsigned int newsize; + + newsize = property_table_size + PROPERTY_TABLE_ALLOCATION_CHUNK;; + if ((newsize * sizeof *property_table) + < (property_table_size * sizeof *property_table)) + { + err = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + newtbl = realloc (property_table, newsize * sizeof *property_table); + if (!newtbl) + { + err = gpg_error_from_syserror (); + goto leave; + } + property_table = newtbl; + for (idx = property_table_size; idx < newsize; idx++) + property_table[idx].dh = NULL; + idx = property_table_size; + property_table_size = newsize; + } + + /* Slot found. */ + property_table[idx].dh = dh; + property_table[idx].dserial = ++last_dserial; + memset (&property_table[idx].flags, 0, sizeof property_table[idx].flags); + *r_idx = idx; + err = 0; + + leave: + UNLOCK (property_table_lock); + return err; +} + + +/* Remove the data object at PROPIDX from the table. DH is only used + * for cross checking. */ +static void +remove_from_property_table (gpgme_data_t dh, unsigned int propidx) +{ + LOCK (property_table_lock); + assert (property_table); + assert (propidx < property_table_size); + assert (property_table[propidx].dh == dh); + property_table[propidx].dh = NULL; + UNLOCK (property_table_lock); +} + + +/* Return the data object's serial number for handle DH. This is a + * unique serial number for each created data object. */ +uint64_t +_gpgme_data_get_dserial (gpgme_data_t dh) +{ + uint64_t dserial; + unsigned int idx; + + if (!dh) + return 0; + + idx = dh->propidx; + LOCK (property_table_lock); + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + dserial = property_table[idx].dserial; + UNLOCK (property_table_lock); + + return dserial; +} + + +/* Set an internal property of a data object. The data object may + * either be identified by the usual DH or by using the data serial + * number DSERIAL. */ +gpg_error_t +_gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int value) +{ + gpg_error_t err = 0; + int idx; + TRACE_BEG3 (DEBUG_DATA, "gpgme_data_set_prop", dh, + "dserial=%llu %lu=%d", + (unsigned long long)dserial, + (unsigned long)name, value); + + LOCK (property_table_lock); + if ((!dh && !dserial) || (dh && dserial)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (dh) /* Lookup via handle. */ + { + idx = dh->propidx; + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + } + else /* Lookup via DSERIAL. */ + { + if (!property_table) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + for (idx = 0; idx < property_table_size; idx++) + if (property_table[idx].dh && property_table[idx].dserial == dserial) + break; + if (!(idx < property_table_size)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + switch (name) + { + case DATA_PROP_NONE: /* Nothing to to do. */ + break; + case DATA_PROP_BLANKOUT: + property_table[idx].flags.blankout = !!value; + break; + + default: + err = gpg_error (GPG_ERR_UNKNOWN_NAME); + break; + } + + leave: + UNLOCK (property_table_lock); + return TRACE_ERR (err); +} + + +/* Get an internal property of a data object. This is the counter + * part to _gpgme_data_set_property. The value of the property is + * stored at R_VALUE. On error 0 is stored at R_VALUE. */ +gpg_error_t +_gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int *r_value) +{ + gpg_error_t err = 0; + int idx; + TRACE_BEG2 (DEBUG_DATA, "gpgme_data_get_prop", dh, + "dserial=%llu %lu", + (unsigned long long)dserial, + (unsigned long)name); + + *r_value = 0; + + LOCK (property_table_lock); + if ((!dh && !dserial) || (dh && dserial)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (dh) /* Lookup via handle. */ + { + idx = dh->propidx; + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + } + else /* Lookup via DSERIAL. */ + { + if (!property_table) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + for (idx = 0; idx < property_table_size; idx++) + if (property_table[idx].dh && property_table[idx].dserial == dserial) + break; + if (!(idx < property_table_size)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + switch (name) + { + case DATA_PROP_NONE: /* Nothing to to do. */ + break; + case DATA_PROP_BLANKOUT: + *r_value = property_table[idx].flags.blankout; + break; + + default: + err = gpg_error (GPG_ERR_UNKNOWN_NAME); + break; + } + + leave: + UNLOCK (property_table_lock); + return TRACE_ERR (err); +} + + gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs) { + gpgme_error_t err; gpgme_data_t dh; if (!r_dh) @@ -56,6 +318,13 @@ _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs) dh->cbs = cbs; + err = insert_into_property_table (dh, &dh->propidx); + if (err) + { + free (dh); + return err; + } + *r_dh = dh; return 0; } @@ -67,11 +336,13 @@ _gpgme_data_release (gpgme_data_t dh) if (!dh) return; + remove_from_property_table (dh, dh->propidx); if (dh->file_name) free (dh->file_name); free (dh); } + /* Read up to SIZE bytes into buffer BUFFER from the data object with the handle DH. Return the number of characters read, 0 on EOF and @@ -80,6 +351,7 @@ gpgme_ssize_t gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size) { gpgme_ssize_t res; + int blankout; TRACE_BEG2 (DEBUG_DATA, "gpgme_data_read", dh, "buffer=%p, size=%u", buffer, size); @@ -93,9 +365,16 @@ gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size) gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } - do - res = (*dh->cbs->read) (dh, buffer, size); - while (res < 0 && errno == EINTR); + + if (_gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout) + || blankout) + res = 0; + else + { + do + res = (*dh->cbs->read) (dh, buffer, size); + while (res < 0 && errno == EINTR); + } return TRACE_SYSRES (res); } @@ -29,6 +29,7 @@ # include <sys/types.h> #endif #include <limits.h> +#include <stdint.h> #include "gpgme.h" @@ -73,6 +74,7 @@ struct gpgme_data { struct _gpgme_data_cbs *cbs; gpgme_data_encoding_t encoding; + unsigned int propidx; /* Index into the property table. */ #ifdef PIPE_BUF #define BUFFER_SIZE PIPE_BUF @@ -89,7 +91,7 @@ struct gpgme_data /* File name of the data object. */ char *file_name; - /* Hint on the to be expected toatl size of the data. */ + /* Hint on the to be expected total size of the data. */ gpgme_off_t size_hint; union @@ -100,6 +102,9 @@ struct gpgme_data /* For gpgme_data_new_from_stream. */ FILE *stream; + /* For gpgme_data_new_from_estream. */ + gpgrt_stream_t e_stream; + /* For gpgme_data_new_from_cbs. */ struct { @@ -127,7 +132,28 @@ struct gpgme_data } data; }; + +/* The data property types. */ +typedef enum + { + DATA_PROP_NONE = 0, /* Dummy property. */ + DATA_PROP_BLANKOUT /* Do not return the held data. */ + } data_prop_t; + + +/* Return the data object's serial number for handle DH. */ +uint64_t _gpgme_data_get_dserial (gpgme_data_t dh); + +/* Set an internal property of a data object. */ +gpg_error_t _gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int value); + +/* Get an internal property of a data object. */ +gpg_error_t _gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int *r_value); + +/* Create a new data object. */ gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs); @@ -140,4 +166,5 @@ int _gpgme_data_get_fd (gpgme_data_t dh); /* Get the size-hint value for DH or 0 if not available. */ gpgme_off_t _gpgme_data_get_size_hint (gpgme_data_t dh); + #endif /* DATA_H */ diff --git a/src/decrypt-verify.c b/src/decrypt-verify.c index 17f79acd..224edc10 100644 --- a/src/decrypt-verify.c +++ b/src/decrypt-verify.c @@ -58,7 +58,7 @@ decrypt_verify_start (gpgme_ctx_t ctx, int synchronous, if (err) return err; - err = _gpgme_op_decrypt_init_result (ctx); + err = _gpgme_op_decrypt_init_result (ctx, plain); if (err) return err; @@ -74,7 +74,7 @@ decrypt_verify_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -127,6 +127,7 @@ gpgme_op_decrypt_verify (gpgme_ctx_t ctx, gpgme_data_t cipher, err = decrypt_verify_start (ctx, 1, GPGME_DECRYPT_VERIFY, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); + ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } @@ -177,5 +178,6 @@ gpgme_op_decrypt_ext (gpgme_ctx_t ctx, err = _gpgme_decrypt_start (ctx, 1, flags, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); + ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } diff --git a/src/decrypt.c b/src/decrypt.c index ecd9c144..b51603a3 100644 --- a/src/decrypt.c +++ b/src/decrypt.c @@ -32,7 +32,7 @@ #include "util.h" #include "context.h" #include "ops.h" - +#include "data.h" typedef struct @@ -53,18 +53,25 @@ typedef struct * status lines for each key the message has been encrypted to but * that secret key is not available. This can't be done for hidden * recipients, though. We track it here to allow for a better error - * message that the general DECRYPTION_FAILED. */ + * message than the general DECRYPTION_FAILED. */ int any_no_seckey; /* If the engine emits a DECRYPTION_INFO status and that does not - * indicate that an integrity proetction mode is active, this flag + * indicate that an integrity protection mode is active, this flag * is set. */ int not_integrity_protected; + /* The error code from the first ERROR line. This is in some cases + * used to return a better matching error code to the caller. */ + gpg_error_t first_status_error; + /* A pointer to the next pointer of the last recipient in the list. This makes appending new invalid signers painless while preserving the order. */ gpgme_recipient_t *last_recipient_p; + + /* The data object serial number of the plaintext. */ + uint64_t plaintext_dserial; } *op_data_t; @@ -97,6 +104,8 @@ gpgme_op_decrypt_result (gpgme_ctx_t ctx) TRACE_BEG (DEBUG_CTX, "gpgme_op_decrypt_result", ctx); + ctx->ignore_mdc_error = 0; /* Always reset this flag. */ + err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, -1, NULL); opd = hook; if (err || !opd) @@ -214,6 +223,15 @@ parse_status_error (char *args, op_data_t opd) break; } } + else if (!strcmp (field[0], "nomdc_with_legacy_cipher")) + { + opd->result.legacy_cipher_nomdc = 1; + opd->not_integrity_protected = 1; + } + + /* Record the first error code. */ + if (err && !opd->first_status_error) + opd->first_status_error = err; free (args2); @@ -353,16 +371,43 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, * only a warning. * Fixme: These error values should probably be attributed to * the underlying crypto engine (as error source). */ - if (opd->failed && opd->pkdecrypt_failed) - return opd->pkdecrypt_failed; - else if (opd->failed && opd->any_no_seckey) - return gpg_error (GPG_ERR_NO_SECKEY); - else if (opd->failed || opd->not_integrity_protected) - return gpg_error (GPG_ERR_DECRYPT_FAILED); + if (opd->failed) + { + /* This comes from a specialized ERROR status line. */ + if (opd->pkdecrypt_failed) + return opd->pkdecrypt_failed; + + /* For an integrity failure return just DECRYPTION_FAILED; + * the actual cause can be taken from an already set + * decryption result flag. */ + if ((opd->not_integrity_protected && !ctx->ignore_mdc_error)) + return gpg_error (GPG_ERR_DECRYPT_FAILED); + + /* If we have any other ERROR code we prefer that over + * NO_SECKEY because it is probably the better matching + * code. For example a garbled message with multiple + * plaintext will return BAD_DATA here but may also have + * indicated a NO_SECKEY. */ + if (opd->first_status_error) + return opd->first_status_error; + + /* No secret key is pretty common reason. */ + if (opd->any_no_seckey) + return gpg_error (GPG_ERR_NO_SECKEY); + + /* Generic decryption failed error code. */ + return gpg_error (GPG_ERR_DECRYPT_FAILED); + } else if (!opd->okay) - return gpg_error (GPG_ERR_NO_DATA); + { + /* No data was found. */ + return gpg_error (GPG_ERR_NO_DATA); + } else if (opd->failure_code) - return opd->failure_code; + { + /* The engine returned failure code at program exit. */ + return opd->failure_code; + } break; case GPGME_STATUS_DECRYPTION_INFO: @@ -377,12 +422,21 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, case GPGME_STATUS_DECRYPTION_FAILED: opd->failed = 1; + /* Tell the data object that it shall not return any data. We + * use the serial number because the data object may be owned by + * another thread. We also don't check for an error because it + * is possible that the data object has already been destroyed + * and we are then not interested in returning an error. */ + if (!ctx->ignore_mdc_error) + _gpgme_data_set_prop (NULL, opd->plaintext_dserial, + DATA_PROP_BLANKOUT, 1); break; case GPGME_STATUS_ERROR: /* Note that this is an informational status code which should - not lead to an error return unless it is something not - related to the backend. */ + * not lead to an error return unless it is something not + * related to the backend. However, it is used to return a + * better matching final error code. */ err = parse_status_error (args, opd); if (err) return err; @@ -465,7 +519,7 @@ decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args) gpgme_error_t -_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx) +_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx, gpgme_data_t plaintext) { gpgme_error_t err; void *hook; @@ -478,6 +532,7 @@ _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx) return err; opd->last_recipient_p = &opd->result.recipients; + opd->plaintext_dserial = _gpgme_data_get_dserial (plaintext); return 0; } @@ -495,7 +550,7 @@ _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous, if (err) return err; - err = _gpgme_op_decrypt_init_result (ctx); + err = _gpgme_op_decrypt_init_result (ctx, plain); if (err) return err; @@ -510,7 +565,7 @@ _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -559,5 +614,6 @@ gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) err = _gpgme_decrypt_start (ctx, 1, 0, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); + ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } @@ -139,8 +139,7 @@ interact_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key, opd->fnc_old = NULL; opd->fnc_value = fnc_value; - err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, - ctx, out); + err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, ctx); if (err) return err; @@ -219,8 +218,7 @@ edit_start (gpgme_ctx_t ctx, int synchronous, int type, gpgme_key_t key, opd->fnc_old = fnc; opd->fnc_value = fnc_value; - err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, - ctx, out); + err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, ctx); if (err) return err; diff --git a/src/encrypt-sign.c b/src/encrypt-sign.c index 4db46e25..cc34fbd5 100644 --- a/src/encrypt-sign.c +++ b/src/encrypt-sign.c @@ -93,7 +93,7 @@ encrypt_sign_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t recp[], if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/encrypt.c b/src/encrypt.c index 2318497e..a27a53ac 100644 --- a/src/encrypt.c +++ b/src/encrypt.c @@ -242,7 +242,7 @@ encrypt_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t recp[], { /* Symmetric encryption requires a passphrase. */ err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/engine-backend.h b/src/engine-backend.h index f6926662..4f33da1c 100644 --- a/src/engine-backend.h +++ b/src/engine-backend.h @@ -55,7 +55,7 @@ struct engine_ops void *fnc_value); gpgme_error_t (*set_command_handler) (void *engine, engine_command_handler_t fnc, - void *fnc_value, gpgme_data_t data); + void *fnc_value); gpgme_error_t (*set_colon_line_handler) (void *engine, engine_colon_line_handler_t fnc, void *fnc_value); diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 173e940c..be78957f 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -26,7 +26,6 @@ #include <stdlib.h> #include <string.h> #include <assert.h> -#include <errno.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif @@ -136,23 +135,24 @@ struct engine_gpg char *keyword; /* what has been requested (malloced) */ engine_command_handler_t fnc; void *fnc_value; - /* The kludges never end. This is used to couple command handlers - with output data in edit key mode. */ - gpgme_data_t linked_data; - int linked_idx; } cmd; struct gpgme_io_cbs io_cbs; gpgme_pinentry_mode_t pinentry_mode; char request_origin[10]; + char *auto_key_locate; struct { unsigned int no_symkey_cache : 1; unsigned int offline : 1; + unsigned int ignore_mdc_error : 1; } flags; /* NULL or the data object fed to --override_session_key-fd. */ gpgme_data_t override_session_key; + + /* Memory data containing diagnostics (--logger-fd) of gpg */ + gpgme_data_t diagnostics; }; typedef struct engine_gpg *engine_gpg_t; @@ -454,8 +454,10 @@ gpg_release (void *engine) free_argv (gpg->argv); if (gpg->cmd.keyword) free (gpg->cmd.keyword); + free (gpg->auto_key_locate); gpgme_data_release (gpg->override_session_key); + gpgme_data_release (gpg->diagnostics); free (gpg); } @@ -503,8 +505,6 @@ gpg_new (void **engine, const char *file_name, const char *home_dir, gpg->colon.fd[1] = -1; gpg->cmd.fd = -1; gpg->cmd.idx = -1; - gpg->cmd.linked_data = NULL; - gpg->cmd.linked_idx = -1; /* Allocate the read buffer for the status pipe. */ gpg->status.bufsize = 1024; @@ -626,6 +626,16 @@ gpg_new (void **engine, const char *file_name, const char *home_dir, } } + rc = gpgme_data_new (&gpg->diagnostics); + if (rc) + goto leave; + + rc = add_arg (gpg, "--logger-fd"); + if (rc) + goto leave; + + rc = add_data (gpg, gpg->diagnostics, -2, 1); + leave: if (rc) gpg_release (gpg); @@ -651,11 +661,20 @@ gpg_set_engine_flags (void *engine, const gpgme_ctx_t ctx) else *gpg->request_origin = 0; + if (ctx->auto_key_locate && have_gpg_version (gpg, "2.1.18")) + { + if (gpg->auto_key_locate) + free (gpg->auto_key_locate); + gpg->auto_key_locate = _gpgme_strconcat ("--auto-key-locate=", + ctx->auto_key_locate, NULL); + } + gpg->flags.no_symkey_cache = (ctx->no_symkey_cache && have_gpg_version (gpg, "2.2.7")); - gpg->flags.offline = (ctx->offline && have_gpg_version (gpg, "2.1.23")); + gpg->flags.ignore_mdc_error = !!ctx->ignore_mdc_error; + } @@ -793,14 +812,14 @@ command_handler (void *opaque, int fd) -/* The Fnc will be called to get a value for one of the commands with - a key KEY. If the Code passed to FNC is 0, the function may release - resources associated with the returned value from another call. To - match such a second call to a first call, the returned value from - the first call is passed as keyword. */ +/* The FNC will be called to get a value for one of the commands with + * a key KEY. If the code passed to FNC is 0, the function may + * release resources associated with the returned value from another + * call. To match such a second call to a first call, the returned + * value from the first call is passed as keyword. */ static gpgme_error_t gpg_set_command_handler (void *engine, engine_command_handler_t fnc, - void *fnc_value, gpgme_data_t linked_data) + void *fnc_value) { engine_gpg_t gpg = engine; gpgme_error_t rc; @@ -819,7 +838,6 @@ gpg_set_command_handler (void *engine, engine_command_handler_t fnc, gpg->cmd.fnc = fnc; gpg->cmd.cb_data = (void *) &gpg->cmd; gpg->cmd.fnc_value = fnc_value; - gpg->cmd.linked_data = linked_data; gpg->cmd.used = 1; return 0; } @@ -950,6 +968,19 @@ build_argv (engine_gpg_t gpg, const char *pgmname) argc++; } + if (gpg->auto_key_locate) + { + argv[argc] = strdup (gpg->auto_key_locate); + if (!argv[argc]) + { + int saved_err = gpg_error_from_syserror (); + free (fd_data_map); + free_argv (argv); + return saved_err; + } + argc++; + } + if (gpg->flags.no_symkey_cache) { argv[argc] = strdup ("--no-symkey-cache"); @@ -963,6 +994,19 @@ build_argv (engine_gpg_t gpg, const char *pgmname) argc++; } + if (gpg->flags.ignore_mdc_error) + { + argv[argc] = strdup ("--ignore-mdc-error"); + if (!argv[argc]) + { + int saved_err = gpg_error_from_syserror (); + free (fd_data_map); + free_argv (argv); + return saved_err; + } + argc++; + } + if (gpg->flags.offline) { argv[argc] = strdup ("--disable-dirmngr"); @@ -1039,10 +1083,10 @@ build_argv (engine_gpg_t gpg, const char *pgmname) if (_gpgme_io_pipe (fds, fd_data_map[datac].inbound ? 1 : 0) == -1) { - int saved_errno = errno; + int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); - return gpg_error (saved_errno); + return saved_err; } if (_gpgme_io_set_close_notify (fds[0], close_notify_handler, gpg) @@ -1077,11 +1121,6 @@ build_argv (engine_gpg_t gpg, const char *pgmname) assert (gpg->cmd.idx == -1); gpg->cmd.idx = datac; } - else if (gpg->cmd.linked_data == a->data) - { - assert (gpg->cmd.linked_idx == -1); - gpg->cmd.linked_idx = datac; - } } fd_data_map[datac].data = a->data; @@ -1268,44 +1307,6 @@ read_status (engine_gpg_t gpg) if (err) return err; } - - if (r == GPGME_STATUS_END_STREAM) - { - if (gpg->cmd.used) - { - /* Before we can actually add the - command fd, we might have to flush - the linked output data pipe. */ - if (gpg->cmd.linked_idx != -1 - && gpg->fd_data_map[gpg->cmd.linked_idx].fd - != -1) - { - struct io_select_fd_s fds; - fds.fd = - gpg->fd_data_map[gpg->cmd.linked_idx].fd; - fds.for_read = 1; - fds.for_write = 0; - fds.opaque = NULL; - do - { - fds.signaled = 0; - _gpgme_io_select (&fds, 1, 1); - if (fds.signaled) - _gpgme_data_inbound_handler - (gpg->cmd.linked_data, fds.fd); - } - while (fds.signaled); - } - - /* XXX We must check if there are any - more fds active after removing this - one. */ - (*gpg->io_cbs.remove) - (gpg->fd_data_map[gpg->cmd.idx].tag); - gpg->cmd.fd = gpg->fd_data_map[gpg->cmd.idx].fd; - gpg->fd_data_map[gpg->cmd.idx].fd = -1; - } - } } } /* To reuse the buffer for the next line we have to @@ -3279,6 +3280,52 @@ gpg_set_pinentry_mode (void *engine, gpgme_pinentry_mode_t mode) } +static gpgme_error_t +gpg_getauditlog (void *engine, gpgme_data_t output, unsigned int flags) +{ + engine_gpg_t gpg = engine; +#define MYBUFLEN 4096 + char buf[MYBUFLEN]; + int nread; + int any_written = 0; + + if (!(flags & GPGME_AUDITLOG_DIAG)) + { + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + if (!gpg || !output) + { + return gpg_error (GPG_ERR_INV_VALUE); + } + + if (!gpg->diagnostics) + { + return gpg_error (GPG_ERR_GENERAL); + } + + gpgme_data_rewind (gpg->diagnostics); + + while ((nread = gpgme_data_read (gpg->diagnostics, buf, MYBUFLEN)) > 0) + { + any_written = 1; + if (gpgme_data_write (output, buf, nread) == -1) + return gpg_error_from_syserror (); + } + if (!any_written) + { + return gpg_error (GPG_ERR_NO_DATA); + } + + if (nread == -1) + return gpg_error_from_syserror (); + + gpgme_data_rewind (output); + return 0; +#undef MYBUFLEN +} + + struct engine_ops _gpgme_engine_ops_gpg = { @@ -3316,7 +3363,7 @@ struct engine_ops _gpgme_engine_ops_gpg = gpg_sign, gpg_trustlist, gpg_verify, - NULL, /* getauditlog */ + gpg_getauditlog, NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index 7b221831..3266e360 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -37,7 +37,6 @@ #include <locale.h> #endif #include <fcntl.h> /* FIXME */ -#include <errno.h> #include "gpgme.h" #include "util.h" @@ -986,8 +985,7 @@ status_handler (void *opaque, int fd) while (linelen > 0) { nwritten = gpgme_data_write (gpgsm->inline_data, src, linelen); - if (!nwritten || (nwritten < 0 && errno != EINTR) - || nwritten > linelen) + if (nwritten <= 0 || nwritten > linelen) { err = gpg_error_from_syserror (); break; @@ -2066,6 +2064,9 @@ gpgsm_getauditlog (void *engine, gpgme_data_t output, unsigned int flags) if (!gpgsm || !output) return gpg_error (GPG_ERR_INV_VALUE); + if ((flags & GPGME_AUDITLOG_DIAG)) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + #if USE_DESCRIPTOR_PASSING gpgsm->output_cb.data = output; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0); diff --git a/src/engine.c b/src/engine.c index b716ca24..b629bea7 100644 --- a/src/engine.c +++ b/src/engine.c @@ -596,8 +596,7 @@ _gpgme_engine_set_status_handler (engine_t engine, gpgme_error_t _gpgme_engine_set_command_handler (engine_t engine, engine_command_handler_t fnc, - void *fnc_value, - gpgme_data_t linked_data) + void *fnc_value) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); @@ -605,8 +604,7 @@ _gpgme_engine_set_command_handler (engine_t engine, if (!engine->ops->set_command_handler) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); - return (*engine->ops->set_command_handler) (engine->engine, - fnc, fnc_value, linked_data); + return (*engine->ops->set_command_handler) (engine->engine, fnc, fnc_value); } gpgme_error_t diff --git a/src/engine.h b/src/engine.h index 8b692f2e..c512a252 100644 --- a/src/engine.h +++ b/src/engine.h @@ -78,8 +78,7 @@ void _gpgme_engine_set_status_handler (engine_t engine, void *fnc_value); gpgme_error_t _gpgme_engine_set_command_handler (engine_t engine, engine_command_handler_t fnc, - void *fnc_value, - gpgme_data_t data); + void *fnc_value); gpgme_error_t _gpgme_engine_set_colon_line_handler (engine_t engine, engine_colon_line_handler_t fnc, diff --git a/src/genkey.c b/src/genkey.c index 16484ecc..ffca7e8e 100644 --- a/src/genkey.c +++ b/src/genkey.c @@ -259,7 +259,7 @@ genkey_start (gpgme_ctx_t ctx, int synchronous, const char *parms, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -345,7 +345,7 @@ createkey_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -433,7 +433,7 @@ createsubkey_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -519,7 +519,7 @@ addrevuid_start (gpgme_ctx_t ctx, int synchronous, int extraflags, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/src/getauditlog.c b/src/getauditlog.c index dbaf260e..d70e66fd 100644 --- a/src/getauditlog.c +++ b/src/getauditlog.c @@ -47,9 +47,12 @@ getauditlog_start (gpgme_ctx_t ctx, int synchronous, if (!output) return gpg_error (GPG_ERR_INV_VALUE); - err = _gpgme_op_reset (ctx, ((synchronous&255) | 256) ); - if (err) - return err; + if (!(flags & GPGME_AUDITLOG_DIAG)) + { + err = _gpgme_op_reset (ctx, ((synchronous&255) | 256) ); + if (err) + return err; + } _gpgme_engine_set_status_handler (ctx->engine, getauditlog_status_handler, ctx); diff --git a/src/gpgme-json.c b/src/gpgme-json.c index a755500d..d636ddbe 100644 --- a/src/gpgme-json.c +++ b/src/gpgme-json.c @@ -48,24 +48,25 @@ int main (void){fputs ("Build with Libgpg-error >= 1.28!\n", stderr);return 1;} /* We don't allow a request with more than 64 MiB. */ #define MAX_REQUEST_SIZE (64 * 1024 * 1024) -/* Minimal, default and maximum chunk size for returned data. The - * first chunk is returned directly. If the "more" flag is also - * returned, a "getmore" command needs to be used to get the next - * chunk. Right now this value covers just the value of the "data" - * element; so to cover for the other returned objects this values - * needs to be lower than the maximum allowed size of the browser. */ -#define MIN_REPLY_CHUNK_SIZE 512 -#define DEF_REPLY_CHUNK_SIZE (512 * 1024) +/* Minimal chunk size for returned data.*/ +#define MIN_REPLY_CHUNK_SIZE 30 + +/* If no chunksize is provided we print everything. Changing + * this to a positive value will result in all messages beeing + * chunked. */ +#define DEF_REPLY_CHUNK_SIZE 0 #define MAX_REPLY_CHUNK_SIZE (10 * 1024 * 1024) static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN; static cjson_t error_object_v (cjson_t json, const char *message, - va_list arg_ptr) GPGRT_ATTR_PRINTF(2,0); + va_list arg_ptr, gpg_error_t err) + GPGRT_ATTR_PRINTF(2,0); static cjson_t error_object (cjson_t json, const char *message, ...) GPGRT_ATTR_PRINTF(2,3); static char *error_object_string (const char *message, ...) GPGRT_ATTR_PRINTF(1,2); +static char *process_request (const char *request); /* True if interactive mode is active. */ @@ -79,8 +80,6 @@ static struct char *buffer; /* Malloced data or NULL if not used. */ size_t length; /* Length of that data. */ size_t written; /* # of already written bytes from BUFFER. */ - const char *type;/* The "type" of the data. */ - int base64; /* The "base64" flag of the data. */ } pending_data; @@ -88,13 +87,7 @@ static struct * Helper functions and macros */ -#define xtrymalloc(a) gpgrt_malloc ((a)) #define xtrystrdup(a) gpgrt_strdup ((a)) -#define xmalloc(a) ({ \ - void *_r = gpgrt_malloc ((a)); \ - if (!_r) \ - xoutofcore ("malloc"); \ - _r; }) #define xcalloc(a,b) ({ \ void *_r = gpgrt_calloc ((a), (b)); \ if (!_r) \ @@ -112,6 +105,21 @@ static struct _r; }) #define xfree(a) gpgrt_free ((a)) +/* Only use calloc. */ +#define CALLOC_ONLY 1 + +#if CALLOC_ONLY +#define xtrymalloc(a) gpgrt_calloc (1, (a)) +#define xmalloc(a) xcalloc(1, (a)) +#else +#define xtrymalloc(a) gpgrt_malloc ((a)) +#define xmalloc(a) ({ \ + void *_r = gpgrt_malloc ((a)); \ + if (!_r) \ + xoutofcore ("malloc"); \ + _r; }) +#endif + #define spacep(p) (*(p) == ' ' || *(p) == '\t') #ifndef HAVE_STPCPY @@ -127,6 +135,19 @@ _my_stpcpy (char *a, const char *b) #endif /*!HAVE_STPCPY*/ +/* Free a NULL terminated array */ +static void +xfree_array (char **array) +{ + if (array) + { + int idx; + for (idx = 0; array[idx]; idx++) + xfree (array[idx]); + xfree (array); + } +} + static void xoutofcore (const char *type) @@ -179,6 +200,15 @@ xjson_AddStringToObject (cjson_t object, const char *name, const char *string) } +/* Same as xjson_AddStringToObject but ignores NULL strings */ +static void +xjson_AddStringToObject0 (cjson_t object, const char *name, const char *string) +{ + if (!string) + return; + xjson_AddStringToObject (object, name, string); +} + /* Wrapper around cJSON_AddBoolToObject which terminates the process * in case of an error. */ static void @@ -189,6 +219,26 @@ xjson_AddBoolToObject (cjson_t object, const char *name, int abool) return ; } +/* Wrapper around cJSON_AddNumberToObject which terminates the process + * in case of an error. */ +static void +xjson_AddNumberToObject (cjson_t object, const char *name, double dbl) +{ + if (!cJSON_AddNumberToObject (object, name, dbl)) + xoutofcore ("cJSON_AddNumberToObject"); + return ; +} + +/* Wrapper around cJSON_AddItemToObject which terminates the process + * in case of an error. */ +static void +xjson_AddItemToObject (cjson_t object, const char *name, cjson_t item) +{ + if (!cJSON_AddItemToObject (object, name, item)) + xoutofcore ("cJSON_AddItemToObject"); + return ; +} + /* This is similar to cJSON_AddStringToObject but takes (DATA, * DATALEN) and adds it under NAME as a base 64 encoded string to * OBJECT. */ @@ -266,7 +316,8 @@ add_base64_to_object (cjson_t object, const char *name, /* Create a JSON error object. If JSON is not NULL the error message * is appended to that object. An existing "type" item will be replaced. */ static cjson_t -error_object_v (cjson_t json, const char *message, va_list arg_ptr) +error_object_v (cjson_t json, const char *message, va_list arg_ptr, + gpg_error_t err) { cjson_t response, j_tmp; char *msg; @@ -287,8 +338,10 @@ error_object_v (cjson_t json, const char *message, va_list arg_ptr) cJSON_ReplaceItemInObject (response, "type", j_tmp); } xjson_AddStringToObject (response, "msg", msg); - xfree (msg); + + xjson_AddNumberToObject (response, "code", err); + return response; } @@ -312,7 +365,20 @@ error_object (cjson_t json, const char *message, ...) va_list arg_ptr; va_start (arg_ptr, message); - response = error_object_v (json, message, arg_ptr); + response = error_object_v (json, message, arg_ptr, 0); + va_end (arg_ptr); + return response; +} + + +static cjson_t +gpg_error_object (cjson_t json, gpg_error_t err, const char *message, ...) +{ + cjson_t response; + va_list arg_ptr; + + va_start (arg_ptr, message); + response = error_object_v (json, message, arg_ptr, err); va_end (arg_ptr); return response; } @@ -326,7 +392,7 @@ error_object_string (const char *message, ...) char *msg; va_start (arg_ptr, message); - response = error_object_v (NULL, message, arg_ptr); + response = error_object_v (NULL, message, arg_ptr, 0); va_end (arg_ptr); msg = xjson_Print (response); @@ -408,12 +474,13 @@ get_chunksize (cjson_t json, size_t *r_chunksize) } -/* Extract the keys from the "keys" array in the JSON object. On - * success a string with the keys identifiers is stored at R_KEYS. +/* Extract the keys from the array or string with the name "name" + * in the JSON object. On success a string with the keys identifiers + * is stored at R_KEYS. * The keys in that string are LF delimited. On failure an error code * is returned. */ static gpg_error_t -get_keys (cjson_t json, char **r_keystring) +get_keys (cjson_t json, const char *name, char **r_keystring) { cjson_t j_keys, j_item; int i, nkeys; @@ -422,7 +489,7 @@ get_keys (cjson_t json, char **r_keystring) *r_keystring = NULL; - j_keys = cJSON_GetObjectItem (json, "keys"); + j_keys = cJSON_GetObjectItem (json, name); if (!j_keys) return gpg_error (GPG_ERR_NO_KEY); if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys)) @@ -505,7 +572,7 @@ _create_new_context (gpgme_protocol_t proto) static gpgme_ctx_t get_context (gpgme_protocol_t proto) { - static gpgme_ctx_t ctx_openpgp, ctx_cms; + static gpgme_ctx_t ctx_openpgp, ctx_cms, ctx_conf; if (proto == GPGME_PROTOCOL_OpenPGP) { @@ -519,12 +586,17 @@ get_context (gpgme_protocol_t proto) ctx_cms = _create_new_context (proto); return ctx_cms; } + else if (proto == GPGME_PROTOCOL_GPGCONF) + { + if (!ctx_conf) + ctx_conf = _create_new_context (proto); + return ctx_conf; + } else log_bug ("invalid protocol %d requested\n", proto); } - /* Free context object retrieved by get_context. */ static void release_context (gpgme_ctx_t ctx) @@ -534,6 +606,23 @@ release_context (gpgme_ctx_t ctx) } +/* Create an addition context for short operations. */ +static gpgme_ctx_t +create_onetime_context (gpgme_protocol_t proto) +{ + return _create_new_context (proto); + +} + + +/* Release a one-time context. */ +static void +release_onetime_context (gpgme_ctx_t ctx) +{ + return gpgme_release (ctx); + +} + /* Given a Base-64 encoded string object in JSON return a gpgme data * object at R_DATA. */ @@ -600,46 +689,161 @@ data_from_base64_string (gpgme_data_t *r_data, cjson_t json) } -/* Helper for summary formatting */ -static void -add_summary_to_object (cjson_t result, gpgme_sigsum_t summary) +/* Create a keylist pattern array from a json keys object + * in the request. Returns either a malloced NULL terminated + * string array which can be used as patterns for + * op_keylist_ext or NULL. */ +static char ** +create_keylist_patterns (cjson_t request, const char *name) +{ + char *keystring; + char *p; + char *tmp; + char **ret; + int cnt = 2; /* Last NULL and one is not newline delimited */ + int i = 0; + + if (get_keys (request, name, &keystring)) + return NULL; + + for (p = keystring; *p; p++) + if (*p == '\n') + cnt++; + + ret = xcalloc (cnt, sizeof *ret); + + for (p = keystring, tmp = keystring; *p; p++) + { + if (*p != '\n') + continue; + *p = '\0'; + ret[i++] = xstrdup (tmp); + tmp = p + 1; + } + /* The last key is not newline delimted. */ + ret[i] = *tmp ? xstrdup (tmp) : NULL; + + xfree (keystring); + return ret; +} + + +/* Do a secret keylisting for protocol proto and add the fingerprints of + the secret keys for patterns to the result as "sec-fprs" array. */ +static gpg_error_t +add_secret_fprs (const char **patterns, gpgme_protocol_t protocol, + cjson_t result) +{ + gpgme_ctx_t ctx; + gpg_error_t err; + gpgme_key_t key = NULL; + cjson_t j_fprs = xjson_CreateArray (); + + ctx = create_onetime_context (protocol); + + gpgme_set_keylist_mode (ctx, GPGME_KEYLIST_MODE_LOCAL | + GPGME_KEYLIST_MODE_WITH_SECRET); + + err = gpgme_op_keylist_ext_start (ctx, patterns, 1, 0); + + if (err) + { + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); + goto leave; + } + + while (!(err = gpgme_op_keylist_next (ctx, &key))) + { + if (!key || !key->fpr) + continue; + cJSON_AddItemToArray (j_fprs, cJSON_CreateString (key->fpr)); + gpgme_key_unref (key); + key = NULL; + } + err = 0; + + release_onetime_context (ctx); + ctx = NULL; + + xjson_AddItemToObject (result, "sec-fprs", j_fprs); + +leave: + release_onetime_context (ctx); + gpgme_key_unref (key); + + return err; +} + + +/* Create sigsum json array */ +static cjson_t +sigsum_to_json (gpgme_sigsum_t summary) { - cjson_t response = xjson_CreateArray (); + cjson_t result = xjson_CreateObject (); + cjson_t sigsum_array = xjson_CreateArray (); + if ( (summary & GPGME_SIGSUM_VALID )) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("valid")); if ( (summary & GPGME_SIGSUM_GREEN )) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("green")); if ( (summary & GPGME_SIGSUM_RED )) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("red")); if ( (summary & GPGME_SIGSUM_KEY_REVOKED)) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("revoked")); if ( (summary & GPGME_SIGSUM_KEY_EXPIRED)) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("key-expired")); if ( (summary & GPGME_SIGSUM_SIG_EXPIRED)) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("sig-expired")); if ( (summary & GPGME_SIGSUM_KEY_MISSING)) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("key-missing")); if ( (summary & GPGME_SIGSUM_CRL_MISSING)) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("crl-missing")); if ( (summary & GPGME_SIGSUM_CRL_TOO_OLD)) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("crl-too-old")); if ( (summary & GPGME_SIGSUM_BAD_POLICY )) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("bad-policy")); if ( (summary & GPGME_SIGSUM_SYS_ERROR )) - cJSON_AddItemToArray (response, + cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("sys-error")); + /* The signature summary as string array. */ + xjson_AddItemToObject (result, "sigsum", sigsum_array); + + /* Bools for the same. */ + xjson_AddBoolToObject (result, "valid", + (summary & GPGME_SIGSUM_VALID )); + xjson_AddBoolToObject (result, "green", + (summary & GPGME_SIGSUM_GREEN )); + xjson_AddBoolToObject (result, "red", + (summary & GPGME_SIGSUM_RED )); + xjson_AddBoolToObject (result, "revoked", + (summary & GPGME_SIGSUM_KEY_REVOKED)); + xjson_AddBoolToObject (result, "key-expired", + (summary & GPGME_SIGSUM_KEY_EXPIRED)); + xjson_AddBoolToObject (result, "sig-expired", + (summary & GPGME_SIGSUM_SIG_EXPIRED)); + xjson_AddBoolToObject (result, "key-missing", + (summary & GPGME_SIGSUM_KEY_MISSING)); + xjson_AddBoolToObject (result, "crl-missing", + (summary & GPGME_SIGSUM_CRL_MISSING)); + xjson_AddBoolToObject (result, "crl-too-old", + (summary & GPGME_SIGSUM_CRL_TOO_OLD)); + xjson_AddBoolToObject (result, "bad-policy", + (summary & GPGME_SIGSUM_BAD_POLICY )); + xjson_AddBoolToObject (result, "sys-error", + (summary & GPGME_SIGSUM_SYS_ERROR )); - cJSON_AddItemToObject (result, "summary", response); + return result; } @@ -659,151 +863,596 @@ validity_to_string (gpgme_validity_t val) } } +static const char * +protocol_to_string (gpgme_protocol_t proto) +{ + switch (proto) + { + case GPGME_PROTOCOL_OpenPGP: return "OpenPGP"; + case GPGME_PROTOCOL_CMS: return "CMS"; + case GPGME_PROTOCOL_GPGCONF: return "gpgconf"; + case GPGME_PROTOCOL_ASSUAN: return "assuan"; + case GPGME_PROTOCOL_G13: return "g13"; + case GPGME_PROTOCOL_UISERVER:return "uiserver"; + case GPGME_PROTOCOL_SPAWN: return "spawn"; + default: + return "unknown"; + } +} -/* Add a single signature to a result */ -static gpg_error_t -add_signature_to_object (cjson_t result, gpgme_signature_t sig) +/* Create a sig_notation json object */ +static cjson_t +sig_notation_to_json (gpgme_sig_notation_t not) { - gpg_error_t err = 0; + cjson_t result = xjson_CreateObject (); + xjson_AddBoolToObject (result, "human_readable", not->human_readable); + xjson_AddBoolToObject (result, "critical", not->critical); + + xjson_AddStringToObject0 (result, "name", not->name); + xjson_AddStringToObject0 (result, "value", not->value); + + xjson_AddNumberToObject (result, "flags", not->flags); + + return result; +} + +/* Create a key_sig json object */ +static cjson_t +key_sig_to_json (gpgme_key_sig_t sig) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", sig->revoked); + xjson_AddBoolToObject (result, "expired", sig->expired); + xjson_AddBoolToObject (result, "invalid", sig->invalid); + xjson_AddBoolToObject (result, "exportable", sig->exportable); - if (!cJSON_AddStringToObject (result, "status", gpgme_strerror (sig->status))) + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sig->pubkey_algo)); + xjson_AddStringToObject0 (result, "keyid", sig->keyid); + xjson_AddStringToObject0 (result, "status", gpgme_strerror (sig->status)); + xjson_AddStringToObject0 (result, "name", sig->name); + xjson_AddStringToObject0 (result, "email", sig->email); + xjson_AddStringToObject0 (result, "comment", sig->comment); + + xjson_AddNumberToObject (result, "pubkey_algo", sig->pubkey_algo); + xjson_AddNumberToObject (result, "timestamp", sig->timestamp); + xjson_AddNumberToObject (result, "expires", sig->expires); + xjson_AddNumberToObject (result, "status_code", sig->status); + xjson_AddNumberToObject (result, "sig_class", sig->sig_class); + + if (sig->notations) { - err = gpg_error_from_syserror (); - goto leave; + gpgme_sig_notation_t not; + cjson_t array = xjson_CreateArray (); + for (not = sig->notations; not; not = not->next) + cJSON_AddItemToArray (array, sig_notation_to_json (not)); + xjson_AddItemToObject (result, "notations", array); } - if (!cJSON_AddNumberToObject (result, "code", sig->status)) + return result; +} + +/* Create a tofu info object */ +static cjson_t +tofu_to_json (gpgme_tofu_info_t tofu) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "description", tofu->description); + + xjson_AddNumberToObject (result, "validity", tofu->validity); + xjson_AddNumberToObject (result, "policy", tofu->policy); + xjson_AddNumberToObject (result, "signcount", tofu->signcount); + xjson_AddNumberToObject (result, "encrcount", tofu->encrcount); + xjson_AddNumberToObject (result, "signfirst", tofu->signfirst); + xjson_AddNumberToObject (result, "signlast", tofu->signlast); + xjson_AddNumberToObject (result, "encrfirst", tofu->encrfirst); + xjson_AddNumberToObject (result, "encrlast", tofu->encrlast); + + return result; +} + +/* Create a userid json object */ +static cjson_t +uid_to_json (gpgme_user_id_t uid) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", uid->revoked); + xjson_AddBoolToObject (result, "invalid", uid->invalid); + + xjson_AddStringToObject0 (result, "validity", + validity_to_string (uid->validity)); + xjson_AddStringToObject0 (result, "uid", uid->uid); + xjson_AddStringToObject0 (result, "name", uid->name); + xjson_AddStringToObject0 (result, "email", uid->email); + xjson_AddStringToObject0 (result, "comment", uid->comment); + xjson_AddStringToObject0 (result, "address", uid->address); + + xjson_AddNumberToObject (result, "origin", uid->origin); + xjson_AddNumberToObject (result, "last_update", uid->last_update); + + /* Key sigs */ + if (uid->signatures) { - err = gpg_error_from_syserror (); - goto leave; - } + cjson_t sig_array = xjson_CreateArray (); + gpgme_key_sig_t sig; - add_summary_to_object (result, sig->summary); + for (sig = uid->signatures; sig; sig = sig->next) + cJSON_AddItemToArray (sig_array, key_sig_to_json (sig)); - if (!cJSON_AddStringToObject (result, "fingerprint", sig->fpr)) + xjson_AddItemToObject (result, "signatures", sig_array); + } + + /* TOFU info */ + if (uid->tofu) { - err = gpg_error_from_syserror (); - goto leave; + gpgme_tofu_info_t tofu; + cjson_t array = xjson_CreateArray (); + for (tofu = uid->tofu; tofu; tofu = tofu->next) + cJSON_AddItemToArray (array, tofu_to_json (tofu)); + xjson_AddItemToObject (result, "tofu", array); } - if (!cJSON_AddNumberToObject (result, "created", sig->timestamp)) + return result; +} + +/* Create a subkey json object */ +static cjson_t +subkey_to_json (gpgme_subkey_t sub) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", sub->revoked); + xjson_AddBoolToObject (result, "expired", sub->expired); + xjson_AddBoolToObject (result, "disabled", sub->disabled); + xjson_AddBoolToObject (result, "invalid", sub->invalid); + xjson_AddBoolToObject (result, "can_encrypt", sub->can_encrypt); + xjson_AddBoolToObject (result, "can_sign", sub->can_sign); + xjson_AddBoolToObject (result, "can_certify", sub->can_certify); + xjson_AddBoolToObject (result, "can_authenticate", sub->can_authenticate); + xjson_AddBoolToObject (result, "secret", sub->secret); + xjson_AddBoolToObject (result, "is_qualified", sub->is_qualified); + xjson_AddBoolToObject (result, "is_cardkey", sub->is_cardkey); + xjson_AddBoolToObject (result, "is_de_vs", sub->is_de_vs); + + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sub->pubkey_algo)); + xjson_AddStringToObject0 (result, "pubkey_algo_string", + gpgme_pubkey_algo_string (sub)); + xjson_AddStringToObject0 (result, "keyid", sub->keyid); + xjson_AddStringToObject0 (result, "card_number", sub->card_number); + xjson_AddStringToObject0 (result, "curve", sub->curve); + xjson_AddStringToObject0 (result, "keygrip", sub->keygrip); + + xjson_AddNumberToObject (result, "pubkey_algo", sub->pubkey_algo); + xjson_AddNumberToObject (result, "length", sub->length); + xjson_AddNumberToObject (result, "timestamp", sub->timestamp); + xjson_AddNumberToObject (result, "expires", sub->expires); + + return result; +} + +/* Create a key json object */ +static cjson_t +key_to_json (gpgme_key_t key) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", key->revoked); + xjson_AddBoolToObject (result, "expired", key->expired); + xjson_AddBoolToObject (result, "disabled", key->disabled); + xjson_AddBoolToObject (result, "invalid", key->invalid); + xjson_AddBoolToObject (result, "can_encrypt", key->can_encrypt); + xjson_AddBoolToObject (result, "can_sign", key->can_sign); + xjson_AddBoolToObject (result, "can_certify", key->can_certify); + xjson_AddBoolToObject (result, "can_authenticate", key->can_authenticate); + xjson_AddBoolToObject (result, "secret", key->secret); + xjson_AddBoolToObject (result, "is_qualified", key->is_qualified); + + xjson_AddStringToObject0 (result, "protocol", + protocol_to_string (key->protocol)); + xjson_AddStringToObject0 (result, "issuer_serial", key->issuer_serial); + xjson_AddStringToObject0 (result, "issuer_name", key->issuer_name); + xjson_AddStringToObject0 (result, "fingerprint", key->fpr); + xjson_AddStringToObject0 (result, "chain_id", key->chain_id); + xjson_AddStringToObject0 (result, "owner_trust", + validity_to_string (key->owner_trust)); + + xjson_AddNumberToObject (result, "origin", key->origin); + xjson_AddNumberToObject (result, "last_update", key->last_update); + + /* Add subkeys */ + if (key->subkeys) { - err = gpg_error_from_syserror (); - goto leave; + cjson_t subkey_array = xjson_CreateArray (); + gpgme_subkey_t sub; + for (sub = key->subkeys; sub; sub = sub->next) + cJSON_AddItemToArray (subkey_array, subkey_to_json (sub)); + + xjson_AddItemToObject (result, "subkeys", subkey_array); } - if (!cJSON_AddNumberToObject (result, "expired", sig->exp_timestamp)) + /* User Ids */ + if (key->uids) { - err = gpg_error_from_syserror (); - goto leave; + cjson_t uid_array = xjson_CreateArray (); + gpgme_user_id_t uid; + for (uid = key->uids; uid; uid = uid->next) + cJSON_AddItemToArray (uid_array, uid_to_json (uid)); + + xjson_AddItemToObject (result, "userids", uid_array); } - if (!cJSON_AddStringToObject (result, "validity", - validity_to_string (sig->validity))) + return result; +} + + +/* Create a signature json object */ +static cjson_t +signature_to_json (gpgme_signature_t sig) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddItemToObject (result, "summary", sigsum_to_json (sig->summary)); + + xjson_AddBoolToObject (result, "wrong_key_usage", sig->wrong_key_usage); + xjson_AddBoolToObject (result, "chain_model", sig->chain_model); + xjson_AddBoolToObject (result, "is_de_vs", sig->is_de_vs); + + xjson_AddStringToObject0 (result, "status_string", + gpgme_strerror (sig->status)); + xjson_AddStringToObject0 (result, "fingerprint", sig->fpr); + xjson_AddStringToObject0 (result, "validity_string", + validity_to_string (sig->validity)); + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sig->pubkey_algo)); + xjson_AddStringToObject0 (result, "hash_algo_name", + gpgme_hash_algo_name (sig->hash_algo)); + xjson_AddStringToObject0 (result, "pka_address", sig->pka_address); + + xjson_AddNumberToObject (result, "status_code", sig->status); + xjson_AddNumberToObject (result, "timestamp", sig->timestamp); + xjson_AddNumberToObject (result, "exp_timestamp", sig->exp_timestamp); + xjson_AddNumberToObject (result, "pka_trust", sig->pka_trust); + xjson_AddNumberToObject (result, "validity", sig->validity); + xjson_AddNumberToObject (result, "validity_reason", sig->validity_reason); + + if (sig->notations) { - err = gpg_error_from_syserror (); - goto leave; + gpgme_sig_notation_t not; + cjson_t array = xjson_CreateArray (); + for (not = sig->notations; not; not = not->next) + cJSON_AddItemToArray (array, sig_notation_to_json (not)); + xjson_AddItemToObject (result, "notations", array); } -leave: - return err; + return result; } -/* Add multiple signatures as an array to a result */ -static gpg_error_t -add_signatures_to_object (cjson_t result, gpgme_signature_t signatures) +/* Create a JSON object from a gpgme_verify result */ +static cjson_t +verify_result_to_json (gpgme_verify_result_t verify_result) { - cjson_t response = xjson_CreateArray (); - gpg_error_t err = 0; - gpgme_signature_t sig; + cjson_t result = xjson_CreateObject (); - for (sig = signatures; sig; sig = sig->next) + xjson_AddStringToObject0 (result, "file_name", verify_result->file_name); + xjson_AddBoolToObject (result, "is_mime", verify_result->is_mime); + + if (verify_result->signatures) { - cjson_t sig_obj = xjson_CreateObject (); - err = add_signature_to_object (sig_obj, sig); - if (err) - { - cJSON_Delete (sig_obj); - sig_obj = NULL; - goto leave; - } + cjson_t array = xjson_CreateArray (); + gpgme_signature_t sig; - cJSON_AddItemToArray (response, sig_obj); + for (sig = verify_result->signatures; sig; sig = sig->next) + cJSON_AddItemToArray (array, signature_to_json (sig)); + xjson_AddItemToObject (result, "signatures", array); } - if (!cJSON_AddItemToObject (result, "signatures", response)) + return result; +} + +/* Create a recipient json object */ +static cjson_t +recipient_to_json (gpgme_recipient_t recp) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "keyid", recp->keyid); + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (recp->pubkey_algo)); + xjson_AddStringToObject0 (result, "status_string", + gpgme_strerror (recp->status)); + + xjson_AddNumberToObject (result, "status_code", recp->status); + + return result; +} + + +/* Create a JSON object from a gpgme_decrypt result */ +static cjson_t +decrypt_result_to_json (gpgme_decrypt_result_t decrypt_result) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "file_name", decrypt_result->file_name); + xjson_AddStringToObject0 (result, "symkey_algo", + decrypt_result->symkey_algo); + + xjson_AddBoolToObject (result, "wrong_key_usage", + decrypt_result->wrong_key_usage); + xjson_AddBoolToObject (result, "is_de_vs", + decrypt_result->is_de_vs); + xjson_AddBoolToObject (result, "is_mime", decrypt_result->is_mime); + xjson_AddBoolToObject (result, "legacy_cipher_nomdc", + decrypt_result->legacy_cipher_nomdc); + + if (decrypt_result->recipients) { - err = gpg_error_from_syserror (); - cJSON_Delete (response); - response = NULL; - return err; + cjson_t array = xjson_CreateArray (); + gpgme_recipient_t recp; + + for (recp = decrypt_result->recipients; recp; recp = recp->next) + cJSON_AddItemToArray (array, recipient_to_json (recp)); + xjson_AddItemToObject (result, "recipients", array); } - response = NULL; -leave: - if (err && response) + return result; +} + + +/* Create a JSON object from an engine_info */ +static cjson_t +engine_info_to_json (gpgme_engine_info_t info) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "protocol", + protocol_to_string (info->protocol)); + xjson_AddStringToObject0 (result, "fname", info->file_name); + xjson_AddStringToObject0 (result, "version", info->version); + xjson_AddStringToObject0 (result, "req_version", info->req_version); + xjson_AddStringToObject0 (result, "homedir", info->home_dir ? + info->home_dir : + "default"); + return result; +} + + +/* Create a JSON object from an import_status */ +static cjson_t +import_status_to_json (gpgme_import_status_t sts) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "fingerprint", sts->fpr); + xjson_AddStringToObject0 (result, "error_string", + gpgme_strerror (sts->result)); + + xjson_AddNumberToObject (result, "status", sts->status); + + return result; +} + +/* Create a JSON object from an import result */ +static cjson_t +import_result_to_json (gpgme_import_result_t imp) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddNumberToObject (result, "considered", imp->considered); + xjson_AddNumberToObject (result, "no_user_id", imp->no_user_id); + xjson_AddNumberToObject (result, "imported", imp->imported); + xjson_AddNumberToObject (result, "imported_rsa", imp->imported_rsa); + xjson_AddNumberToObject (result, "unchanged", imp->unchanged); + xjson_AddNumberToObject (result, "new_user_ids", imp->new_user_ids); + xjson_AddNumberToObject (result, "new_sub_keys", imp->new_sub_keys); + xjson_AddNumberToObject (result, "new_signatures", imp->new_signatures); + xjson_AddNumberToObject (result, "new_revocations", imp->new_revocations); + xjson_AddNumberToObject (result, "secret_read", imp->secret_read); + xjson_AddNumberToObject (result, "secret_imported", imp->secret_imported); + xjson_AddNumberToObject (result, "secret_unchanged", imp->secret_unchanged); + xjson_AddNumberToObject (result, "skipped_new_keys", imp->skipped_new_keys); + xjson_AddNumberToObject (result, "not_imported", imp->not_imported); + xjson_AddNumberToObject (result, "skipped_v3_keys", imp->skipped_v3_keys); + + + if (imp->imports) { - cJSON_Delete (response); - response = NULL; + cjson_t array = xjson_CreateArray (); + gpgme_import_status_t status; + + for (status = imp->imports; status; status = status->next) + cJSON_AddItemToArray (array, import_status_to_json (status)); + xjson_AddItemToObject (result, "imports", array); } - return err; + + return result; } -/* Add an array of signature informations under the name "name". */ -static gpg_error_t -add_signatures_object (cjson_t result, const char *name, - gpgme_verify_result_t verify_result) +/* Create a JSON object from a gpgconf arg */ +static cjson_t +conf_arg_to_json (gpgme_conf_arg_t arg, gpgme_conf_type_t type) { - cjson_t response = xjson_CreateObject (); - gpg_error_t err = 0; + cjson_t result = xjson_CreateObject (); + int is_none = 0; + switch (type) + { + case GPGME_CONF_STRING: + case GPGME_CONF_PATHNAME: + case GPGME_CONF_LDAP_SERVER: + case GPGME_CONF_KEY_FPR: + case GPGME_CONF_PUB_KEY: + case GPGME_CONF_SEC_KEY: + case GPGME_CONF_ALIAS_LIST: + xjson_AddStringToObject0 (result, "string", arg->value.string); + break; + + case GPGME_CONF_UINT32: + xjson_AddNumberToObject (result, "number", arg->value.uint32); + break; + + case GPGME_CONF_INT32: + xjson_AddNumberToObject (result, "number", arg->value.int32); + break; + + case GPGME_CONF_NONE: + default: + is_none = 1; + break; + } + xjson_AddBoolToObject (result, "is_none", is_none); + return result; +} - err = add_signatures_to_object (response, verify_result->signatures); - if (err) +/* Create a JSON object from a gpgconf option */ +static cjson_t +conf_opt_to_json (gpgme_conf_opt_t opt) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "name", opt->name); + xjson_AddStringToObject0 (result, "description", opt->description); + xjson_AddStringToObject0 (result, "argname", opt->argname); + xjson_AddStringToObject0 (result, "default_description", + opt->default_description); + xjson_AddStringToObject0 (result, "no_arg_description", + opt->no_arg_description); + + xjson_AddNumberToObject (result, "flags", opt->flags); + xjson_AddNumberToObject (result, "level", opt->level); + xjson_AddNumberToObject (result, "type", opt->type); + xjson_AddNumberToObject (result, "alt_type", opt->alt_type); + + if (opt->default_value) { - goto leave; + cjson_t array = xjson_CreateArray (); + gpgme_conf_arg_t arg; + + for (arg = opt->default_value; arg; arg = arg->next) + cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); + xjson_AddItemToObject (result, "default_value", array); } - if (!cJSON_AddItemToObject (result, name, response)) + if (opt->no_arg_value) { - err = gpg_error_from_syserror (); - goto leave; + cjson_t array = xjson_CreateArray (); + gpgme_conf_arg_t arg; + + for (arg = opt->no_arg_value; arg; arg = arg->next) + cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); + xjson_AddItemToObject (result, "no_arg_value", array); } - leave: - if (err) + + if (opt->value) { - cJSON_Delete (response); - response = NULL; + cjson_t array = xjson_CreateArray (); + gpgme_conf_arg_t arg; + + for (arg = opt->value; arg; arg = arg->next) + cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); + xjson_AddItemToObject (result, "value", array); } - return err; + return result; } - -/* - * Implementation of the commands. - */ +/* Create a JSON object from a gpgconf component*/ +static cjson_t +conf_comp_to_json (gpgme_conf_comp_t cmp) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "name", cmp->name); + xjson_AddStringToObject0 (result, "description", cmp->description); + xjson_AddStringToObject0 (result, "program_name", cmp->program_name); + + + if (cmp->options) + { + cjson_t array = xjson_CreateArray (); + gpgme_conf_opt_t opt; + + for (opt = cmp->options; opt; opt = opt->next) + cJSON_AddItemToArray (array, conf_opt_to_json (opt)); + xjson_AddItemToObject (result, "options", array); + } + + return result; +} -/* Create a "data" object and the "type", "base64" and "more" flags +/* Create a gpgme_data from json string data named "name" + * in the request. Takes the base64 option into account. + * + * Adds an error to the "result" on error. */ +static gpg_error_t +get_string_data (cjson_t request, cjson_t result, const char *name, + gpgme_data_t *r_data) +{ + gpgme_error_t err; + int opt_base64; + cjson_t j_data; + + if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) + return err; + + /* Get the data. Note that INPUT is a shallow data object with the + * storage hold in REQUEST. */ + j_data = cJSON_GetObjectItem (request, name); + if (!j_data) + { + return gpg_error (GPG_ERR_NO_DATA); + } + if (!cjson_is_string (j_data)) + { + return gpg_error (GPG_ERR_INV_VALUE); + } + if (opt_base64) + { + err = data_from_base64_string (r_data, j_data); + if (err) + { + gpg_error_object (result, err, + "Error decoding Base-64 encoded '%s': %s", + name, gpg_strerror (err)); + return err; + } + } + else + { + err = gpgme_data_new_from_mem (r_data, j_data->valuestring, + strlen (j_data->valuestring), 0); + if (err) + { + gpg_error_object (result, err, "Error getting '%s': %s", + name, gpg_strerror (err)); + return err; + } + } + return 0; +} + + +/* Create a "data" object and the "type" and "base64" flags * from DATA and append them to RESULT. Ownership of DATA is * transferred to this function. TYPE must be a fixed string. - * CHUNKSIZE is the chunksize requested from the caller. If BASE64 is - * -1 the need for base64 encoding is determined by the content of - * DATA, all other values are taken as true or false. Note that - * op_getmore has similar code but works on PENDING_DATA which is set - * here. */ + * If BASE64 is -1 the need for base64 encoding is determined + * by the content of DATA, all other values are taken as true + * or false. */ static gpg_error_t -make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize, +make_data_object (cjson_t result, gpgme_data_t data, const char *type, int base64) { gpg_error_t err; char *buffer; const char *s; size_t buflen, n; - int c; if (!base64 || base64 == -1) /* Make sure that we really have a string. */ gpgme_data_write (data, "", 1); @@ -835,49 +1484,118 @@ make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize, } } - /* Adjust the chunksize if we need to do base64 conversion. */ - if (base64) - chunksize = (chunksize / 4) * 3; - xjson_AddStringToObject (result, "type", type); xjson_AddBoolToObject (result, "base64", base64); - if (buflen > chunksize) + if (base64) + err = add_base64_to_object (result, "data", buffer, buflen); + else + err = cjson_AddStringToObject (result, "data", buffer); + + leave: + gpgme_free (buffer); + return err; +} + + +/* Encode and chunk response. + * + * If neccessary this base64 encodes and chunks the repsonse + * for getmore so that we always return valid json independent + * of the chunksize. + * + * A chunked repsonse contains the base64 encoded chunk + * as a string and a boolean if there is still more data + * available for getmore like: + * { + * chunk: "SGVsbG8gV29ybGQK" + * more: true + * } + * + * Chunking is only done if the response is larger then the + * chunksize. + * + * caller has to xfree the return value. + */ +static char * +encode_and_chunk (cjson_t request, cjson_t response) +{ + char *data; + gpg_error_t err = 0; + size_t chunksize; + char *getmore_request = NULL; + + if (opt_interactive) + data = cJSON_Print (response); + else + data = cJSON_PrintUnformatted (response); + + if (!data) { - xjson_AddBoolToObject (result, "more", 1); + err = GPG_ERR_NO_DATA; + goto leave; + } - c = buffer[chunksize]; - buffer[chunksize] = 0; - if (base64) - err = add_base64_to_object (result, "data", buffer, chunksize); - else - err = cjson_AddStringToObject (result, "data", buffer); - buffer[chunksize] = c; - if (err) - goto leave; + if (!request) + { + err = GPG_ERR_INV_VALUE; + goto leave; + } - pending_data.buffer = buffer; - buffer = NULL; - pending_data.length = buflen; - pending_data.written = chunksize; - pending_data.type = type; - pending_data.base64 = base64; + if ((err = get_chunksize (request, &chunksize))) + { + err = GPG_ERR_INV_VALUE; + goto leave; } - else + + if (!chunksize) + goto leave; + + pending_data.buffer = data; + /* Data should already be encoded so that it does not + contain 0.*/ + pending_data.length = strlen (data); + pending_data.written = 0; + + if (gpgrt_asprintf (&getmore_request, + "{ \"op\":\"getmore\", \"chunksize\": %i }", + (int) chunksize) == -1) { - if (base64) - err = add_base64_to_object (result, "data", buffer, buflen); - else - err = cjson_AddStringToObject (result, "data", buffer); + err = gpg_error_from_syserror (); + goto leave; } - leave: - gpgme_free (buffer); - return err; + data = process_request (getmore_request); + +leave: + xfree (getmore_request); + + if (!err && !data) + { + err = GPG_ERR_GENERAL; + } + + if (err) + { + cjson_t err_obj = gpg_error_object (NULL, err, + "Encode and chunk failed: %s", + gpgme_strerror (err)); + xfree (data); + if (opt_interactive) + data = cJSON_Print (err_obj); + data = cJSON_PrintUnformatted (err_obj); + + cJSON_Delete (err_obj); + } + + return data; } +/* + * Implementation of the commands. + */ static const char hlp_encrypt[] = "op: \"encrypt\"\n" "keys: Array of strings with the fingerprints or user-ids\n" @@ -887,7 +1605,8 @@ static const char hlp_encrypt[] = "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" + "signing_keys: Similar to the keys parameter for added signing.\n" + " (openpgp only)" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" @@ -905,32 +1624,27 @@ static const char hlp_encrypt[] = "data: Unless armor mode is used a Base64 encoded binary\n" " ciphertext. In armor mode a string with an armored\n" " OpenPGP or a PEM message.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "base64: Boolean indicating whether data is base64 encoded."; static gpg_error_t op_encrypt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; - int opt_base64; + char **signing_patterns = NULL; int opt_mime; char *keystring = NULL; - cjson_t j_input; gpgme_data_t input = NULL; gpgme_data_t output = NULL; int abool; gpgme_encrypt_flags_t encrypt_flags = 0; + gpgme_ctx_t keylist_ctx = NULL; + gpgme_key_t key = NULL; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; - if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) - goto leave; if ((err = get_boolean_flag (request, "mime", 0, &opt_mime))) goto leave; @@ -964,47 +1678,49 @@ op_encrypt (cjson_t request, cjson_t result) /* Get the keys. */ - err = get_keys (request, &keystring); + err = get_keys (request, "keys", &keystring); if (err) { /* Provide a custom error response. */ - error_object (result, "Error getting keys: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Error getting keys: %s", + gpg_strerror (err)); goto leave; } - /* Get the data. Note that INPUT is a shallow data object with the - * storage hold in REQUEST. */ - j_input = cJSON_GetObjectItem (request, "data"); - if (!j_input) - { - err = gpg_error (GPG_ERR_NO_DATA); - goto leave; - } - if (!cjson_is_string (j_input)) - { - err = gpg_error (GPG_ERR_INV_VALUE); - goto leave; - } - if (opt_base64) + /* Do we have signing keys ? */ + signing_patterns = create_keylist_patterns (request, "signing_keys"); + if (signing_patterns) { - err = data_from_base64_string (&input, j_input); + keylist_ctx = create_onetime_context (protocol); + gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL); + + err = gpgme_op_keylist_ext_start (keylist_ctx, + (const char **) signing_patterns, + 1, 0); if (err) { - error_object (result, "Error decoding Base-64 encoded 'data': %s", - gpg_strerror (err)); + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); goto leave; } - } - else - { - err = gpgme_data_new_from_mem (&input, j_input->valuestring, - strlen (j_input->valuestring), 0); - if (err) + while (!(err = gpgme_op_keylist_next (keylist_ctx, &key))) { - error_object (result, "Error getting 'data': %s", gpg_strerror (err)); - goto leave; + if ((err = gpgme_signers_add (ctx, key))) + { + gpg_error_object (result, err, "Error adding signer: %s", + gpg_strerror (err)); + goto leave; + } + gpgme_key_unref (key); + key = NULL; } + release_onetime_context (keylist_ctx); + keylist_ctx = NULL; } + + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + if (opt_mime) gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME); @@ -1013,30 +1729,44 @@ op_encrypt (cjson_t request, cjson_t result) err = gpgme_data_new (&output); if (err) { - error_object (result, "Error creating output data object: %s", - gpg_strerror (err)); + gpg_error_object (result, err, "Error creating output data object: %s", + gpg_strerror (err)); goto leave; } /* Encrypt. */ - err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags, - input, output); + if (!signing_patterns) + { + err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags, + input, output); + } + else + { + err = gpgme_op_encrypt_sign_ext (ctx, NULL, keystring, encrypt_flags, + input, output); + + } /* encrypt_result = gpgme_op_encrypt_result (ctx); */ if (err) { - error_object (result, "Encryption failed: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Encryption failed: %s", + gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; /* We need to base64 if armoring has not been requested. */ - err = make_data_object (result, output, chunksize, + err = make_data_object (result, output, "ciphertext", !gpgme_get_armor (ctx)); output = NULL; leave: + xfree_array (signing_patterns); xfree (keystring); + release_onetime_context (keylist_ctx); + gpgme_key_unref (key); + gpgme_signers_clear (ctx); release_context (ctx); gpgme_data_release (input); gpgme_data_release (output); @@ -1051,27 +1781,88 @@ static const char hlp_decrypt[] = "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" "\n" "Response on success:\n" - "type: \"plaintext\"\n" - "data: The decrypted data. This may be base64 encoded.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "mime: A Boolean indicating whether the data is a MIME object.\n" - "info: An optional object with extra information.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "type: \"plaintext\"\n" + "data: The decrypted data. This may be base64 encoded.\n" + "base64: Boolean indicating whether data is base64 encoded.\n" + "mime: deprecated - use dec_info is_mime instead\n" + "dec_info: An object with decryption information. (gpgme_decrypt_result_t)\n" + " Boolean values:\n" + " wrong_key_usage: Key should not have been used for encryption.\n" + " is_de_vs: Message was encrypted in compliance to the de-vs\n" + " mode.\n" + " is_mime: Message claims that the content is a MIME Message.\n" + " legacy_cipher_nomdc: The message was made by a legacy algorithm\n" + " without integrity protection.\n" + " String values:\n" + " file_name: The filename contained in the decrypt result.\n" + " symkey_algo: A string with the symmetric encryption algorithm and\n" + " mode using the format \"<algo>.<mode>\".\n" + " Array values:\n" + " recipients: The list of recipients (gpgme_recipient_t).\n" + " String values:\n" + " keyid: The keyid of the recipient.\n" + " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" + " status_string: The status code as localized gpg-error string\n" + " Number values:\n" + " status_code: The status as a number. (gpg_error_t)\n" + "info: Optional an object with verification information.\n" + " (gpgme_verify_result_t)\n" + " file_name: The filename contained in the verify result.\n" + " is_mime: The is_mime info contained in the verify result.\n" + " signatures: Array of signatures\n" + " summary: Object containing summary information.\n" + " Boolean values: (Check gpgme_sigsum_t doc for meaning)\n" + " valid\n" + " green\n" + " red\n" + " revoked\n" + " key-expired\n" + " sig-expired\n" + " key-missing\n" + " crl-missing\n" + " crl-too-old\n" + " bad-policy\n" + " sys-error\n" + " sigsum: Array of strings representing the sigsum.\n" + " Boolean values:\n" + " wrong_key_usage: Key should not have been used for signing.\n" + " chain_model: Validity has been verified using the chain model.\n" + " is_de_vs: signature is in compliance to the de-vs mode.\n" + " String values:\n" + " status_string: The status code as localized gpg-error string\n" + " fingerprint: The fingerprint of the signing key.\n" + " validity_string: The validity as string.\n" + " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" + " hash_algo_name: gpgme_hash_algo_name of used hash algo\n" + " pka_address: The mailbox from the PKA information.\n" + " Number values:\n" + " status_code: The status as a number. (gpg_error_t)\n" + " timestamp: Signature creation time. (secs since epoch)\n" + " exp_timestamp: Signature expiration or 0. (secs since epoch)\n" + " pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n" + " validity: validity as number (gpgme_validity_t)\n" + " validity_reason: (gpg_error_t)\n" + " Array values:\n" + " notations: Notation data and policy urls (gpgme_sig_notation_t)\n" + " Boolean values:\n" + " human_readable\n" + " critical\n" + " String values:\n" + " name\n" + " value\n" + " Number values:\n" + " flags\n"; static gpg_error_t op_decrypt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; - int opt_base64; - cjson_t j_input; gpgme_data_t input = NULL; gpgme_data_t output = NULL; gpgme_decrypt_result_t decrypt_result; @@ -1080,52 +1871,17 @@ op_decrypt (cjson_t request, cjson_t result) if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; - - if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) - goto leave; - /* Get the data. Note that INPUT is a shallow data object with the - * storage hold in REQUEST. */ - j_input = cJSON_GetObjectItem (request, "data"); - if (!j_input) - { - err = gpg_error (GPG_ERR_NO_DATA); - goto leave; - } - if (!cjson_is_string (j_input)) - { - err = gpg_error (GPG_ERR_INV_VALUE); + if ((err = get_string_data (request, result, "data", &input))) goto leave; - } - if (opt_base64) - { - err = data_from_base64_string (&input, j_input); - if (err) - { - error_object (result, "Error decoding Base-64 encoded 'data': %s", - gpg_strerror (err)); - goto leave; - } - } - else - { - err = gpgme_data_new_from_mem (&input, j_input->valuestring, - strlen (j_input->valuestring), 0); - if (err) - { - error_object (result, "Error getting 'data': %s", gpg_strerror (err)); - goto leave; - } - } /* Create an output data object. */ err = gpgme_data_new (&output); if (err) { - error_object (result, "Error creating output data object: %s", - gpg_strerror (err)); + gpg_error_object (result, err, + "Error creating output data object: %s", + gpg_strerror (err)); goto leave; } @@ -1135,7 +1891,8 @@ op_decrypt (cjson_t request, cjson_t result) decrypt_result = gpgme_op_decrypt_result (ctx); if (err) { - error_object (result, "Decryption failed: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Decryption failed: %s", + gpg_strerror (err)); goto leave; } gpgme_data_release (input); @@ -1144,24 +1901,23 @@ op_decrypt (cjson_t request, cjson_t result) if (decrypt_result->is_mime) xjson_AddBoolToObject (result, "mime", 1); + xjson_AddItemToObject (result, "dec_info", + decrypt_result_to_json (decrypt_result)); + verify_result = gpgme_op_verify_result (ctx); if (verify_result && verify_result->signatures) { - err = add_signatures_object (result, "info", verify_result); - } - - if (err) - { - error_object (result, "Info output failed: %s", gpg_strerror (err)); - goto leave; + xjson_AddItemToObject (result, "info", + verify_result_to_json (verify_result)); } - err = make_data_object (result, output, chunksize, "plaintext", -1); + err = make_data_object (result, output, "plaintext", -1); output = NULL; if (err) { - error_object (result, "Plaintext output failed: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Plaintext output failed: %s", + gpg_strerror (err)); goto leave; } @@ -1182,7 +1938,6 @@ static const char hlp_sign[] = "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "sender: The mail address of the sender.\n" "mode: A string with the signing mode can be:\n" " detached (default)\n" @@ -1198,18 +1953,14 @@ static const char hlp_sign[] = "data: Unless armor mode is used a Base64 encoded binary\n" " signature. In armor mode a string with an armored\n" " OpenPGP or a PEM message.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "base64: Boolean indicating whether data is base64 encoded.\n"; static gpg_error_t op_sign (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; - int opt_base64; - char *keystring = NULL; - cjson_t j_input; + char **patterns = NULL; gpgme_data_t input = NULL; gpgme_data_t output = NULL; int abool; @@ -1221,11 +1972,6 @@ op_sign (cjson_t request, cjson_t result) if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; - - if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) - goto leave; if ((err = get_boolean_flag (request, "armor", 0, &abool))) goto leave; @@ -1250,117 +1996,1112 @@ op_sign (cjson_t request, cjson_t result) gpgme_set_sender (ctx, j_tmp->valuestring); } - /* Get the keys. */ - err = get_keys (request, &keystring); - if (err) + patterns = create_keylist_patterns (request, "keys"); + if (!patterns) { - /* Provide a custom error response. */ - error_object (result, "Error getting keys: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Error getting keys: %s", + gpg_strerror (gpg_error (GPG_ERR_NO_KEY))); goto leave; } /* Do a keylisting and add the keys */ - if ((err = gpgme_new (&keylist_ctx))) - goto leave; - gpgme_set_protocol (keylist_ctx, protocol); + keylist_ctx = create_onetime_context (protocol); gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL); - err = gpgme_op_keylist_start (ctx, keystring, 1); + err = gpgme_op_keylist_ext_start (keylist_ctx, + (const char **) patterns, 1, 0); if (err) { - error_object (result, "Error listing keys: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); goto leave; } - while (!(err = gpgme_op_keylist_next (ctx, &key))) + while (!(err = gpgme_op_keylist_next (keylist_ctx, &key))) { if ((err = gpgme_signers_add (ctx, key))) { - error_object (result, "Error adding signer: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Error adding signer: %s", + gpg_strerror (err)); goto leave; } gpgme_key_unref (key); + key = NULL; } - /* Get the data. Note that INPUT is a shallow data object with the - * storage hold in REQUEST. */ - j_input = cJSON_GetObjectItem (request, "data"); - if (!j_input) + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + + /* Create an output data object. */ + err = gpgme_data_new (&output); + if (err) { - err = gpg_error (GPG_ERR_NO_DATA); + gpg_error_object (result, err, "Error creating output data object: %s", + gpg_strerror (err)); goto leave; } - if (!cjson_is_string (j_input)) + + /* Sign. */ + err = gpgme_op_sign (ctx, input, output, mode); + if (err) { - err = gpg_error (GPG_ERR_INV_VALUE); + gpg_error_object (result, err, "Signing failed: %s", + gpg_strerror (err)); goto leave; } - if (opt_base64) + + gpgme_data_release (input); + input = NULL; + + /* We need to base64 if armoring has not been requested. */ + err = make_data_object (result, output, + "signature", !gpgme_get_armor (ctx)); + output = NULL; + + leave: + xfree_array (patterns); + gpgme_signers_clear (ctx); + gpgme_key_unref (key); + release_onetime_context (keylist_ctx); + release_context (ctx); + gpgme_data_release (input); + gpgme_data_release (output); + return err; +} + + + +static const char hlp_verify[] = + "op: \"verify\"\n" + "data: The data to verify.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "signature: A detached signature. If missing opaque is assumed.\n" + "\n" + "Optional boolean flags (default is false):\n" + "base64: Input data is base64 encoded.\n" + "\n" + "Response on success:\n" + "type: \"plaintext\"\n" + "data: The verified data. This may be base64 encoded.\n" + "base64: Boolean indicating whether data is base64 encoded.\n" + "info: An object with verification information (gpgme_verify_result_t).\n" + " file_name: Optional string of the plaintext file name.\n" + " is_mime: Boolean that is true if the messages claims it is MIME.\n" + " signatures: Array of signatures\n" + " summary: Object containing summary information.\n" + " Boolean values: (Check gpgme_sigsum_t doc for meaning)\n" + " valid\n" + " green\n" + " red\n" + " revoked\n" + " key-expired\n" + " sig-expired\n" + " key-missing\n" + " crl-missing\n" + " crl-too-old\n" + " bad-policy\n" + " sys-error\n" + " sigsum: Array of strings representing the sigsum.\n" + " Boolean values:\n" + " wrong_key_usage: Key should not have been used for signing.\n" + " chain_model: Validity has been verified using the chain model.\n" + " is_de_vs: signature is in compliance to the de-vs mode.\n" + " String values:\n" + " status_string: The status code as localized gpg-error string\n" + " fingerprint: The fingerprint of the signing key.\n" + " validity_string: The validity as string.\n" + " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" + " hash_algo_name: gpgme_hash_algo_name of used hash algo\n" + " pka_address: The mailbox from the PKA information.\n" + " Number values:\n" + " status_code: The status as a number. (gpg_error_t)\n" + " timestamp: Signature creation time. (secs since epoch)\n" + " exp_timestamp: Signature expiration or 0. (secs since epoch)\n" + " pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n" + " validity: validity as number (gpgme_validity_t)\n" + " validity_reason: (gpg_error_t)\n" + " Array values:\n" + " notations: Notation data and policy urls (gpgme_sig_notation_t)\n" + " Boolean values:\n" + " human_readable\n" + " critical\n" + " String values:\n" + " name\n" + " value\n" + " Number values:\n" + " flags\n"; +static gpg_error_t +op_verify (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + gpgme_data_t input = NULL; + gpgme_data_t signature = NULL; + gpgme_data_t output = NULL; + gpgme_verify_result_t verify_result; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + + err = get_string_data (request, result, "signature", &signature); + /* Signature data is optional otherwise we expect opaque or clearsigned. */ + if (err && err != gpg_error (GPG_ERR_NO_DATA)) + goto leave; + + /* Create an output data object. */ + err = gpgme_data_new (&output); + if (err) { - err = data_from_base64_string (&input, j_input); - if (err) - { - error_object (result, "Error decoding Base-64 encoded 'data': %s", + gpg_error_object (result, err, "Error creating output data object: %s", gpg_strerror (err)); - goto leave; - } + goto leave; + } + + /* Verify. */ + if (signature) + { + err = gpgme_op_verify (ctx, signature, input, output); } else { - err = gpgme_data_new_from_mem (&input, j_input->valuestring, - strlen (j_input->valuestring), 0); - if (err) - { - error_object (result, "Error getting 'data': %s", gpg_strerror (err)); - goto leave; - } + err = gpgme_op_verify (ctx, input, 0, output); + } + if (err) + { + gpg_error_object (result, err, "Verify failed: %s", gpg_strerror (err)); + goto leave; } + gpgme_data_release (input); + input = NULL; + gpgme_data_release (signature); + signature = NULL; + + verify_result = gpgme_op_verify_result (ctx); + if (verify_result && verify_result->signatures) + { + xjson_AddItemToObject (result, "info", + verify_result_to_json (verify_result)); + } + + err = make_data_object (result, output, "plaintext", -1); + output = NULL; - /* Create an output data object. */ - err = gpgme_data_new (&output); if (err) { - error_object (result, "Error creating output data object: %s", - gpg_strerror (err)); + gpg_error_object (result, err, "Plaintext output failed: %s", + gpg_strerror (err)); goto leave; } - /* Sign. */ - err = gpgme_op_sign (ctx, input, output, mode); + leave: + release_context (ctx); + gpgme_data_release (input); + gpgme_data_release (output); + gpgme_data_release (signature); + return err; +} + + + +static const char hlp_version[] = + "op: \"version\"\n" + "\n" + "Response on success:\n" + "gpgme: The GPGME Version.\n" + "info: dump of engine info. containing:\n" + " protocol: The protocol.\n" + " fname: The file name.\n" + " version: The version.\n" + " req_ver: The required version.\n" + " homedir: The homedir of the engine or \"default\".\n"; +static gpg_error_t +op_version (cjson_t request, cjson_t result) +{ + gpg_error_t err = 0; + gpgme_engine_info_t ei = NULL; + cjson_t infos = xjson_CreateArray (); + + (void)request; + + if (!cJSON_AddStringToObject (result, "gpgme", gpgme_check_version (NULL))) + { + cJSON_Delete (infos); + return gpg_error_from_syserror (); + } + + if ((err = gpgme_get_engine_info (&ei))) + { + cJSON_Delete (infos); + return err; + } + + for (; ei; ei = ei->next) + cJSON_AddItemToArray (infos, engine_info_to_json (ei)); + + if (!cJSON_AddItemToObject (result, "info", infos)) + { + err = gpg_error_from_syserror (); + cJSON_Delete (infos); + return err; + } + + return 0; +} + + + +static const char hlp_keylist[] = + "op: \"keylist\"\n" + "\n" + "Optional parameters:\n" + "keys: Array of strings or fingerprints to lookup\n" + " For a single key a String may be used instead of an array.\n" + " default lists all keys.\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "secret: List only secret keys.\n" + "with-secret: Add KEYLIST_MODE_WITH_SECRET.\n" + "extern: Add KEYLIST_MODE_EXTERN.\n" + "local: Add KEYLIST_MODE_LOCAL. (default mode).\n" + "sigs: Add KEYLIST_MODE_SIGS.\n" + "notations: Add KEYLIST_MODE_SIG_NOTATIONS.\n" + "tofu: Add KEYLIST_MODE_WITH_TOFU.\n" + "ephemeral: Add KEYLIST_MODE_EPHEMERAL.\n" + "validate: Add KEYLIST_MODE_VALIDATE.\n" + "locate: Add KEYLIST_MODE_LOCATE.\n" + "\n" + "Response on success:\n" + "keys: Array of keys.\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " disabled\n" + " invalid\n" + " can_encrypt\n" + " can_sign\n" + " can_certify\n" + " can_authenticate\n" + " secret\n" + " is_qualified\n" + " String values:\n" + " protocol\n" + " issuer_serial (CMS Only)\n" + " issuer_name (CMS Only)\n" + " chain_id (CMS Only)\n" + " owner_trust (OpenPGP only)\n" + " fingerprint\n" + " Number values:\n" + " last_update\n" + " origin\n" + " Array values:\n" + " subkeys\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " disabled\n" + " invalid\n" + " can_encrypt\n" + " can_sign\n" + " can_certify\n" + " can_authenticate\n" + " secret\n" + " is_qualified\n" + " is_cardkey\n" + " is_de_vs\n" + " String values:\n" + " pubkey_algo_name\n" + " pubkey_algo_string\n" + " keyid\n" + " card_number\n" + " curve\n" + " keygrip\n" + " Number values:\n" + " pubkey_algo\n" + " length\n" + " timestamp\n" + " expires\n" + " userids\n" + " Boolean values:\n" + " revoked\n" + " invalid\n" + " String values:\n" + " validity\n" + " uid\n" + " name\n" + " email\n" + " comment\n" + " address\n" + " Number values:\n" + " origin\n" + " last_update\n" + " Array values:\n" + " signatures\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " invalid\n" + " exportable\n" + " String values:\n" + " pubkey_algo_name\n" + " keyid\n" + " status\n" + " uid\n" + " name\n" + " email\n" + " comment\n" + " Number values:\n" + " pubkey_algo\n" + " timestamp\n" + " expires\n" + " status_code\n" + " sig_class\n" + " Array values:\n" + " notations\n" + " Boolean values:\n" + " human_readable\n" + " critical\n" + " String values:\n" + " name\n" + " value\n" + " Number values:\n" + " flags\n" + " tofu\n" + " String values:\n" + " description\n" + " Number values:\n" + " validity\n" + " policy\n" + " signcount\n" + " encrcount\n" + " signfirst\n" + " signlast\n" + " encrfirst\n" + " encrlast\n"; +static gpg_error_t +op_keylist (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + char **patterns = NULL; + int abool; + int secret_only = 0; + gpgme_keylist_mode_t mode = 0; + gpgme_key_t key = NULL; + cjson_t keyarray = xjson_CreateArray (); + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + /* Handle the various keylist mode bools. */ + if ((err = get_boolean_flag (request, "secret", 0, &abool))) + goto leave; + if (abool) + { + mode |= GPGME_KEYLIST_MODE_WITH_SECRET; + secret_only = 1; + } + if ((err = get_boolean_flag (request, "with-secret", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_WITH_SECRET; + if ((err = get_boolean_flag (request, "extern", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_EXTERN; + + if ((err = get_boolean_flag (request, "local", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_LOCAL; + + if ((err = get_boolean_flag (request, "sigs", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_SIGS; + + if ((err = get_boolean_flag (request, "notations", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_SIG_NOTATIONS; + + if ((err = get_boolean_flag (request, "tofu", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_WITH_TOFU; + + if ((err = get_boolean_flag (request, "ephemeral", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_EPHEMERAL; + + if ((err = get_boolean_flag (request, "validate", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_VALIDATE; + + if ((err = get_boolean_flag (request, "locate", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_LOCATE; + + if (!mode) + { + /* default to local */ + mode = GPGME_KEYLIST_MODE_LOCAL; + } + + /* Get the keys. */ + patterns = create_keylist_patterns (request, "keys"); + + /* Do a keylisting and add the keys */ + gpgme_set_keylist_mode (ctx, mode); + + err = gpgme_op_keylist_ext_start (ctx, (const char **) patterns, + secret_only, 0); if (err) { - error_object (result, "Signing failed: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); + goto leave; + } + + while (!(err = gpgme_op_keylist_next (ctx, &key))) + { + cJSON_AddItemToArray (keyarray, key_to_json (key)); + gpgme_key_unref (key); + } + err = 0; + + if (!cJSON_AddItemToObject (result, "keys", keyarray)) + { + err = gpg_error_from_syserror (); goto leave; } + leave: + xfree_array (patterns); + if (err) + { + cJSON_Delete (keyarray); + } + return err; +} + + + +static const char hlp_import[] = + "op: \"import\"\n" + "data: The data to import.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "base64: Input data is base64 encoded.\n" + "\n" + "Response on success:\n" + "result: The import result.\n" + " Number values:\n" + " considered\n" + " no_user_id\n" + " imported\n" + " imported_rsa\n" + " unchanged\n" + " new_user_ids\n" + " new_sub_keys\n" + " new_signatures\n" + " new_revocations\n" + " secret_read\n" + " secret_imported\n" + " secret_unchanged\n" + " skipped_new_keys\n" + " not_imported\n" + " skipped_v3_keys\n" + " Array values:\n" + " imports: List of keys for which an import was attempted\n" + " String values:\n" + " fingerprint\n" + " error_string\n" + " Number values:\n" + " error_code\n" + " status\n"; +static gpg_error_t +op_import (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_data_t input = NULL; + gpgme_import_result_t import_result; + gpgme_protocol_t protocol; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + if ((err = get_string_data (request, result, "data", &input))) + goto leave; + + /* Import. */ + err = gpgme_op_import (ctx, input); + import_result = gpgme_op_import_result (ctx); + if (err) + { + gpg_error_object (result, err, "Import failed: %s", + gpg_strerror (err)); + goto leave; + } gpgme_data_release (input); input = NULL; - /* We need to base64 if armoring has not been requested. */ - err = make_data_object (result, output, chunksize, - "ciphertext", !gpgme_get_armor (ctx)); - output = NULL; + xjson_AddItemToObject (result, "result", + import_result_to_json (import_result)); leave: - xfree (keystring); release_context (ctx); - release_context (keylist_ctx); gpgme_data_release (input); + return err; +} + + +static const char hlp_export[] = + "op: \"export\"\n" + "\n" + "Optional parameters:\n" + "keys: Array of strings or fingerprints to lookup\n" + " For a single key a String may be used instead of an array.\n" + " default exports all keys.\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "armor: Request output in armored format.\n" + "extern: Add EXPORT_MODE_EXTERN.\n" + "minimal: Add EXPORT_MODE_MINIMAL.\n" + "raw: Add EXPORT_MODE_RAW.\n" + "pkcs12: Add EXPORT_MODE_PKCS12.\n" + "with-sec-fprs: Add the sec-fprs array to the result.\n" + "\n" + "Response on success:\n" + "type: \"keys\"\n" + "data: Unless armor mode is used a Base64 encoded binary.\n" + " In armor mode a string with an armored\n" + " OpenPGP or a PEM / PKCS12 key.\n" + "base64: Boolean indicating whether data is base64 encoded.\n" + "sec-fprs: Optional, only if with-secret is set. An array containing\n" + " the fingerprints of the keys in the export for which a secret\n" + " key is available"; +static gpg_error_t +op_export (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + char **patterns = NULL; + int abool; + int with_secret = 0; + gpgme_export_mode_t mode = 0; + gpgme_data_t output = NULL; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + + if ((err = get_boolean_flag (request, "armor", 0, &abool))) + goto leave; + gpgme_set_armor (ctx, abool); + + /* Handle the various export mode bools. */ + if ((err = get_boolean_flag (request, "secret", 0, &abool))) + goto leave; + if (abool) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + if ((err = get_boolean_flag (request, "extern", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_EXTERN; + + if ((err = get_boolean_flag (request, "minimal", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_MINIMAL; + + if ((err = get_boolean_flag (request, "raw", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_RAW; + + if ((err = get_boolean_flag (request, "pkcs12", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_EXPORT_MODE_PKCS12; + + if ((err = get_boolean_flag (request, "with-sec-fprs", 0, &abool))) + goto leave; + if (abool) + with_secret = 1; + + /* Get the export patterns. */ + patterns = create_keylist_patterns (request, "keys"); + + /* Create an output data object. */ + err = gpgme_data_new (&output); + if (err) + { + gpg_error_object (result, err, "Error creating output data object: %s", + gpg_strerror (err)); + goto leave; + } + + err = gpgme_op_export_ext (ctx, (const char **) patterns, + mode, output); + if (err) + { + gpg_error_object (result, err, "Error exporting keys: %s", + gpg_strerror (err)); + goto leave; + } + + /* We need to base64 if armoring has not been requested. */ + err = make_data_object (result, output, + "keys", !gpgme_get_armor (ctx)); + output = NULL; + + if (!err && with_secret) + { + err = add_secret_fprs ((const char **) patterns, protocol, result); + } + +leave: + xfree_array (patterns); + release_context (ctx); gpgme_data_release (output); + return err; } + +static const char hlp_delete[] = + "op: \"delete\"\n" + "key: Fingerprint of the key to delete.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "\n" + "Response on success:\n" + "success: Boolean true.\n"; +static gpg_error_t +op_delete (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_ctx_t keylist_ctx = NULL; + gpgme_protocol_t protocol; + gpgme_key_t key = NULL; + int secret = 0; + cjson_t j_key = NULL; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + keylist_ctx = get_context (protocol); + + if ((err = get_boolean_flag (request, "secret", 0, &secret))) + goto leave; + if (secret) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + j_key = cJSON_GetObjectItem (request, "key"); + if (!j_key) + { + err = gpg_error (GPG_ERR_NO_KEY); + goto leave; + } + if (!cjson_is_string (j_key)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + /* Get the key */ + if ((err = gpgme_get_key (keylist_ctx, j_key->valuestring, &key, 0))) + { + gpg_error_object (result, err, "Error fetching key for delete: %s", + gpg_strerror (err)); + goto leave; + } + + err = gpgme_op_delete (ctx, key, 0); + if (err) + { + gpg_error_object (result, err, "Error deleting key: %s", + gpg_strerror (err)); + goto leave; + } + + xjson_AddBoolToObject (result, "success", 1); + +leave: + gpgme_key_unref (key); + release_context (ctx); + release_context (keylist_ctx); + + return err; +} + + +static const char hlp_config_opt[] = + "op: \"config_opt\"\n" + "component: The component of the option.\n" + "option: The name of the option.\n" + "\n" + "Response on success:\n" + "\n" + "option: Information about the option.\n" + " String values:\n" + " name: The name of the option\n" + " description: Localized description of the opt.\n" + " argname: Thhe argument name e.g. --verbose\n" + " default_description\n" + " no_arg_description\n" + " Number values:\n" + " flags: Flags for this option.\n" + " level: the level of the description. See gpgme_conf_level_t.\n" + " type: The type of the option. See gpgme_conf_type_t.\n" + " alt_type: Alternate type of the option. See gpgme_conf_type_t\n" + " Arg type values: (see desc. below)\n" + " default_value: Array of the default value.\n" + " no_arg_value: Array of the value if it is not set.\n" + " value: Array for the current value if the option is set.\n" + "\n" + "If the response is empty the option was not found\n" + ""; +static gpg_error_t +op_config_opt (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_conf_comp_t conf = NULL; + gpgme_conf_comp_t comp = NULL; + cjson_t j_tmp; + char *comp_name = NULL; + char *opt_name = NULL; + + ctx = get_context (GPGME_PROTOCOL_GPGCONF); + + j_tmp = cJSON_GetObjectItem (request, "component"); + if (!j_tmp || !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + comp_name = j_tmp->valuestring; + + + j_tmp = cJSON_GetObjectItem (request, "option"); + if (!j_tmp || !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + opt_name = j_tmp->valuestring; + + /* Load the config */ + err = gpgme_op_conf_load (ctx, &conf); + if (err) + { + goto leave; + } + + comp = conf; + for (comp = conf; comp; comp = comp->next) + { + gpgme_conf_opt_t opt = NULL; + int found = 0; + if (!comp->name || strcmp (comp->name, comp_name)) + { + /* Skip components if a single one is specified */ + continue; + } + for (opt = comp->options; opt; opt = opt->next) + { + if (!opt->name || strcmp (opt->name, opt_name)) + { + /* Skip components if a single one is specified */ + continue; + } + xjson_AddItemToObject (result, "option", conf_opt_to_json (opt)); + found = 1; + break; + } + if (found) + break; + } + +leave: + gpgme_conf_release (conf); + release_context (ctx); + + return err; +} + + +static const char hlp_config[] = + "op: \"config\"\n" + "\n" + "Optional parameters:\n" + "component: Component of entries to list.\n" + " Default: all\n" + "\n" + "Response on success:\n" + " components: Array of the component program configs.\n" + " name: The component name.\n" + " description: Description of the component.\n" + " program_name: The absolute path to the program.\n" + " options: Array of config options\n" + " String values:\n" + " name: The name of the option\n" + " description: Localized description of the opt.\n" + " argname: Thhe argument name e.g. --verbose\n" + " default_description\n" + " no_arg_description\n" + " Number values:\n" + " flags: Flags for this option.\n" + " level: the level of the description. See gpgme_conf_level_t.\n" + " type: The type of the option. See gpgme_conf_type_t.\n" + " alt_type: Alternate type of the option. See gpgme_conf_type_t\n" + " Arg type values: (see desc. below)\n" + " default_value: Array of the default value.\n" + " no_arg_value: Array of the value if it is not set.\n" + " value: Array for the current value if the option is set.\n" + "\n" + "Conf type values are an array of values that are either\n" + "of type number named \"number\" or of type string,\n" + "named \"string\".\n" + "If the type is none the bool value is_none is true.\n" + ""; +static gpg_error_t +op_config (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_conf_comp_t conf = NULL; + gpgme_conf_comp_t comp = NULL; + cjson_t j_tmp; + char *comp_name = NULL; + cjson_t j_comps = xjson_CreateArray (); + + ctx = get_context (GPGME_PROTOCOL_GPGCONF); + + j_tmp = cJSON_GetObjectItem (request, "component"); + if (j_tmp && cjson_is_string (j_tmp)) + { + comp_name = j_tmp->valuestring; + } + else if (j_tmp && !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + /* Load the config */ + err = gpgme_op_conf_load (ctx, &conf); + if (err) + { + goto leave; + } + + comp = conf; + for (comp = conf; comp; comp = comp->next) + { + if (comp_name && comp->name && strcmp (comp->name, comp_name)) + { + /* Skip components if a single one is specified */ + continue; + } + cJSON_AddItemToArray (j_comps, conf_comp_to_json (comp)); + } + xjson_AddItemToObject (result, "components", j_comps); + +leave: + gpgme_conf_release (conf); + release_context (ctx); + + return err; +} + + -static const char hlp_getmore[] = - "op: \"getmore\"\n" +static const char hlp_createkey[] = + "op: \"createkey\"\n" + "userid: The user id. E.g. \"Foo Bar <[email protected]>\"\n" "\n" "Optional parameters:\n" - "chunksize: Max number of bytes in the \"data\" object.\n" + "algo: Algo of the key as string. See doc for gpg --quick-gen-key.\n" + "subkey-algo: Algo of the encryption subkey. If ommited the same as algo\n" + " is used.\n" + " Except for dsa and ed25519 where the according\n" + " elg / cv25519 algo will be used as subkey-algo.\n" + "\n" + " If algo is omitted or default or future-default subkey-algo\n" + " is ignored.\n" + "expires: Seconds from now to expiry as Number. 0 means no expiry.\n" + "\n" + "Response on success:\n" + "fingerprint: The fingerprint of the created key.\n" + "\n" + "Note: This interface does not allow key generation if the userid\n" + "of the new key already exists in the keyring.\n"; +static gpg_error_t +op_createkey (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + unsigned int flags = GPGME_CREATE_FORCE; /* Always force as the GUI should + handle checks, if required. */ + unsigned long expires = 0; + cjson_t j_tmp; + const char *algo = "default"; + const char *userid; + gpgme_genkey_result_t res; + char *new_fpr = NULL; + +#ifdef GPG_AGENT_ALLOWS_KEYGEN_TRHOUGH_BROWSER + /* GnuPG forbids keygen through the browser socket so for + this we create an unrestricted context. + See GnuPG-Bug-Id: T4010 for more info */ + ctx = get_context (GPGME_PROTOCOL_OpenPGP); +#else + err = gpgme_new (&ctx); + if (err) + log_fatal ("error creating GPGME context: %s\n", gpg_strerror (err)); + gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP); +#endif + + j_tmp = cJSON_GetObjectItem (request, "algo"); + if (j_tmp && cjson_is_string (j_tmp)) + { + algo = j_tmp->valuestring; + } + + j_tmp = cJSON_GetObjectItem (request, "userid"); + if (!j_tmp || !cjson_is_string (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + userid = j_tmp->valuestring; + + j_tmp = cJSON_GetObjectItem (request, "expires"); + if (j_tmp) + { + if (!cjson_is_number (j_tmp)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + expires = j_tmp->valueint; + + if (!expires) + flags |= GPGME_CREATE_NOEXPIRE; + } + + + if ((err = gpgme_op_createkey (ctx, userid, algo, 0, expires, NULL, flags))) + goto leave; + + res = gpgme_op_genkey_result (ctx); + if (!res) + { + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* Dup the fpr as the result might become invalid after context reuse. */ + new_fpr = xstrdup (res->fpr); + + if (algo && strcmp ("default", algo) && strcmp ("future-default", algo)) + { + /* We need to add the encryption subkey manually */ + gpgme_ctx_t keylistctx = create_onetime_context (GPGME_PROTOCOL_OpenPGP); + gpgme_key_t new_key = NULL; + char *subkey_algo = NULL; + + j_tmp = cJSON_GetObjectItem (request, "subkey_algo"); + if (j_tmp && cjson_is_string (j_tmp)) + { + subkey_algo = xstrdup (j_tmp->valuestring); + } + + if (!subkey_algo) + { + subkey_algo = strdup (algo); + if (!strncmp ("dsa", subkey_algo, 3)) + { + subkey_algo[0] = 'e'; + subkey_algo[1] = 'l'; + subkey_algo[2] = 'g'; + } + if (!strcmp ("ed25519", subkey_algo)) + { + strcpy (subkey_algo, "cv25519"); + } + } + + err = gpgme_get_key (keylistctx, new_fpr, &new_key, 1); + release_onetime_context (keylistctx); + if (err) + { + gpg_error_object (result, err, "Error finding created key: %s", + gpg_strerror (err)); + xfree (subkey_algo); + goto leave; + } + + err = gpgme_op_createsubkey (ctx, new_key, subkey_algo, + 0, expires, flags |= GPGME_CREATE_ENCR); + xfree (subkey_algo); + if (err) + goto leave; + } + + xjson_AddStringToObject0 (result, "fingerprint", new_fpr); + +leave: + xfree (new_fpr); +#ifdef GPG_AGENT_ALLOWS_KEYGEN_TRHOUGH_BROWSER + release_context (ctx); +#else + gpgme_release (ctx); +#endif + + return err; +} + + + +static const char hlp_getmore[] = + "op: \"getmore\"\n" "\n" "Response on success:\n" - "type: Type of the pending data\n" - "data: The next chunk of data\n" - "base64: Boolean indicating whether data is base64 encoded\n" - "more: Optional boolean requesting another \"getmore\"."; + "response: base64 encoded json response.\n" + "more: Another getmore is required.\n" + "base64: boolean if the response is base64 encoded.\n"; static gpg_error_t op_getmore (cjson_t request, cjson_t result) { @@ -1372,20 +3113,24 @@ op_getmore (cjson_t request, cjson_t result) if ((err = get_chunksize (request, &chunksize))) goto leave; - /* Adjust the chunksize if we need to do base64 conversion. */ - if (pending_data.base64) - chunksize = (chunksize / 4) * 3; + /* For the meta data we need 41 bytes: + {"more":true,"base64":true,"response":""} */ + chunksize -= 41; + + /* Adjust the chunksize for the base64 conversion. */ + chunksize = (chunksize / 4) * 3; /* Do we have anything pending? */ if (!pending_data.buffer) { err = gpg_error (GPG_ERR_NO_DATA); - error_object (result, "Operation not possible: %s", gpg_strerror (err)); + gpg_error_object (result, err, "Operation not possible: %s", + gpg_strerror (err)); goto leave; } - xjson_AddStringToObject (result, "type", pending_data.type); - xjson_AddBoolToObject (result, "base64", pending_data.base64); + /* We currently always use base64 encoding for simplicity. */ + xjson_AddBoolToObject (result, "base64", 1); if (pending_data.written >= pending_data.length) { @@ -1394,7 +3139,7 @@ op_getmore (cjson_t request, cjson_t result) gpgme_free (pending_data.buffer); pending_data.buffer = NULL; xjson_AddBoolToObject (result, "more", 0); - err = cjson_AddStringToObject (result, "data", ""); + err = cjson_AddStringToObject (result, "response", ""); } else { @@ -1409,21 +3154,16 @@ op_getmore (cjson_t request, cjson_t result) c = pending_data.buffer[pending_data.written + n]; pending_data.buffer[pending_data.written + n] = 0; - if (pending_data.base64) - err = add_base64_to_object (result, "data", - (pending_data.buffer - + pending_data.written), n); - else - err = cjson_AddStringToObject (result, "data", - (pending_data.buffer - + pending_data.written)); + err = add_base64_to_object (result, "response", + (pending_data.buffer + + pending_data.written), n); pending_data.buffer[pending_data.written + n] = c; if (!err) { pending_data.written += n; if (pending_data.written >= pending_data.length) { - gpgme_free (pending_data.buffer); + xfree (pending_data.buffer); pending_data.buffer = NULL; } } @@ -1443,11 +3183,27 @@ static const char hlp_help[] = "operation is not performned but a string with the documentation\n" "returned. To list all operations it is allowed to leave out \"op\" in\n" "help mode. Supported values for \"op\" are:\n\n" - " encrypt Encrypt data.\n" + " config Read configuration values.\n" + " config_opt Read a single configuration value.\n" " decrypt Decrypt data.\n" + " delete Delete a key.\n" + " encrypt Encrypt data.\n" + " export Export keys.\n" + " createkey Generate a keypair (OpenPGP only).\n" + " import Import data.\n" + " keylist List keys.\n" " sign Sign data.\n" - " getmore Retrieve remaining data.\n" - " help Help overview."; + " verify Verify data.\n" + " version Get engine information.\n" + " getmore Retrieve remaining data if chunksize was used.\n" + " help Help overview.\n" + "\n" + "If the data needs to be transferred in smaller chunks the\n" + "property \"chunksize\" with an integer value can be added.\n" + "When \"chunksize\" is set the response (including json) will\n" + "not be larger then \"chunksize\" but might be smaller.\n" + "The chunked result will be transferred in base64 encoded chunks\n" + "using the \"getmore\" operation. See help getmore for more info."; static gpg_error_t op_help (cjson_t request, cjson_t result) { @@ -1484,11 +3240,20 @@ process_request (const char *request) gpg_error_t (*handler)(cjson_t request, cjson_t result); const char * const helpstr; } optbl[] = { - { "encrypt", op_encrypt, hlp_encrypt }, - { "decrypt", op_decrypt, hlp_decrypt }, - { "sign", op_sign, hlp_sign }, - { "getmore", op_getmore, hlp_getmore }, - { "help", op_help, hlp_help }, + { "config", op_config, hlp_config }, + { "config_opt", op_config_opt, hlp_config_opt }, + { "encrypt", op_encrypt, hlp_encrypt }, + { "export", op_export, hlp_export }, + { "decrypt", op_decrypt, hlp_decrypt }, + { "delete", op_delete, hlp_delete }, + { "createkey", op_createkey, hlp_createkey }, + { "keylist", op_keylist, hlp_keylist }, + { "import", op_import, hlp_import }, + { "sign", op_sign, hlp_sign }, + { "verify", op_verify, hlp_verify }, + { "version", op_version, hlp_version }, + { "getmore", op_getmore, hlp_getmore }, + { "help", op_help, hlp_help }, { NULL } }; size_t erroff; @@ -1496,8 +3261,9 @@ process_request (const char *request) cjson_t j_tmp, j_op; cjson_t response; int helpmode; + int is_getmore = 0; const char *op; - char *res; + char *res = NULL; int idx; response = xjson_CreateObject (); @@ -1541,7 +3307,7 @@ process_request (const char *request) else { gpg_error_t err; - + is_getmore = optbl[idx].handler == op_getmore; /* If this is not the "getmore" command and we have any * pending data release that data. */ if (pending_data.buffer && optbl[idx].handler != op_getmore) @@ -1558,8 +3324,8 @@ process_request (const char *request) || strcmp (j_tmp->valuestring, "error")) { /* No error type response - provide a generic one. */ - error_object (response, "Operation failed: %s", - gpg_strerror (err)); + gpg_error_object (response, err, "Operation failed: %s", + gpg_strerror (err)); } xjson_AddStringToObject (response, "op", op); @@ -1573,14 +3339,37 @@ process_request (const char *request) } leave: - cJSON_Delete (json); - if (opt_interactive) - res = cJSON_Print (response); + if (is_getmore) + { + /* For getmore we bypass the encode_and_chunk. */ + if (opt_interactive) + res = cJSON_Print (response); + else + res = cJSON_PrintUnformatted (response); + } else - res = cJSON_PrintUnformatted (response); + res = encode_and_chunk (json, response); if (!res) - log_error ("Printing JSON data failed\n"); + { + cjson_t err_obj; + + log_error ("printing JSON data failed\n"); + + err_obj = error_object (NULL, "Printing JSON data failed"); + if (opt_interactive) + res = cJSON_Print (err_obj); + res = cJSON_PrintUnformatted (err_obj); + cJSON_Delete (err_obj); + } + + cJSON_Delete (json); cJSON_Delete (response); + + if (!res) + { + /* Can't happen unless we created a broken error_object above */ + return xtrystrdup ("Bug: Fatal error in process request\n"); + } return res; } @@ -1952,7 +3741,7 @@ native_messaging_repl (void) } /* Read request. */ - request = xtrymalloc (nrequest); + request = xtrymalloc (nrequest + 1); if (!request) { err = gpg_error_from_syserror (); @@ -1977,6 +3766,7 @@ native_messaging_repl (void) } else /* Process request */ { + request[n] = '\0'; /* Ensure that request has an end */ if (opt_debug) log_debug ("request='%s'\n", request); xfree (response); @@ -2015,6 +3805,10 @@ native_messaging_repl (void) log_error ("error writing request: %s\n", gpg_strerror (err)); break; } + xfree (response); + response = NULL; + xfree (request); + request = NULL; } xfree (response); @@ -2079,6 +3873,8 @@ main (int argc, char *argv[]) }; gpgrt_argparse_t pargs = { &argc, &argv}; + int log_file_set = 0; + gpgrt_set_strusage (my_strusage); #ifdef HAVE_SETLOCALE @@ -2115,12 +3911,24 @@ main (int argc, char *argv[]) if (!opt_debug) { + /* Handling is similar to GPGME_DEBUG */ const char *s = getenv ("GPGME_JSON_DEBUG"); + const char *s1; + if (s && atoi (s) > 0) - opt_debug = 1; + { + opt_debug = 1; + s1 = strchr (s, PATHSEP_C); + if (s1 && strlen (s1) > 2) + { + s1++; + log_set_file (s1); + log_file_set = 1; + } + } } - if (opt_debug) + if (opt_debug && !log_file_set) { const char *home = getenv ("HOME"); char *file = xstrconcat ("socket://", diff --git a/src/gpgme.c b/src/gpgme.c index 82d67478..2d829d9b 100644 --- a/src/gpgme.c +++ b/src/gpgme.c @@ -249,6 +249,7 @@ gpgme_release (gpgme_ctx_t ctx) free (ctx->lc_messages); free (ctx->override_session_key); free (ctx->request_origin); + free (ctx->auto_key_locate); _gpgme_engine_info_release (ctx->engine_info); ctx->engine_info = NULL; DESTROY_LOCK (ctx->lock); @@ -542,6 +543,17 @@ gpgme_set_ctx_flag (gpgme_ctx_t ctx, const char *name, const char *value) { ctx->no_symkey_cache = abool; } + else if (!strcmp (name, "ignore-mdc-error")) + { + ctx->ignore_mdc_error = abool; + } + else if (!strcmp (name, "auto-key-locate")) + { + free (ctx->auto_key_locate); + ctx->auto_key_locate = strdup (value); + if (!ctx->auto_key_locate) + err = gpg_error_from_syserror (); + } else err = gpg_error (GPG_ERR_UNKNOWN_NAME); @@ -591,6 +603,14 @@ gpgme_get_ctx_flag (gpgme_ctx_t ctx, const char *name) { return ctx->no_symkey_cache? "1":""; } + else if (!strcmp (name, "ignore-mdc-error")) + { + return ctx->ignore_mdc_error? "1":""; + } + else if (!strcmp (name, "auto-key-locate")) + { + return ctx->auto_key_locate? ctx->auto_key_locate : ""; + } else return NULL; } diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 49fafb90..35968017 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -404,7 +404,9 @@ typedef unsigned int gpgme_export_mode_t; /* Flags for the audit log functions. */ +#define GPGME_AUDITLOG_DEFAULT 0 #define GPGME_AUDITLOG_HTML 1 +#define GPGME_AUDITLOG_DIAG 2 #define GPGME_AUDITLOG_WITH_HELP 128 @@ -1178,6 +1180,8 @@ gpgme_error_t gpgme_data_new_from_cbs (gpgme_data_t *dh, gpgme_error_t gpgme_data_new_from_fd (gpgme_data_t *dh, int fd); gpgme_error_t gpgme_data_new_from_stream (gpgme_data_t *dh, FILE *stream); +gpgme_error_t gpgme_data_new_from_estream (gpgme_data_t *r_dh, + gpgrt_stream_t stream); /* Return the encoding attribute of the data buffer DH */ gpgme_data_encoding_t gpgme_data_get_encoding (gpgme_data_t dh); @@ -1365,8 +1369,12 @@ struct _gpgme_op_decrypt_result /* The message claims that the content is a MIME object. */ unsigned int is_mime : 1; + /* The message was made by a legacy algorithm without any integrity + * protection. This might be an old but legitimate message. */ + unsigned int legacy_cipher_nomdc : 1; + /* Internal to GPGME, do not use. */ - int _unused : 29; + int _unused : 28; gpgme_recipient_t recipients; diff --git a/src/keysign.c b/src/keysign.c index c2fcabb5..5e497935 100644 --- a/src/keysign.c +++ b/src/keysign.c @@ -171,7 +171,7 @@ keysign_start (gpgme_ctx_t ctx, int synchronous, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -85,7 +85,8 @@ gpgme_error_t _gpgme_verify_status_handler (void *priv, /* From decrypt.c. */ -gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx); +gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx, + gpgme_data_t plaintext); gpgme_error_t _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args); diff --git a/src/passwd.c b/src/passwd.c index 5bd67a52..6c03002b 100644 --- a/src/passwd.c +++ b/src/passwd.c @@ -151,7 +151,7 @@ passwd_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } @@ -449,7 +449,7 @@ sign_start (gpgme_ctx_t ctx, int synchronous, gpgme_data_t plain, if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler - (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 30c35f09..b5825d20 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -19,7 +19,8 @@ ## Process this file with automake to produce Makefile.in -TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) +GNUPGHOME=$(abs_builddir) +TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) TESTS = t-version t-data t-engine-info diff --git a/tests/gpg/Makefile.am b/tests/gpg/Makefile.am index b50f4b07..392fc898 100644 --- a/tests/gpg/Makefile.am +++ b/tests/gpg/Makefile.am @@ -22,7 +22,8 @@ GPG = gpg GPG_AGENT = gpg-agent -TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) LC_ALL=C GPG_AGENT_INFO= \ +GNUPGHOME=$(abs_builddir) +TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) LC_ALL=C GPG_AGENT_INFO= \ top_srcdir=$(top_srcdir) # The keylist tests must come after the import and the edit test. diff --git a/tests/gpgsm/Makefile.am b/tests/gpgsm/Makefile.am index d2acd05b..c2599204 100644 --- a/tests/gpgsm/Makefile.am +++ b/tests/gpgsm/Makefile.am @@ -22,7 +22,8 @@ GPGSM = gpgsm GPG_AGENT = gpg-agent -TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) LC_ALL=C GPG_AGENT_INFO= \ +GNUPGHOME=$(abs_builddir) +TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) LC_ALL=C GPG_AGENT_INFO= \ top_srcdir=$(top_srcdir) noinst_HEADERS = t-support.h diff --git a/tests/opassuan/Makefile.am b/tests/opassuan/Makefile.am index 31d26edd..1dba3e8f 100644 --- a/tests/opassuan/Makefile.am +++ b/tests/opassuan/Makefile.am @@ -17,7 +17,8 @@ ## Process this file with automake to produce Makefile.in -TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) GPG_AGENT_INFO= +GNUPGHOME=$(abs_builddir) +TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) GPG_AGENT_INFO= noinst_HEADERS = TESTS = diff --git a/tests/run-decrypt.c b/tests/run-decrypt.c index 69de139c..c9d9e72d 100644 --- a/tests/run-decrypt.c +++ b/tests/run-decrypt.c @@ -55,6 +55,7 @@ print_result (gpgme_decrypt_result_t result) printf ("Original file name .: %s\n", nonnull(result->file_name)); printf ("Wrong key usage ....: %s\n", result->wrong_key_usage? "yes":"no"); + printf ("Legacy w/o MDC ... .: %s\n", result->legacy_cipher_nomdc?"yes":"no"); printf ("Compliance de-vs ...: %s\n", result->is_de_vs? "yes":"no"); printf ("MIME flag ..........: %s\n", result->is_mime? "yes":"no"); printf ("Unsupported algo ...: %s\n", nonnull(result->unsupported_algorithm)); @@ -85,7 +86,9 @@ show_usage (int ex) " --override-session-key STRING use STRING as session key\n" " --request-origin STRING use STRING as request origin\n" " --no-symkey-cache disable the use of that cache\n" + " --ignore-mdc-error allow decryption of legacy data\n" " --unwrap remove only the encryption layer\n" + " --diagnostics print diagnostics\n" , stderr); exit (ex); } @@ -108,7 +111,9 @@ main (int argc, char **argv) const char *override_session_key = NULL; const char *request_origin = NULL; int no_symkey_cache = 0; + int ignore_mdc_error = 0; int raw_output = 0; + int diagnostics = 0; if (argc) { argc--; argv++; } @@ -169,6 +174,16 @@ main (int argc, char **argv) no_symkey_cache = 1; argc--; argv++; } + else if (!strcmp (*argv, "--ignore-mdc-error")) + { + ignore_mdc_error = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--diagnostics")) + { + diagnostics = 1; + argc--; argv++; + } else if (!strcmp (*argv, "--unwrap")) { flags |= GPGME_DECRYPT_UNWRAP; @@ -240,7 +255,18 @@ main (int argc, char **argv) err = gpgme_set_ctx_flag (ctx, "no-symkey-cache", "1"); if (err) { - fprintf (stderr, PGM ": error setting no-symkey-cache: %s\n", + fprintf (stderr, PGM ": error setting no-symkey-cache: %s\n", + gpgme_strerror (err)); + exit (1); + } + } + + if (ignore_mdc_error) + { + err = gpgme_set_ctx_flag (ctx, "ignore-mdc-error", "1"); + if (err) + { + fprintf (stderr, PGM ": error setting ignore-mdc-error: %s\n", gpgme_strerror (err)); exit (1); } @@ -264,9 +290,33 @@ main (int argc, char **argv) err = gpgme_op_decrypt_ext (ctx, flags, in, out); result = gpgme_op_decrypt_result (ctx); + + if (diagnostics) + { + gpgme_data_t diag; + gpgme_error_t diag_err; + + gpgme_data_new (&diag); + diag_err = gpgme_op_getauditlog (ctx, diag, GPGME_AUDITLOG_DIAG); + if (diag_err) + { + fprintf (stderr, PGM ": getting diagnostics failed: %s\n", + gpgme_strerror (diag_err)); + } + else + { + fputs ("Begin Diagnostics:\n", stdout); + print_data (diag); + fputs ("End Diagnostics.\n", stdout); + } + gpgme_data_release (diag); + } + if (err) { fprintf (stderr, PGM ": decrypt failed: %s\n", gpgme_strerror (err)); + if (result) + print_result (result); exit (1); } if (result) diff --git a/tests/run-keylist.c b/tests/run-keylist.c index 295251ae..9206b50a 100644 --- a/tests/run-keylist.c +++ b/tests/run-keylist.c @@ -47,6 +47,7 @@ show_usage (int ex) " --openpgp use the OpenPGP protocol (default)\n" " --cms use the CMS protocol\n" " --secret list only secret keys\n" + " --with-secret list pubkeys with secret info filled\n" " --local use GPGME_KEYLIST_MODE_LOCAL\n" " --extern use GPGME_KEYLIST_MODE_EXTERN\n" " --sigs use GPGME_KEYLIST_MODE_SIGS\n" @@ -57,6 +58,7 @@ show_usage (int ex) " --import import all keys\n" " --offline use offline mode\n" " --from-file list all keys in the given file\n" + " --from-wkd list key from a web key directory\n" " --require-gnupg required at least the given GnuPG version\n" , stderr); exit (ex); @@ -100,6 +102,7 @@ main (int argc, char **argv) int only_secret = 0; int offline = 0; int from_file = 0; + int from_wkd = 0; gpgme_data_t data = NULL; @@ -171,6 +174,11 @@ main (int argc, char **argv) mode |= GPGME_KEYLIST_MODE_VALIDATE; argc--; argv++; } + else if (!strcmp (*argv, "--with-secret")) + { + mode |= GPGME_KEYLIST_MODE_WITH_SECRET; + argc--; argv++; + } else if (!strcmp (*argv, "--import")) { import = 1; @@ -194,6 +202,12 @@ main (int argc, char **argv) gpgme_set_global_flag ("require-gnupg", *argv); argc--; argv++; } + else if (!strcmp (*argv, "--from-wkd")) + { + argc--; argv++; + mode |= GPGME_KEYLIST_MODE_LOCATE; + from_wkd = 1; + } else if (!strncmp (*argv, "--", 2)) show_usage (1); } @@ -213,6 +227,13 @@ main (int argc, char **argv) gpgme_set_offline (ctx, offline); + if (from_wkd) + { + err = gpgme_set_ctx_flag (ctx, "auto-key-locate", + "clear,nodefault,wkd"); + fail_if_err (err); + } + if (from_file) { err = gpgme_data_new_from_file (&data, *argv, 1); |