aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS8
-rw-r--r--NEWS21
-rw-r--r--configure.ac11
-rw-r--r--doc/gpgme.texi132
-rw-r--r--lang/cpp/src/context.cpp30
-rw-r--r--lang/cpp/src/context.h14
-rw-r--r--lang/cpp/src/data.cpp5
-rw-r--r--lang/cpp/src/data.h3
-rw-r--r--lang/cpp/src/decryptionresult.cpp13
-rw-r--r--lang/cpp/src/decryptionresult.h2
-rw-r--r--lang/cpp/src/gpggencardkeyinteractor.cpp9
-rw-r--r--lang/cpp/src/key.cpp7
-rw-r--r--lang/cpp/src/verificationresult.cpp2
-rw-r--r--lang/python/README22
-rw-r--r--lang/python/README.org22
-rw-r--r--lang/python/docs/GPGMEpythonHOWTOen.org2271
-rw-r--r--lang/python/examples/assuan.py4
-rw-r--r--lang/python/examples/decryption-filter.py7
-rwxr-xr-xlang/python/examples/delkey.py3
-rwxr-xr-xlang/python/examples/exportimport.py3
-rwxr-xr-xlang/python/examples/genkey.py3
-rwxr-xr-xlang/python/examples/howto/decrypt-file.py4
-rwxr-xr-xlang/python/examples/howto/encrypt-file.py10
-rwxr-xr-xlang/python/examples/howto/encrypt-sign-file.py10
-rwxr-xr-xlang/python/examples/howto/encrypt-to-group-gullible.py81
-rwxr-xr-xlang/python/examples/howto/encrypt-to-group-trustno1.py90
-rwxr-xr-xlang/python/examples/howto/encrypt-to-group.py91
-rwxr-xr-xlang/python/examples/howto/export-key.py73
-rwxr-xr-xlang/python/examples/howto/export-minimised-key.py73
-rwxr-xr-xlang/python/examples/howto/export-secret-key.py77
-rwxr-xr-xlang/python/examples/howto/export-secret-keys.py110
-rw-r--r--lang/python/examples/howto/groups.py18
-rwxr-xr-xlang/python/examples/howto/import-key.py93
-rwxr-xr-xlang/python/examples/howto/import-keys.py70
-rwxr-xr-xlang/python/examples/howto/mutt-groups.py64
-rwxr-xr-xlang/python/examples/howto/pmkey-import-alt.py132
-rwxr-xr-xlang/python/examples/howto/pmkey-import.py116
-rwxr-xr-xlang/python/examples/howto/symcrypt-file.py63
-rwxr-xr-xlang/python/examples/howto/temp-homedir-config.py31
-rw-r--r--lang/python/examples/inter-edit.py12
-rwxr-xr-xlang/python/examples/low_level-encrypt_to_all.py13
-rwxr-xr-xlang/python/examples/sign.py3
-rwxr-xr-xlang/python/examples/signverify.py3
-rwxr-xr-xlang/python/examples/simple.py7
-rw-r--r--lang/python/examples/testCMSgetkey.py4
-rwxr-xr-xlang/python/examples/verifydetails.py22
-rwxr-xr-xlang/python/setup.py.in203
-rw-r--r--lang/python/src/__init__.py10
-rw-r--r--lang/python/src/callbacks.py10
-rw-r--r--lang/python/src/constants/__init__.py22
-rw-r--r--lang/python/src/constants/create.py3
-rw-r--r--lang/python/src/constants/data/__init__.py4
-rw-r--r--lang/python/src/constants/data/encoding.py3
-rw-r--r--lang/python/src/constants/event.py3
-rw-r--r--lang/python/src/constants/import.py3
-rw-r--r--lang/python/src/constants/keylist/__init__.py4
-rw-r--r--lang/python/src/constants/keylist/mode.py3
-rw-r--r--lang/python/src/constants/keysign.py3
-rw-r--r--lang/python/src/constants/md.py3
-rw-r--r--lang/python/src/constants/pk.py3
-rw-r--r--lang/python/src/constants/protocol.py3
-rw-r--r--lang/python/src/constants/sig/__init__.py4
-rw-r--r--lang/python/src/constants/sig/mode.py3
-rw-r--r--lang/python/src/constants/sig/notation.py3
-rw-r--r--lang/python/src/constants/sigsum.py3
-rw-r--r--lang/python/src/constants/tofu/__init__.py3
-rw-r--r--lang/python/src/constants/tofu/policy.py3
-rw-r--r--lang/python/src/constants/validity.py3
-rw-r--r--lang/python/src/core.py539
-rw-r--r--lang/python/src/errors.py51
-rw-r--r--lang/python/src/results.py33
-rw-r--r--lang/python/src/util.py20
-rw-r--r--lang/python/tests/Makefile.am3
-rwxr-xr-xlang/python/tests/final.py11
-rwxr-xr-xlang/python/tests/initial.py9
-rw-r--r--lang/python/tests/run-tests.py93
-rw-r--r--lang/python/tests/support.py29
-rwxr-xr-xlang/python/tests/t-callbacks.py53
-rwxr-xr-xlang/python/tests/t-data.py9
-rwxr-xr-xlang/python/tests/t-decrypt-verify.py26
-rwxr-xr-xlang/python/tests/t-decrypt.py3
-rwxr-xr-xlang/python/tests/t-edit.py21
-rwxr-xr-xlang/python/tests/t-encrypt-large.py13
-rwxr-xr-xlang/python/tests/t-encrypt-sign.py17
-rwxr-xr-xlang/python/tests/t-encrypt-sym.py14
-rwxr-xr-xlang/python/tests/t-encrypt.py36
-rwxr-xr-xlang/python/tests/t-export.py7
-rwxr-xr-xlang/python/tests/t-file-name.py5
-rwxr-xr-xlang/python/tests/t-idiomatic.py13
-rwxr-xr-xlang/python/tests/t-import.py73
-rwxr-xr-xlang/python/tests/t-keylist-from-data.py286
-rwxr-xr-xlang/python/tests/t-keylist.py322
-rwxr-xr-xlang/python/tests/t-protocol-assuan.py13
-rwxr-xr-xlang/python/tests/t-quick-key-creation.py28
-rwxr-xr-xlang/python/tests/t-quick-key-manipulation.py11
-rwxr-xr-xlang/python/tests/t-quick-key-signing.py54
-rwxr-xr-xlang/python/tests/t-quick-subkey-creation.py21
-rwxr-xr-xlang/python/tests/t-sig-notation.py28
-rwxr-xr-xlang/python/tests/t-sign.py12
-rwxr-xr-xlang/python/tests/t-signers.py6
-rwxr-xr-xlang/python/tests/t-trustlist.py13
-rwxr-xr-xlang/python/tests/t-verify.py52
-rwxr-xr-xlang/python/tests/t-wait.py5
-rwxr-xr-xlang/python/tests/t-wrapper.py4
-rw-r--r--lang/python/version.py.in15
-rw-r--r--lang/qt/src/threadedjobmixin.cpp84
-rw-r--r--lang/qt/tests/Makefile.am3
-rw-r--r--lang/qt/tests/t-various.cpp19
-rw-r--r--m4/python.m47
-rw-r--r--src/Makefile.am1
-rw-r--r--src/cJSON.c89
-rw-r--r--src/context.h7
-rw-r--r--src/data-estream.c99
-rw-r--r--src/data-mem.c34
-rw-r--r--src/data.c285
-rw-r--r--src/data.h29
-rw-r--r--src/decrypt-verify.c6
-rw-r--r--src/decrypt.c88
-rw-r--r--src/edit.c6
-rw-r--r--src/encrypt-sign.c2
-rw-r--r--src/encrypt.c2
-rw-r--r--src/engine-backend.h2
-rw-r--r--src/engine-gpg.c169
-rw-r--r--src/engine-gpgsm.c7
-rw-r--r--src/engine.c6
-rw-r--r--src/engine.h3
-rw-r--r--src/genkey.c8
-rw-r--r--src/getauditlog.c9
-rw-r--r--src/gpgme-json.c2524
-rw-r--r--src/gpgme.c20
-rw-r--r--src/gpgme.h.in10
-rw-r--r--src/keysign.c2
-rw-r--r--src/ops.h3
-rw-r--r--src/passwd.c2
-rw-r--r--src/sign.c2
-rw-r--r--tests/Makefile.am3
-rw-r--r--tests/gpg/Makefile.am3
-rw-r--r--tests/gpgsm/Makefile.am3
-rw-r--r--tests/opassuan/Makefile.am3
-rw-r--r--tests/run-decrypt.c52
-rw-r--r--tests/run-keylist.c21
141 files changed, 7328 insertions, 2397 deletions
diff --git a/AUTHORS b/AUTHORS
index c989eff7..e0136ffd 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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
diff --git a/NEWS b/NEWS
index e0adb35b..20a80e8d 100644
--- a/NEWS
+++ b/NEWS
@@ -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
+
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),
+ ("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);
diff --git a/src/data.c b/src/data.c
index 7ae5b327..1df6b0a1 100644
--- a/src/data.c
+++ b/src/data.c
@@ -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);
}
diff --git a/src/data.h b/src/data.h
index 0a15b613..692eb9a2 100644
--- a/src/data.h
+++ b/src/data.h
@@ -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);
}
diff --git a/src/edit.c b/src/edit.c
index ca4d5957..2867efb5 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -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;
}
diff --git a/src/ops.h b/src/ops.h
index 5955454c..3b9728dd 100644
--- a/src/ops.h
+++ b/src/ops.h
@@ -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;
}
diff --git a/src/sign.c b/src/sign.c
index bfd9ad18..ab4109ea 100644
--- a/src/sign.c
+++ b/src/sign.c
@@ -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);