diff options
59 files changed, 3081 insertions, 7176 deletions
@@ -1,3 +1,258 @@ +2019-06-13 Werner Koch <wk@gnupg.org> + + Release 1.13.1. + + commit ea11c2a13cd44caf0bab395bd5132bf232318ad8 + * configure.ac: Bump LT versions to c=C33/A22/R1 cpp=C16/A10/R0 + qt=C10/A3/R4. + + python: Fix regression in t-decrypt-verify test. + + commit 7d0a979c07d2a32c1e39a9403f009cbe026f77ff + * lang/python/tests/t-decrypt-verify.py: Comment recent changes. + + python: Set a default-key into gpg.conf for the tests. + + commit ad1395f210f3a3d5839f482f1933eac80a94d174 + * lang/python/tests/Makefile.am (gpg.conf): Set a default key. + + core: At debug levels up to 9 print only an ascii dump. + + commit 4f11210b21a1914a1daf67474e9b82084b2cac01 + * src/debug.c (_gpgme_debug_buffer): Switch between two output + formats. + +2019-06-06 Werner Koch <wk@gnupg.org> + + tests: Minor fix to run-threaded.c. + + commit f6fd90c99aea8b604d76ae49d29bc5269c236a98 + * tests/run-threaded.c (random_data_close): Correct FD test. + + core: Add commented debug helper to posix-io.c. + + commit f84abcad253ace782708c05760e52793814896f4 + * src/posix-io.c (_gpgme_is_fd_valid): New out-commented function. + + core: Fix error return value of _gpgme_run_io_cb. + + commit 6b9ff1ba391a4364b37cb116748194f3f33b4f12 + * src/wait.c (_gpgme_run_io_cb): Fix return code. + +2019-06-06 Andre Heinecke <aheinecke@gnupg.org> + + tests: Add option "allow-del" to run-threaded. + + commit e6f28273062ff26344163e219c1b784ae99de980 + * tests/run-threaded.c (main): Handle allow-del. + (allow-del): New. Variable to allow deletion of keys. + (import): Delete key after import if allow-del is set. + (delete_impres): Delete keys from an import result. + (delete_fpr): Delete a key by fingerprint. + +2019-06-05 Andre Heinecke <aheinecke@gnupg.org> + + tests: Implement import in run-threaded. + + commit 024a7f75d4b8e017b92a13552b23e7fb1d22a5ce + * tests/run-threaded.c (import): New. + (do_data_op): Call it. + +2019-06-05 Werner Koch <wk@gnupg.org> + + core: Prettify _gpgme_io_select debug output again and fix TRACE_SYSRES. + + commit 0f68c9f16bdae7295cac4cbf3c9a197840989a85 + * src/debug.c (_gpgme_debug): Take better care of NULL userinfo. + (_gpgme_debug_end): Rework. + (_trace_sysres): Print ERRNO and not the supplied RES. + +2019-06-05 Andre Heinecke <aheinecke@gnupg.org> + + tests: Use synced output for error in run-threaded. + + commit 3a3648e3a56712261bda4fa866bd2419a740cec8 + * tests/run-threaded.c (do_data_op): Use synced out macro. + + tests: Avoid variable named the same as a macro. + + commit 3a11421d0f63b8cb5afa407cd65f763b11033241 + * tests/run-threaded.c (decrypt, verify): Don't use a variable name + that is also a macro. + + tests,w32: Fix thread creation in run-threaded. + + commit 9bbe15ebbc41533fa219d5b3017a26a75bc72731 + * tests/run-threaded.c (create_thread): Check proper return + value and not return value of CloseHandle. + +2019-06-05 Werner Koch <wk@gnupg.org> + + core: Improve the debug messages even more. + + commit 8f9f3224aac78ce9d54e19e73acf7ab659787168 + * src/debug.c (_gpgme_debug): Add arg LINE. Chnage all callers. + (_gpgme_debug_begin): Remove. + * src/debug.h (TRACE_SEQ): Use the LINE arg of _gpgme_debug. + + core: Avoid explicit locks in the debug code. + + commit 856d2e8d64f668855b1c22d3d38fe783904c6c48 + * src/debug.c (debug_lock): Remove. Also remove all users. + (_gpgme_debug): Use gpgrt_bsprintf to prepare the output and finally + print using standard fprintf. Reformat to prefix to be narrower. + +2019-06-04 Werner Koch <wk@gnupg.org> + + json: Print "nan", "-inf", "inf" if needed. + + commit f56c996318dfa1c9f0d10582b18c2fce10d24668 + * src/cJSON.c (print_number): Print NaN and INF. + + json: Improve handling of large exponents in the JSON parsor. + + commit fabe96126b4ed9c5e07b713813d7db26a02e5e5f + * src/cJSON.c: Include stdint.h. + (parse_number): Avoid overflob in SUBSCALE and cap integer values. + + core: Implement recpstring option parsing for gpgsm. + + commit 1024884e07f750ce781fa74dffa62e126bdda622 + * src/engine-gpg.c (append_args_from_recipients_string): Detect bad + options. + * src/engine-gpgsm.c (set_recipients_from_string): Implement option + parsing. + + core: Make gpgme_op_encrypt_ext work for CMS. + + commit e9ca36f876e1066227668981f5a9e1a7f1031d9e + * src/engine-gpgsm.c (gpgsm_encrypt): Fix argument check. + +2019-06-04 NIIBE Yutaka <gniibe@fsij.org> + + python: Fix typo in DecryptionError exception. + + commit 47135ffdb923de66bc275a37e31811ad22c73dd7 + * lang/python/src/errors.py (DecryptionError): Rename from + DeryptionError. + (UnsupportedAlgorithm): Use DecryptionError. + +2019-06-03 Andre Heinecke <aheinecke@gnupg.org> + + Add NEWS for 1.13.1. + + commit 49883023f661a18d73f9f2c7a3a98902af62ef6b + * NEWS: Add news for 1.13.1 + +2019-05-06 Andre Heinecke <aheinecke@gnupg.org> + + python: Make EXTRA_DIST files explicit. + + commit f9c923bb2d87711235312e8085964372d7480ce4 + * configure.ac: Configure new Makefiles. + * lang/python/Makefile.am: Remove dirs from extra dist and use + subdirs. + * lang/python/examples/Makefile.am, lang/python/src/Makefile.am, + lang/python/doc/Makefile.am: New. Files that list EXTRA_DIST files. + + Python, doc: Minor style improvement. + + commit 63055f13407760c877e5a3a94e564dfb3077dd47 + * lang/python/src/core.py (Context): Retab and shorten + max line length. + + Merge remote-tracking branch 'origin/dkg/fix-T4276' + + commit f303806a110a9813023a5fd9468a55ab0f7fb39d + + +2019-05-03 Andre Heinecke <aheinecke@gnupg.org> + + Always use maintainer mode -Wno cflags. + + commit 33b13d1c290d7ce35c636cce4265b512fb8e64c3 + * configure.ac (CFLAGS): Move -Wno flags out of the maintainer mode. + + cpp: Fix initialization warning. + + commit 0ed81498147723f0e9282df5ae08b8b8e40edd2f + * lanc/cpp/src/gpggencardkeyinteractor.cpp + (GpgGenCardKeyInteractor::Private): Fix initialization warning. + +2019-05-03 Daniel Kahn Gillmor <dkg@fifthhorseman.net> + + python: stop raising BadSignatures from decrypt(verify=True) + + commit 4100794e305ba22241ea5a4f7b42bb5189fbd948 + * src/core.py (decrypt): filter out signatures with errors from the + returned verify_result, but avoid raising BadSignatures + * tests/t-decrypt-verify.py: ensure that only a single signature is + returned when evaluating cipher-3.asc, since the other signature is + unknown. + + python/tests: try to decrypt and verify new test data. + + commit bd2d282e572b5d02669238c9e087259b85638477 + * lang/python/tests/t-decrypt.py: test decryption of cipher-3.asc and + cipher-no-sig.asc + * lang/python/tests/t-decrypt-verify.py: test decryption and + verification of cipher-3.asc and cipher-no-sig.asc + + tests: add two new types of encrypted data. + + commit c5c3a9d10be415ea7bc0cd9730ad6085f16ee7a0 + * tests/gpg/cipher-3.asc: add an encrypted file containing signatures + (one from a known key, and one from an unknown key) + * tests/gpg/cipher-no-sig.asc: add an encrypted file containing no + signatures at all + + python: make it easier to run a limited number of tests. + + commit 30bd1c097544376f257d426d5feb4706fb5d3afd + * lang/python/tests/Makefile.am: prefer py_tests from the environment + if present. + +2019-05-02 Andre Heinecke <aheinecke@gnupg.org> + + tests: Add cms mode to run-import. + + commit 4746c5c9e2dd9a3ee471a429c19bc1f7fd1d07db + * tests/run-import.c (show_usage): Add doc for cms / openpgp. + (main): Handle protocol. + +2019-04-24 Andre Heinecke <aheinecke@gnupg.org> + + cpp: Add wrapper for gpgme_set_global_flag. + + commit 7981ec4147f3058d5b56905903456247993dc6f7 + * lang/cpp/src/context.cpp (setGlobalFlag): New. + * lang/cpp/src/global.h (setGlobalFlag): Export it. + +2019-04-19 NIIBE Yutaka <gniibe@fsij.org> + + core: Fix duplication of close_notify_handler for gpgsm. + + commit 7673ef7953482f42cab50dca1810e5c9d10f461e + * src/engine-gpgsm.c [!USE_DESCRIPTOR_PASSING] (gpgsm_new): Remove + last call to _gpgme_io_set_close_notify. + + core: Fix error return. + + commit 814f6c8de8006830f19a029a879bd92f9e584789 + * src/engine.c (_gpgme_set_engine_info): Add error return. + +2019-04-09 Andre Heinecke <aheinecke@gnupg.org> + + core,w32: Fix minor potential memleak. + + commit 140d694e1fddf16fa3fd2371b9a852ebb14622c8 + * src/w32-util.c (_gpgme_create_process_utf8): Free converted + startup info strings. + + core,w32: Fix new w32-util functions. + + commit ecbba12b869106ba03e10b7b7dd80f74d086831b + * src/w32-util.c (_gpgme_access): Respect mode parameter. + (_gpgme_create_process_utf8): Convert startupinfo, too. + + core,w32: Improve handling of Unicode paths. + + commit a82e3a0ae57a48ba173e282a050680751006c074 + * src/dirinfo.c (get_gpgconf_item): Use _gpgme_access. + * src/posix-util.c (_gpgme_access): Add forward to normal access. + * src/sys-util.h (_gpgme_access): New for posix and w32. + * src/w32-io.c (_gpgme_io_spawn): Use _gpgme_crate_process_utf8. + * src/w32-util.c (utf8_to_wchar, utf8_to_wchar0): The usual w32 conv. + (find_program_in_dir): Use _gpgme_access. + (find_program_at_standard_place): Use wchar API and convert to UTF-8. + (_gpgme_access): Convert UTF-8 to wchar and use wchar API. + (_gpgme_create_process_utf8): Convert UTF-8 to wchar and use wchar API. + + core,w32: Show w32-spawn warning only once. + + commit 937adfdcbb22f715c5a331e5e2d4546ac15a1b7e + * src/w32-io.c (_gpgme_io_spawn): Show MessageBox only once. + +2019-03-27 Andre Heinecke <aheinecke@gnupg.org> + + core, w32: Fix format string errors on windows. + + commit 4a4680f8901ecdcb7e8d5ed55f48226ccfccd7c8 + * src/debug.c (_gpgme_debug): Use gpgrt_vasprintf instead of + vfprintf to have a more portable format. + + core: Fix assuan logger-fd hack for windows. + + commit 19a4c4daa2cfd075b181d5131a4b8b4d54714b8c + * src/assuan-support.c (my_spawn): Zero is a perfectly fine fd. + +2019-03-26 Andre Heinecke <aheinecke@gnupg.org> + + core,w32,glib: Fix build of w32-glib-io.c. + + commit 213c4bc1eb1f45695cc3955cc722ebb363dcbdd0 + * src/w32-glib-io.c (_gpgme_io_pipe, _gpgme_io_connect): Do not + use TRACE_SUC in a return statement. + 2019-03-26 Werner Koch <wk@gnupg.org> Release GPGME 1.13.0. @@ -1,3 +1,34 @@ +Noteworthy changes in version 1.13.1 (2019-06-13) +------------------------------------------------- + + * cpp: gpgme_set_global_flag is now wrapped. [#4471] + + * w32: Improved handling of unicode install paths. [#4453] + + * w32: The gpgme_io_spawn error message is now only shown once. [#4453] + + * Fixed a crash introduced in 1.13.0 when working with S/MIME. [#4556] + + * w32: Fixed format string errors introduced in 1.13.0 that could + cause crashes. [#4440] + + * w32: Fixed an error in the new diagnostic gpgsm support introduced + in 1.13.0 that caused crashes in low fd scenarios. [#4439] + + * python: Fixed a DecryptionError Exception. [#4478] + + * python: No longer raises BadSignatures from decrypt(verify=True). + [#4276] + + * Interface changes relative to the 1.13.0 release: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + cpp: setGlobalFlag NEW. + + [c=C33/A22/R1 cpp=C16/A10/R0 qt=C10/A3/R4] + + Release-info: https://dev.gnupg.org/T4551 + + Noteworthy changes in version 1.13.0 (2019-03-26) ------------------------------------------------- @@ -1 +1 @@ -1.13.0 +1.13.1 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for gpgme 1.13.0. +# Generated by GNU Autoconf 2.69 for gpgme 1.13.1. # # Report bugs to <https://bugs.gnupg.org>. # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='gpgme' PACKAGE_TARNAME='gpgme' -PACKAGE_VERSION='1.13.0' -PACKAGE_STRING='gpgme 1.13.0' +PACKAGE_VERSION='1.13.1' +PACKAGE_STRING='gpgme 1.13.1' PACKAGE_BUGREPORT='https://bugs.gnupg.org' PACKAGE_URL='' @@ -1458,7 +1458,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures gpgme 1.13.0 to adapt to many kinds of systems. +\`configure' configures gpgme 1.13.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1528,7 +1528,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of gpgme 1.13.0:";; + short | recursive ) echo "Configuration of gpgme 1.13.1:";; esac cat <<\_ACEOF @@ -1678,7 +1678,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -gpgme configure 1.13.0 +gpgme configure 1.13.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2508,7 +2508,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by gpgme $as_me 1.13.0, which was +It was created by gpgme $as_me 1.13.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2874,18 +2874,18 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu # LIBGPGME_LT_CURRENT=33 LIBGPGME_LT_AGE=22 -LIBGPGME_LT_REVISION=0 +LIBGPGME_LT_REVISION=1 # If there is an ABI break in gpgmepp or qgpgme also bump the # version in IMPORTED_LOCATION in the GpgmeppConfig-w32.cmake.in.in -LIBGPGMEPP_LT_CURRENT=15 -LIBGPGMEPP_LT_AGE=9 +LIBGPGMEPP_LT_CURRENT=16 +LIBGPGMEPP_LT_AGE=10 LIBGPGMEPP_LT_REVISION=0 LIBQGPGME_LT_CURRENT=10 LIBQGPGME_LT_AGE=3 -LIBQGPGME_LT_REVISION=3 +LIBQGPGME_LT_REVISION=4 ################################################ @@ -2911,7 +2911,7 @@ NEED_LIBASSUAN_VERSION=2.4.2 VERSION_MAJOR=1 VERSION_MINOR=13 -VERSION_MICRO=0 +VERSION_MICRO=1 ac_aux_dir= for ac_dir in build-aux "$srcdir"/build-aux; do @@ -3432,7 +3432,7 @@ fi # Define the identity of the package. PACKAGE='gpgme' - VERSION='1.13.0' + VERSION='1.13.1' cat >>confdefs.h <<_ACEOF @@ -6493,7 +6493,7 @@ test -n "$GITLOG_TO_CHANGELOG" || GITLOG_TO_CHANGELOG="gitlog-to-changelog" -VERSION_NUMBER=0x010d00 +VERSION_NUMBER=0x010d01 # We need to compile and run a program on the build machine. A @@ -23884,7 +23884,7 @@ ENABLED_LANGUAGES=$enabled_languages # # Provide information about the build. # -BUILD_REVISION="1b5a6bf2" +BUILD_REVISION="ea11c2a1" cat >>confdefs.h <<_ACEOF @@ -23893,7 +23893,7 @@ _ACEOF BUILD_VERSION=`echo "$PACKAGE_VERSION" | sed 's/\([0-9.]*\).*/\1./'` -BUILD_VERSION="${BUILD_VERSION}7002" +BUILD_VERSION="${BUILD_VERSION}59921" BUILD_FILEVERSION=`echo "${BUILD_VERSION}" | tr . ,` @@ -24398,16 +24398,15 @@ fi # Checks for compiler features. if test "$GCC" = yes; then CFLAGS="$CFLAGS -Wall -Wcast-align -Wshadow -Wstrict-prototypes" - if test "$USE_MAINTAINER_MODE" = "yes"; then - CFLAGS="$CFLAGS -Wformat -Wno-format-y2k -Wformat-security" + CFLAGS="$CFLAGS -Wno-format-y2k" - # If -Wno-missing-field-initializers is supported we can enable a - # a bunch of really useful warnings. - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if gcc supports -Wno-missing-field-initializers" >&5 + # If -Wno-missing-field-initializers is supported we can expect a + # a larger set of warning options. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if gcc supports -Wno-missing-field-initializers" >&5 $as_echo_n "checking if gcc supports -Wno-missing-field-initializers... " >&6; } - _gcc_cflags_save=$CFLAGS - CFLAGS="-Wno-missing-field-initializers" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext + _gcc_cflags_save=$CFLAGS + CFLAGS="-Wno-missing-field-initializers" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int @@ -24424,19 +24423,23 @@ else _gcc_wopt=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $_gcc_wopt" >&5 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $_gcc_wopt" >&5 $as_echo "$_gcc_wopt" >&6; } - CFLAGS=$_gcc_cflags_save; + CFLAGS=$_gcc_cflags_save; + if test x"$_gcc_wopt" = xyes ; then + CFLAGS="$CFLAGS -Wno-missing-field-initializers" + CFLAGS="$CFLAGS -Wno-sign-compare" + CFLAGS="$CFLAGS -Wno-format-zero-length" + CFLAGS="$CFLAGS -Wno-format-truncation" + CFLAGS="$CFLAGS -Wno-sizeof-pointer-div" + fi + if test "$USE_MAINTAINER_MODE" = "yes"; then if test x"$_gcc_wopt" = xyes ; then CFLAGS="$CFLAGS -W -Wextra -Wbad-function-cast" CFLAGS="$CFLAGS -Wwrite-strings" CFLAGS="$CFLAGS -Wdeclaration-after-statement" - CFLAGS="$CFLAGS -Wno-missing-field-initializers" - CFLAGS="$CFLAGS -Wno-sign-compare" - CFLAGS="$CFLAGS -Wno-format-zero-length" - CFLAGS="$CFLAGS -Wno-format-truncation" - CFLAGS="$CFLAGS -Wno-sizeof-pointer-div" fi + CFLAGS="$CFLAGS -Wformat -Wformat-security" CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wno-shadow" { $as_echo "$as_me:${as_lineno-$LINENO}: checking if gcc supports -Wpointer-arith" >&5 @@ -25663,7 +25666,7 @@ ac_config_files="$ac_config_files lang/js/Makefile lang/js/src/Makefile lang/js/ ac_config_files="$ac_config_files lang/qt/doc/Makefile" -ac_config_files="$ac_config_files lang/python/Makefile lang/python/version.py lang/python/tests/Makefile" +ac_config_files="$ac_config_files lang/python/Makefile lang/python/version.py lang/python/tests/Makefile lang/python/src/Makefile lang/python/examples/Makefile lang/python/doc/Makefile" ac_config_files="$ac_config_files lang/python/setup.py" @@ -26265,7 +26268,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by gpgme $as_me 1.13.0, which was +This file was extended by gpgme $as_me 1.13.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -26331,7 +26334,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -gpgme config.status 1.13.0 +gpgme config.status 1.13.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" @@ -26965,6 +26968,9 @@ do "lang/python/Makefile") CONFIG_FILES="$CONFIG_FILES lang/python/Makefile" ;; "lang/python/version.py") CONFIG_FILES="$CONFIG_FILES lang/python/version.py" ;; "lang/python/tests/Makefile") CONFIG_FILES="$CONFIG_FILES lang/python/tests/Makefile" ;; + "lang/python/src/Makefile") CONFIG_FILES="$CONFIG_FILES lang/python/src/Makefile" ;; + "lang/python/examples/Makefile") CONFIG_FILES="$CONFIG_FILES lang/python/examples/Makefile" ;; + "lang/python/doc/Makefile") CONFIG_FILES="$CONFIG_FILES lang/python/doc/Makefile" ;; "lang/python/setup.py") CONFIG_FILES="$CONFIG_FILES lang/python/setup.py" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; @@ -28651,7 +28657,7 @@ fi echo " GPGME v${VERSION} has been configured as follows: - Revision: 1b5a6bf2 (7002) + Revision: ea11c2a1 (59921) Platform: $host UI Server: $uiserver diff --git a/configure.ac b/configure.ac index 25184f4..7bc0604 100644 --- a/configure.ac +++ b/configure.ac @@ -30,7 +30,7 @@ min_automake_version="1.14" m4_define([mym4_package],[gpgme]) m4_define([mym4_major], [1]) m4_define([mym4_minor], [13]) -m4_define([mym4_micro], [0]) +m4_define([mym4_micro], [1]) # Below is m4 magic to extract and compute the git revision number, # the decimalized short revision number, a beta version string and a @@ -53,18 +53,18 @@ AC_INIT([mym4_package],[mym4_version], [https://bugs.gnupg.org]) # LIBGPGME_LT_CURRENT=33 LIBGPGME_LT_AGE=22 -LIBGPGME_LT_REVISION=0 +LIBGPGME_LT_REVISION=1 # If there is an ABI break in gpgmepp or qgpgme also bump the # version in IMPORTED_LOCATION in the GpgmeppConfig-w32.cmake.in.in -LIBGPGMEPP_LT_CURRENT=15 -LIBGPGMEPP_LT_AGE=9 +LIBGPGMEPP_LT_CURRENT=16 +LIBGPGMEPP_LT_AGE=10 LIBGPGMEPP_LT_REVISION=0 LIBQGPGME_LT_CURRENT=10 LIBQGPGME_LT_AGE=3 -LIBQGPGME_LT_REVISION=3 +LIBQGPGME_LT_REVISION=4 ################################################ AC_SUBST(LIBGPGME_LT_CURRENT) @@ -599,27 +599,30 @@ AM_SUBST_NOTMAKE(API__SSIZE_T) # Checks for compiler features. if test "$GCC" = yes; then CFLAGS="$CFLAGS -Wall -Wcast-align -Wshadow -Wstrict-prototypes" + CFLAGS="$CFLAGS -Wno-format-y2k" + + # If -Wno-missing-field-initializers is supported we can expect a + # a larger set of warning options. + AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers]) + _gcc_cflags_save=$CFLAGS + CFLAGS="-Wno-missing-field-initializers" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) + AC_MSG_RESULT($_gcc_wopt) + CFLAGS=$_gcc_cflags_save; + if test x"$_gcc_wopt" = xyes ; then + CFLAGS="$CFLAGS -Wno-missing-field-initializers" + CFLAGS="$CFLAGS -Wno-sign-compare" + CFLAGS="$CFLAGS -Wno-format-zero-length" + CFLAGS="$CFLAGS -Wno-format-truncation" + CFLAGS="$CFLAGS -Wno-sizeof-pointer-div" + fi if test "$USE_MAINTAINER_MODE" = "yes"; then - CFLAGS="$CFLAGS -Wformat -Wno-format-y2k -Wformat-security" - - # If -Wno-missing-field-initializers is supported we can enable a - # a bunch of really useful warnings. - AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers]) - _gcc_cflags_save=$CFLAGS - CFLAGS="-Wno-missing-field-initializers" - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) - AC_MSG_RESULT($_gcc_wopt) - CFLAGS=$_gcc_cflags_save; if test x"$_gcc_wopt" = xyes ; then CFLAGS="$CFLAGS -W -Wextra -Wbad-function-cast" CFLAGS="$CFLAGS -Wwrite-strings" CFLAGS="$CFLAGS -Wdeclaration-after-statement" - CFLAGS="$CFLAGS -Wno-missing-field-initializers" - CFLAGS="$CFLAGS -Wno-sign-compare" - CFLAGS="$CFLAGS -Wno-format-zero-length" - CFLAGS="$CFLAGS -Wno-format-truncation" - CFLAGS="$CFLAGS -Wno-sizeof-pointer-div" fi + CFLAGS="$CFLAGS -Wformat -Wformat-security" CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wno-shadow" AC_MSG_CHECKING([if gcc supports -Wpointer-arith]) @@ -919,7 +922,10 @@ AC_CONFIG_FILES([lang/js/Makefile lang/js/src/Makefile AC_CONFIG_FILES(lang/qt/doc/Makefile) AC_CONFIG_FILES([lang/python/Makefile lang/python/version.py - lang/python/tests/Makefile]) + lang/python/tests/Makefile + lang/python/src/Makefile + lang/python/examples/Makefile + lang/python/doc/Makefile]) AC_CONFIG_FILES([lang/python/setup.py], [chmod a+x lang/python/setup.py]) AC_OUTPUT @@ -1,7 +1,7 @@ # This is a template. The dist target uses it to create the real file. Summary: GPGME - GnuPG Made Easy Name: gpgme -Version: 1.13.0 +Version: 1.13.1 Release: 1 URL: https://gnupg.org/gpgme.html Source: https://www.gnupg.org/ftp/gcrypt/gpgme/%{name}-%{version}.tar.gz diff --git a/lang/cl/gpgme.asd b/lang/cl/gpgme.asd index b43eca7..43c4744 100644 --- a/lang/cl/gpgme.asd +++ b/lang/cl/gpgme.asd @@ -27,7 +27,7 @@ (defsystem gpgme :description "GnuPG Made Easy." :author "g10 Code GmbH" - :version "1.13.0" + :version "1.13.1" :licence "GPL" :defsystem-depends-on ("cffi-grovel") :depends-on ("cffi" "gpg-error" "trivial-garbage") diff --git a/lang/cpp/src/context.cpp b/lang/cpp/src/context.cpp index 444809a..c0a1dc2 100644 --- a/lang/cpp/src/context.cpp +++ b/lang/cpp/src/context.cpp @@ -1791,3 +1791,8 @@ bool GpgME::hasFeature(unsigned long features, unsigned long features2) && features2 == (features2 & supported_features2) ; } + +int GpgME::setGlobalFlag(const char *name, const char *value) +{ + return gpgme_set_global_flag(name, value); +} diff --git a/lang/cpp/src/global.h b/lang/cpp/src/global.h index 9760e21..d5c2e13 100644 --- a/lang/cpp/src/global.h +++ b/lang/cpp/src/global.h @@ -98,6 +98,9 @@ GPGMEPP_EXPORT const char *dirInfo(const char *what); GPGMEPP_EXPORT Error checkEngine(Protocol proto); GPGMEPP_EXPORT Error checkEngine(Engine engine); +/* Wrapper for gpgme_set_global_flag */ +GPGMEPP_EXPORT int setGlobalFlag(const char *name, const char *value); + GPGMEPP_EXPORT GIOChannel *getGIOChannel(int fd); GPGMEPP_EXPORT QIODevice *getQIODevice(int fd); diff --git a/lang/cpp/src/gpggencardkeyinteractor.cpp b/lang/cpp/src/gpggencardkeyinteractor.cpp index b109866..4d90aa0 100644 --- a/lang/cpp/src/gpggencardkeyinteractor.cpp +++ b/lang/cpp/src/gpggencardkeyinteractor.cpp @@ -41,8 +41,8 @@ public: } std::string name, email, backupFileName, expiry, serial, keysize; - Algo algo; bool backup; + Algo algo; }; GpgGenCardKeyInteractor::~GpgGenCardKeyInteractor() {} diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am index c888448..845b7b1 100644 --- a/lang/python/Makefile.am +++ b/lang/python/Makefile.am @@ -21,12 +21,9 @@ EXTRA_DIST = \ README \ MANIFEST.in \ gpgme.i \ - helpers.c helpers.h private.h \ - examples \ - doc \ - src + helpers.c helpers.h private.h -SUBDIRS = . tests +SUBDIRS = . tests examples doc src .PHONY: prepare prepare: copystamp diff --git a/lang/python/Makefile.in b/lang/python/Makefile.in index 3d83947..6bc5a15 100644 --- a/lang/python/Makefile.in +++ b/lang/python/Makefile.in @@ -411,12 +411,9 @@ EXTRA_DIST = \ README \ MANIFEST.in \ gpgme.i \ - helpers.c helpers.h private.h \ - examples \ - doc \ - src + helpers.c helpers.h private.h -SUBDIRS = . tests +SUBDIRS = . tests examples doc src CLEANFILES = copystamp \ config.h \ data.h \ diff --git a/lang/python/doc/Makefile.am b/lang/python/doc/Makefile.am new file mode 100644 index 0000000..3abc9e6 --- /dev/null +++ b/lang/python/doc/Makefile.am @@ -0,0 +1,48 @@ +# Makefile.am for the Python bindings. +# Copyright (C) 2019 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 <https://gnu.org/licenses/>. +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Created by: +# find . -type f -print | sed 's/^.\// /;$q;s/$/ \\/' | sort +EXTRA_DIST = texinfo/what-was-new.texi \ + meta/old-commits.log \ + meta/TODO.org \ + README \ + rst/_build/README \ + rst/conf.py \ + rst/gpgme-python-howto.rst \ + rst/index.rst \ + rst/maintenance-mode.rst \ + rst/short-history.rst \ + rst/_static/README \ + rst/_templates/README \ + rst/what-is-new.rst \ + rst/what-was-new.rst \ + src/gpgme-python-howto \ + src/index \ + src/maintenance-mode \ + src/short-history \ + src/what-is-new \ + src/what-was-new \ + texinfo/gpgme-python-howto.texi \ + texinfo/index.texi \ + texinfo/maintenance-mode.texi \ + texinfo/short-history.texi \ + texinfo/texinfo.tex \ + texinfo/what-is-new.texi \ + texinfo/what-was-new.texi diff --git a/lang/python/doc/Makefile.in b/lang/python/doc/Makefile.in new file mode 100644 index 0000000..31f8187 --- /dev/null +++ b/lang/python/doc/Makefile.in @@ -0,0 +1,574 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program 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. + +@SET_MAKE@ + +# Makefile.am for the Python bindings. +# Copyright (C) 2019 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 <https://gnu.org/licenses/>. +# SPDX-License-Identifier: LGPL-2.1-or-later +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = lang/python/doc +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_pkg_swig.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \ + $(top_srcdir)/m4/gnupg-ttyname.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/conf/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/mkinstalldirs README +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLED_LANGUAGES = @ENABLED_LANGUAGES@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@ +GLIBC21 = @GLIBC21@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@ +GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@ +GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@ +GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@ +GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@ +GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@ +GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@ +GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@ +GPGME_QT_LIBS = @GPGME_QT_LIBS@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GRAPHVIZ = @GRAPHVIZ@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +HAVE_DOT = @HAVE_DOT@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDADD_FOR_TESTS_KLUDGE = @LDADD_FOR_TESTS_KLUDGE@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@ +LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@ +LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@ +LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@ +LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@ +LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@ +LIBOBJS = @LIBOBJS@ +LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@ +LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@ +LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MOC = @MOC@ +MOC2 = @MOC2@ +NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PYTHON = @PYTHON@ +PYTHONS = @PYTHONS@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_LDFLAGS = @PYTHON_LDFLAGS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +QTCHOOSER = @QTCHOOSER@ +RANLIB = @RANLIB@ +RC = @RC@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SWIG = @SWIG@ +SWIG_LIB = @SWIG_LIB@ +SYSROOT = @SYSROOT@ +VERSION = @VERSION@ +VERSION_MAJOR = @VERSION_MAJOR@ +VERSION_MICRO = @VERSION_MICRO@ +VERSION_MINOR = @VERSION_MINOR@ +VERSION_NUMBER = @VERSION_NUMBER@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +emacs_local_vars_begin = @emacs_local_vars_begin@ +emacs_local_vars_end = @emacs_local_vars_end@ +emacs_local_vars_read_only = @emacs_local_vars_read_only@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ + +# Created by: +# find . -type f -print | sed 's/^.\// /;$q;s/$/ \\/' | sort +EXTRA_DIST = texinfo/what-was-new.texi \ + meta/old-commits.log \ + meta/TODO.org \ + README \ + rst/_build/README \ + rst/conf.py \ + rst/gpgme-python-howto.rst \ + rst/index.rst \ + rst/maintenance-mode.rst \ + rst/short-history.rst \ + rst/_static/README \ + rst/_templates/README \ + rst/what-is-new.rst \ + rst/what-was-new.rst \ + src/gpgme-python-howto \ + src/index \ + src/maintenance-mode \ + src/short-history \ + src/what-is-new \ + src/what-was-new \ + texinfo/gpgme-python-howto.texi \ + texinfo/index.texi \ + texinfo/maintenance-mode.texi \ + texinfo/short-history.texi \ + texinfo/texinfo.tex \ + texinfo/what-is-new.texi \ + texinfo/what-was-new.texi + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/python/doc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu lang/python/doc/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lang/python/doc/src/gpgme-python-howto.pdf b/lang/python/doc/src/gpgme-python-howto.pdf Binary files differdeleted file mode 100644 index 4bc9cb0..0000000 --- a/lang/python/doc/src/gpgme-python-howto.pdf +++ /dev/null diff --git a/lang/python/doc/src/gpgme-python-howto.tex b/lang/python/doc/src/gpgme-python-howto.tex deleted file mode 100644 index 29b305a..0000000 --- a/lang/python/doc/src/gpgme-python-howto.tex +++ /dev/null @@ -1,3110 +0,0 @@ -% Created 2019-01-18 Fri 10:12 -% Intended LaTeX compiler: xelatex -\documentclass[12pt]{article} -\usepackage{graphicx} -\usepackage{grffile} -\usepackage{longtable} -\usepackage{wrapfig} -\usepackage{rotating} -\usepackage[normalem]{ulem} -\usepackage{amsmath} -\usepackage{textcomp} -\usepackage{amssymb} -\usepackage{capt-of} -\usepackage{hyperref} -\usepackage{xltxtra} -\usepackage[margin=1in]{geometry} -\setmainfont[Ligatures={Common}]{Times New Roman} -\author{Ben McGinnes <ben@gnupg.org>} -\author{Ben McGinnes} -\date{\today} -\title{GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)} -\hypersetup{ - pdfauthor={Ben McGinnes}, - pdftitle={GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)}, - pdfkeywords={}, - pdfsubject={}, - pdfcreator={Emacs 26.1 (Org mode 9.1.14)}, - pdflang={English}} -\begin{document} - -\maketitle -\tableofcontents - - - -\section{Introduction} -\label{sec:org94d4d8e} -\begin{center} -\begin{tabular}{ll} -Version: & 0.1.4\\ -GPGME Version: & 1.12.1\\ -Author: & Ben McGinnes <ben@gnupg.org>\\ -Author GPG Key: & DB4724E6FA4286C92B4E55C4321E4E2373590E5D\\ -Language: & Australian English, British English\\ -Language codes: & en-AU, en-GB, en\\ -\end{tabular} -\end{center} - -This document provides basic instruction in how to use the GPGME -Python bindings to programmatically leverage the GPGME library. - - -\subsection{Python 2 versus Python 3} -\label{sec:org64e998a} -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. - -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. - - -\subsection{Examples} -\label{sec:orge70b980} -All of the examples found in this document can be found as Python 3 -scripts in the \texttt{lang/python/examples/howto} directory. - - -\subsection{Unofficial Drafts} -\label{sec:orgc039694} -In addition to shipping with each release of GPGME, there is a section -on locations to read or download \hyperref[sec:org1cd0343]{draft editions} of this document from -at the end of it. These are unofficial versions produced in between -major releases. - - -\subsection{What's New} -\label{sec:org5a3566f} -Full details of what is new are now available in the \href{what-is-new.org}{What's New} file -and archives of the preceding \emph{What's New} sections are available in -the \href{what-was-new}{What Was New} file. - - -\subsubsection{New in GPGME 1·13·0} -\label{sec:orgf12910d} -See the \href{what-is-new\#new-stuff-1-13-0}{What's New} document for what is new in version 1.13.0. - - -\subsubsection{New in GPGME 1·12·0} -\label{sec:org5a31f34} -See the \href{what-was-new\#new-stuff-1-12-0}{What Was New} document for what was new in version 1.12.0. - - -\section{GPGME Concepts} -\label{sec:org5ac85f0} -\subsection{A C API} -\label{sec:org6179bbb} -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 \texttt{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. - - -\subsection{Python bindings} -\label{sec:org9a68df9} -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 -\texttt{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 -\texttt{gpgme.h}. - - -\subsection{Difference between the Python bindings and other GnuPG Python packages} -\label{sec:org780b78f} -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. - - -\subsubsection{The python-gnupg package maintained by Vinay Sajip} -\label{sec:orgb59b8cc} -This is arguably the most popular means of integrating GPG with -Python. The package utilises the \texttt{subprocess} module to implement -wrappers for the \texttt{gpg} and \texttt{gpg2} executables normally invoked on the -command line (\texttt{gpg.exe} and \texttt{gpg2.exe} on Windows). - -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 \texttt{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. - - -\subsubsection{The gnupg package created and maintained by Isis Lovecruft} -\label{sec:orgb7c1dae} -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 \texttt{gpg} or \texttt{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 gnupg package is available under the GNU General Public License -version 3.0 (or any later version). - - -\subsubsection{The PyME package maintained by Martin Albrecht} -\label{sec:orgf9599d9} -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 \href{short-history.org}{Short History} document.\footnote{\texttt{short-history} and/or \texttt{short-history.html}.} - -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 -\texttt{python-gnupg} or \texttt{gnupg} packages. - -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. - -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). - - -\section{GPGME Python bindings installation} -\label{sec:org9681d17} -\subsection{No PyPI} -\label{sec:org002a824} -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. - -This is because the bindings use SWIG to dynamically generate C -bindings against \texttt{gpgme.h} and \texttt{gpgme.h} is generated from -\texttt{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. - -See the additional notes regarding \hyperref[sec:org4315699]{CFFI and SWIG} at the end of this -section for further details. - - -\subsection{Requirements} -\label{sec:org8e7dfcb} -The GPGME Python bindings only have three requirements: - -\begin{enumerate} -\item A suitable version of Python 2 or Python 3. With Python 2 that -means CPython 2.7 and with Python 3 that means CPython 3.4 or -higher. -\item \href{https://www.swig.org}{SWIG}. -\item GPGME itself. Which also means that all of GPGME's dependencies -must be installed too. -\end{enumerate} - - -\subsubsection{Recommended Additions} -\label{sec:orgda93e2c} -Though none of the following are absolute requirements, they are all -recommended for use with the Python bindings. In some cases these -recommendations refer to which version(s) of CPython to use the -bindings with, while others refer to third party modules which provide -a significant advantage in some way. - -\begin{enumerate} -\item If possible, use Python 3 instead of 2. -\item Favour a more recent version of Python since even 3.4 is due to -reach EOL soon. In production systems and services, Python 3.6 -should be robust enough to be relied on. -\item If possible add the following Python modules which are not part of -the standard library: \href{http://docs.python-requests.org/en/latest/index.html}{Requests}, \href{https://cython.org/}{Cython}, \href{https://pendulum.eustace.io/}{Pendulum} and \href{https://github.com/Selfnet/hkp4py}{hkp4py}. -\end{enumerate} - -Chances are quite high that at least the first one and maybe two of -those will already be installed. - -Note that, as with Cython, some of advanced use case scenarios will -bring with them additional requirements. Most of these will be fairly -well known and commonly installed ones, however, which are in many -cases likely to have already been installed on many systems or be -familiar to Python programmers. - - -\subsection{Installation} -\label{sec:org7ac02c3} -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 \texttt{\$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 \texttt{\$PATH}. It specifically checks for the \texttt{python} and \texttt{python3} -executables first and then checks for specific version numbers. - -For Python 2 it checks for these executables in this order: \texttt{python}, -\texttt{python2} and \texttt{python2.7}. - -For Python 3 it checks for these executables in this order: \texttt{python3}, - \texttt{python3.7}, \texttt{python3.6}, \texttt{python3.5} and \texttt{python3.4}.\footnote{With no issues reported specific to Python 3.7, the release of -Python 3.7.1 at around the same time as GPGME 1.12.0 and the testing -with Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of -3.6 now. Production environments with more conservative requirements -will always enforce their own policies anyway and installation to each -supported minor release is quite possible too.} - -On systems where \texttt{python} is actually \texttt{python3} and not \texttt{python2} it -may be possible that \texttt{python2} may be overlooked, but there have been -no reports of that actually occurring as yet. - -In the three months or so since the release of Python 3.7.0 there has -been extensive testing and work with these bindings with no issues -specifically relating to the new version of Python or any of the new -features of either the language or the bindings. This has also been -the case with Python 3.7.1rc1. With that in mind and given the -release of Python 3.7.1 is scheduled for around the same time as GPGME -1.12.0, the order of preferred Python versions has been changed to -move Python 3.7 ahead of Python 3.6. - - -\subsubsection{Installing GPGME} -\label{sec:org0d28316} -See the GPGME \texttt{README} file for details of how to install GPGME from -source. - - -\subsection{Known Issues} -\label{sec:org5f11db8} -There are a few known issues with the current build process and the -Python bindings. For the most part these are easily addressed should -they be encountered. - - -\subsubsection{Breaking Builds} -\label{sec:orga10be64} -Occasionally when installing GPGME with the Python bindings included -it may be observed that the \texttt{make} portion of that process induces a -large very number of warnings and, eventually errors which end that -part of the build process. Yet following that with \texttt{make check} and -\texttt{make install} appears to work seamlessly. - -The cause of this is related to the way SWIG needs to be called to -dynamically generate the C bindings for GPGME in the first place. So -the entire process will always produce \texttt{lang/python/python2-gpg/} and -\texttt{lang/python/python3-gpg/} directories. These should contain the -build output generated during compilation, including the complete -bindings and module installed into \texttt{site-packages}. - -Occasionally the errors in the early part or some other conflict -(e.g. not installing as \textbf{\emph{root}} or \textbf{\emph{su}}) may result in nothing -being installed to the relevant \texttt{site-packages} directory and the -build directory missing a lot of expected files. Even when this -occurs, the solution is actually quite simple and will always work. - -That solution is simply to run the following commands as either the -\textbf{root} user or prepended with \texttt{sudo -H}\footnote{Yes, even if you use virtualenv with everything you do in -Python. If you want to install this module as just your user account -then you will need to manually configure, compile and install the -\emph{entire} GnuPG stack as that user as well. This includes libraries -which are not often installed that way. It can be done and there are -circumstances under which it is worthwhile, but generally only on -POSIX systems which utilise single user mode (some even require it).} in the \texttt{lang/python/} -directory: - -\begin{verbatim} -/path/to/pythonX.Y setup.py build -/path/to/pythonX.Y setup.py build -/path/to/pythonX.Y setup.py install -\end{verbatim} - -Yes, the build command does need to be run twice. Yes, you still need -to run the potentially failing or incomplete steps during the -\texttt{configure}, \texttt{make} and \texttt{make install} steps with installing GPGME. -This is because those steps generate a lot of essential files needed, -both by and in order to create, the bindings (including both the -\texttt{setup.py} and \texttt{gpgme.h} files). - - -\begin{enumerate} -\item IMPORTANT Note -\label{sec:org6926f46} -If specifying a selected number of languages to create bindings for, -try to leave Python last. Currently the majority of the other -language bindings are also preceding Python of either version when -listed alphabetically (not counting the Qt bindings). - -If Python is set to precede one of the other languages then it is -possible that the errors described here may interrupt the build -process before generating bindings for those other languages. In -these cases it may be preferable to configure all preferred language -bindings separately with alternative \texttt{configure} steps for GPGME using -the \texttt{-{}-enable-languages=\$LANGUAGE} option. - -Alternatively \texttt{make} (or \texttt{gmake}, depending on your platform) may be -run with the the \texttt{-k} option, which tells make to keep going even if -errors are encountered. In that case the failure of one language's -set of bindings to build should not hamper another language's bindings -to build. -\end{enumerate} - - -\subsubsection{Reinstalling Responsibly} -\label{sec:orgc1457e4} -Regardless of whether you're installing for one version of Python or -several, there will come a point where reinstallation is required. -With most Python module installations, the installed files go into the -relevant site-packages directory and are then forgotten about. Then -the module is upgraded, the new files are copied over the old and -that's the end of the matter. - -While the same is true of these bindings, there have been intermittent -issues observed on some platforms which have benefited significantly -from removing all the previous installations of the bindings before -installing the updated versions. - -Removing the previous version(s) is simply a matter of changing to the -relevant \texttt{site-packages} directory for the version of Python in -question and removing the \texttt{gpg/} directory and any accompanying -egg-info files for that module. - -In most cases this will require root or administration privileges on -the system, but the same is true of installing the module in the first -place. - - -\subsubsection{Multiple installations} -\label{sec:org3a1a2a2} -For a variety of reasons it may be either necessary or just preferable -to install the bindings to alternative installed Python versions which -meet the requirements of these bindings. - -On POSIX systems this will generally be most simply achieved by -running the manual installation commands (build, build, install) as -described in the previous section for each Python installation the -bindings need to be installed to. - -As per the SWIG documentation: the compilers, libraries and runtime -used to build GPGME and the Python Bindings \textbf{must} match those used to -compile Python itself, including the version number(s) (at least going -by major version numbers and probably minor numbers too). - -On most POSIX systems, including OS X, this will very likely be the -case in most, if not all, cases. - -Note that from GPGME \href{https://dev.gnupg.org/rMff6ff616aea6f59b7f2ce1176492850ecdf3851e}{1.12.1} the default installation installs to each -version of Python it can find first. That is that it will currently -install for the first copies of Python versions 2.7, 3.4, 3.5, 3.6, -3.7 and 3.8 (dev branch) that it finds. Usually this will be in the -same prefix as GPGME itself, but is dictated by the \texttt{\$PATH} when the -installation is performed. The above instructions can still be -performed on other python installations which the installer does not -find, including alternative prefixes. - - - -\subsubsection{Won't Work With Windows} -\label{sec:org06b5473} -There are semi-regular reports of Windows users having considerable -difficulty in installing and using the Python bindings at all. Very -often, possibly even always, these reports come from Cygwin users -and/or MinGW users and/or Msys2 users. Though not all of them have -been confirmed, it appears that these reports have also come from -people who installed Python using the Windows installer files from the -\href{https://python.org}{Python website} (i.e. mostly MSI installers, sometimes self-extracting -\texttt{.exe} files). - -The Windows versions of Python are not built using Cygwin, MinGW or -Msys2; they're built using Microsoft Visual Studio. Furthermore the -version used is \emph{considerably} more advanced than the version which -MinGW obtained a small number of files from many years ago in order to -be able to compile anything at all. Not only that, but there are -changes to the version of Visual Studio between some micro releases, -though that is is particularly the case with Python 2.7, since it has -been kept around far longer than it should have been. - -There are two theoretical solutions to this issue: - -\begin{enumerate} -\item Compile and install the GnuPG stack, including GPGME and the -Python bindings using the same version of Microsoft Visual Studio -used by the Python Foundation to compile the version of Python -installed. - -If there are multiple versions of Python then this will need to be -done with each different version of Visual Studio used for those -versions of Python. - -\item Compile and install Python using the same tools used by choice, -such as MinGW or Msys2. -\end{enumerate} - -Do \textbf{not} use the official Windows installer for Python unless -following the first method. - -In this type of situation it may even be for the best to accept that -there are less limitations on permissive software than free software -and simply opt to use a recent version of the Community Edition of -Microsoft Visual Studio to compile and build all of it, no matter -what. - -Investigations into the extent or the limitations of this issue are -ongoing. - -The following table lists the version of Microsoft Visual Studio which -needs to be used when compiling GPGME and the Python bindings with -each version of the CPython binary released \href{https://www.python.org/downloads/windows/}{for Windows}: - -\begin{center} -\begin{tabular}{rll} -CPython & Microsoft product name & runtime filename\\ -2.7.6 & Visual Studio 2008 & MSVCR90.DLL\\ -3.4.0 & Visual Studio 2010 & MSVCR100.DLL\\ -3.5.0 & Visual Studio 2015 & \textbf{see below}\\ -3.6.0 & Visual Studio 2015 & \textbf{see below}\\ -3.7.0 & Visual Studio 2017* & \textbf{see below}\\ -\end{tabular} -\end{center} - -It is important to note that MingW and Msys2 ship with the Visual C -runtime from Microsoft Visual Studio 2005 and are thus \textbf{incompatible} -with all the versions of CPython which can be used with the GPGME -Python bindings. - -It is also important to note that from CPython 3.5 onwards, the Python -Foundation has adopted the reworking of the Visual C runtime which was -performed for Visual Studio 2015 and aimed at resolving many of these -kinds of issues. Much greater detail on these issues and the correct -file(s) to link to are available from Matthew Brett's invaluable page, -\href{https://matthew-brett.github.io/pydagogue/python\_msvc.html}{Using Microsoft Visual C with Python}. It is also worth reading the -Microsoft Developer Network blog post on \href{http://blogs.msdn.com/b/vcblog/archive/2015/03/03/introducing-the-universal-crt.aspx}{the universal CRT} and Steve -Dower's blog posts on Python extensions (\href{http://stevedower.id.au/blog/building-for-python-3-5}{part 1} and \href{http://stevedower.id.au/blog/building-for-python-3-5-part-two}{part 2}). - -The second of those two posts by Steve Dower contains the details of -specific configuration options required for compiling anything to be -used with official CPython releases. In addition to those -configuration and compiler settings to use, the versions of Visual -Studio prior to Visual Studio 2015 did not support 64-bit systems by -default. So compiling a 64-bit version of these bindings for a 64-bit -version of CPython 2.7 or 3.4 requires additional work. - -In addition to the blog posts, the \href{https://wiki.python.org/moin/WindowsCompilers}{Windows compilers} wiki page on the -CPython wiki is another essential reference on the relevant versions -of Visual Studio to use and the degree of compatibility with CPython -releases. - -Eventually someone will ask why there isn't an installable binary for -Windows, which the GPGME of the licenses do not preclude as long as -the source code is available in conjunction with such a release. - -The sheer number of versions of Visual Studio in conjunction with -differing configuration options depending on the target Windows -version and whether the architecture is 64-bit or 32-bit makes it -difficult to provide a correct binary installer for Windows users. At -the bare minimum doing so would require the GnuPG project compile ten -different versions of the bindings with each release; both 32-bit and -64-bit versions for CPython 2.7 and 3.4, with 64-bit versions for both -x86-64 (i.e. Intel and AMD) and ARM architectures for CPython 3.5, -3.6, 3.7 and later releases. That's the bare \textbf{minimum}, it'd probably -be higher. - -Additionally, with only a binary installation used in conjunction with -the CPython installer from \texttt{python.org} the advanced options available -which utilise \hyperref[sec:orgeda6cec]{Cython} will not be able to be used at all. Cython -depends on being able to compile the C code it generates and that too -would need to utilise a matching runtime to both the installed version -of CPython and these bindings in order to work with the bindings. - -Considering all of that, what do we recommend? - -\begin{enumerate} -\item Use a recent version of CPython; at least 3.5, but ideally 3.6 or -later. - -\item Use Visual Studio 2015 or the standalone build tools for Visual -Studio 2017 (or later). - -\item Compile both CPython and GPGME with these bindings using the tools -selected in step 2. - -\item Ignore MingW, Msys2 and the official CPython binary installers. - -\item Be thankful the answer to this question wasn't simply to say -something like, “install Linux” or “install FreeBSD” (or even -Apple's OS X). -\end{enumerate} - - -\subsubsection{CFFI is the Best™ and GPGME should use it instead of SWIG} -\label{sec:org4315699} -There are many reasons for favouring \href{https://cffi.readthedocs.io/en/latest/overview.html}{CFFI} and proponents of it are -quite happy to repeat these things as if all it would take to switch -from SWIG to CFFI is repeating that list as if it were a new concept. - -The fact is that there are things which Python's CFFI implementation -cannot handle in the GPGME C code. Beyond that there are features of -SWIG which are simply not available with CFFI at all. SWIG generates -the bindings to Python using the \texttt{gpgme.h} file, but that file is not -a single version shipped with each release, it too is generated when -GPGME is compiled. - -CFFI is currently unable to adapt to such a potentially mutable -codebase. If there were some means of applying SWIG's dynamic code -generation to produce the Python/CFFI API modes of accessing the GPGME -libraries (or the source source code directly), but such a thing does -not exist yet either and it currently appears that work is needed in -at least one of CFFI's dependencies before any of this can be -addressed. - -So if you're a massive fan of CFFI; that's great, but if you want this -project to switch to CFFI then rather than just insisting that it -should, I'd suggest you volunteer to bring CFFI up to the level this -project needs. - -If you're actually seriously considering doing so, then I'd suggest -taking the \texttt{gpgme-tool.c} file in the GPGME \texttt{src/} directory and -getting that to work with any of the CFFI API methods (not the ABI -methods, they'll work with pretty much anything). When you start -running into trouble with "ifdefs" then you'll know what sort of -things are lacking. That doesn't even take into account the amount of -work saved via SWIG's code generation techniques either. - - -\subsubsection{Virtualised Environments} -\label{sec:org16ddce9} -It is fairly common practice amongst Python developers to, as much as -possible, use packages like virtualenv to keep various things that are -to be installed from interfering with each other. Given how much of -the GPGME bindings is often at odds with the usual pythonic way of -doing things, it stands to reason that this would be called into -question too. - -As it happens the answer as to whether or not the bindings can be used -with virtualenv, the answer is both yes and no. - -In general we recommend installing to the relevant path and matching -prefix of GPGME itself. Which means that when GPGME, and ideally the -rest of the GnuPG stack, is installed to a prefix like \texttt{/usr/local} or -\texttt{/opt/local} then the bindings would need to be installed to the main -Python installation and not a virtualised abstraction. Attempts to -separate the two in the past have been known to cause weird and -intermittent errors ranging from minor annoyances to complete failures -in the build process. - -As a consequence we only recommend building with and installing to the -main Python installations within the same prefix as GPGME is installed -to or which are found by GPGME's configuration stage immediately prior -to running the make commands. Which is exactly what the compiling and -installing process of GPGME does by default. - -Once that is done, however, it appears that a copy of the compiled -module may be installed into a virtualenv of the same major and minor -version matching the build. Alternatively it is possible to utilise a -\texttt{sites.pth} file in the \texttt{site-packages/} directory of a virtualenv -installation, which links back to the system installations -corresponding directory in order to import anything installed system -wide. This may or may not be appropriate on a case by case basis. - -Though extensive testing of either of these options is not yet -complete, preliminary testing of them indicates that both are viable -as long as the main installation is complete. Which means that -certain other options normally restricted to virtual environments are -also available, including integration with pythonic test suites -(e.g. \href{https://docs.pytest.org/en/latest/index.html}{pytest}) and other large projects. - -That said, it is worth reiterating the warning regarding non-standard -installations. If one were to attempt to install the bindings only to -a virtual environment without somehow also including the full GnuPG -stack (or enough of it as to include GPGME) then it is highly likely -that errors would be encountered at some point and more than a little -likely that the build process itself would break. - -If a degree of separation from the main operating system is still -required in spite of these warnings, then consider other forms of -virtualisation. Either a virtual machine (e.g. \href{https://www.virtualbox.org/}{VirtualBox}), a -hardware emulation layer (e.g. \href{https://www.qemu.org/}{QEMU}) or an application container -(e.g. \href{https://www.docker.com/why-docker}{Docker}). - -Finally it should be noted that the limited tests conducted thus far -have been using the \texttt{virtualenv} command in a new directory to create -the virtual python environment. As opposed to the standard \texttt{python3 --m venv} and it is possible that this will make a difference depending -on the system and version of Python in use. Another option is to run -the command \texttt{python3 -m virtualenv /path/to/install/virtual/thingy} -instead. - - -\section{Fundamentals} -\label{sec:orgabe2f65} -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. - - -\subsection{No REST} -\label{sec:org1784ffa} -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 \emph{\textbf{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. - -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 that this HOWTO deals with. - - -\subsection{Context} -\label{sec:org0fda7e2} -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. - -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. - - -\section{Working with keys} -\label{sec:org3a0c959} -\subsection{Key selection} -\label{sec:orgf03fe33} -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. - -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: - -\begin{verbatim} -import gpg - -k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") -keys = list(k) -\end{verbatim} - -This is passable and very likely to be common: - -\begin{verbatim} -import gpg - -k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") -keys = list(k) -\end{verbatim} - -And this is a really bad idea: - -\begin{verbatim} -import gpg - -k = gpg.Context().keylist(pattern="0xDEADBEEF") -keys = list(k) -\end{verbatim} - -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{verbatim} -import gpg - -ncsc = gpg.Context().keylist(pattern="ncsc.mil") -nsa = list(ncsc) -\end{verbatim} - - -\subsubsection{Counting keys} -\label{sec:orga4f8d07} -Counting the number of keys in your public keybox (\texttt{pubring.kbx}), the -format which has superseded the old keyring format (\texttt{pubring.gpg} and -\texttt{secring.gpg}), or the number of secret keys is a very simple task. - -\begin{verbatim} -import gpg - -c = gpg.Context() -seckeys = c.keylist(pattern=None, secret=True) -pubkeys = c.keylist(pattern=None, secret=False) - -seclist = list(seckeys) -secnum = len(seclist) - -publist = list(pubkeys) -pubnum = len(publist) - -print(""" - Number of secret keys: {0} - Number of public keys: {1} -""".format(secnum, pubnum)) -\end{verbatim} - -NOTE: The \hyperref[sec:orgeda6cec]{Cython} introduction in the \hyperref[sec:org261e415]{Advanced and Experimental} -section uses this same key counting code with Cython to demonstrate -some areas where Cython can improve performance even with the -bindings. Users with large public keyrings or keyboxes, for instance, -should consider these options if they are comfortable with using -Cython. - - -\subsection{Get key} -\label{sec:org661cd0b} -An alternative method of getting a single key via its fingerprint is -available directly within a Context with \texttt{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. - -This first example demonstrates selecting the current key of Werner -Koch, which is due to expire at the end of 2018: - -\begin{verbatim} -import gpg - -fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" -key = gpg.Context().get_key(fingerprint) -\end{verbatim} - -Whereas this example demonstrates selecting the author's current key -with the \texttt{secret} key word argument set to \texttt{True}: - -\begin{verbatim} -import gpg - -fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" -key = gpg.Context().get_key(fingerprint, secret=True) -\end{verbatim} - -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. - - -\subsection{Importing keys} -\label{sec:org5a4abec} -Importing keys is possible with the \texttt{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{verbatim} -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{verbatim} - -NOTE: When searching for a key ID of any length or a fingerprint -(without spaces), the SKS servers require the the leading \texttt{0x} -indicative of hexadecimal be included. Also note that the old short -key IDs (e.g. \texttt{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). - -Testing for whether a string in any given search is or may be a -hexadecimal value which may be missing the leading \texttt{0x} is a simple -matter of using a try/except statement which attempts to convert the -string as hex to an integer and then back to hex; then using that to -search with. Raising a ValueError simply results in treating the -string as a string. This is the method and logic utilised in the -\texttt{import-keys-hkp.py} script (see below). - - -\subsubsection{Working with ProtonMail} -\label{sec:org151cae2} -Here is a variation on the example above which checks the constrained -ProtonMail keyserver for ProtonMail public keys. - -\begin{verbatim} -import gpg -import requests -import sys - -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) -\end{verbatim} - -Both the above example, \href{../examples/howto/pmkey-import.py}{pmkey-import.py}, and a version which prompts -for an alternative GnuPG home directory, \href{../examples/howto/pmkey-import-alt.py}{pmkey-import-alt.py}, are -available with the other examples and are executable scripts. - -Note that while the ProtonMail servers are based on the SKS servers, -their server is related more to their API and is not feature complete -by comparison to the servers in the SKS pool. One notable difference -being that the ProtonMail server does not permit non ProtonMail users -to update their own keys, which could be a vector for attacking -ProtonMail users who may not receive a key's revocation if it had been -compromised. - - -\subsubsection{Importing with HKP for Python} -\label{sec:org11e4a60} -Performing the same tasks with the \href{https://github.com/Selfnet/hkp4py}{hkp4py module} (available via PyPI) -is not too much different, but does provide a number of options of -benefit to end users. Not least of which being the ability to perform -some checks on a key before importing it or not. For instance it may -be the policy of a site or project to only import keys which have not -been revoked. The hkp4py module permits such checks prior to the -importing of the keys found. - -\begin{verbatim} -import gpg -import hkp4py -import sys - -c = gpg.Context() -server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") -results = [] -keys = [] - -if len(sys.argv) > 2: - pattern = " ".join(sys.argv[1:]) -elif len(sys.argv) == 2: - pattern = sys.argv[1] -else: - pattern = input("Enter the pattern to search for keys or user IDs: ") - - -if pattern is not None: - try: - key = server.search(hex(int(pattern, 16))) - keyed = True - except ValueError as ve: - key = server.search(pattern) - keyed = False - - if key is not None: - keys.append(key[0]) - if keyed is True: - try: - fob = server.search(pattern) - except: - fob = None - if fob is not None: - keys.append(fob[0]) - else: - pass - else: - pass - - for logrus in pattern.split(): - try: - key = server.search(hex(int(logrus, 16))) - hexed = True - except ValueError as ve: - key = server.search(logrus) - hexed = False - - if key is not None: - keys.append(key[0]) - if hexed is True: - try: - fob = server.search(logrus) - except: - fob = None - if fob is not None: - keys.append(fob[0]) - else: - pass - else: - pass - - -if len(keys) > 0: - for key in keys: - import_result = c.key_import(key.key_blob) - results.append(import_result) - -for result in results: - 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 -\end{verbatim} - -Since the hkp4py module handles multiple keys just as effectively as -one (\texttt{keys} is a list of responses per matching key), the example -above is able to do a little bit more with the returned data before -anything is actually imported. - - -\subsubsection{Importing from ProtonMail with HKP for Python} -\label{sec:orgce872d8} -Though this can provide certain benefits even when working with -ProtonMail, the scope is somewhat constrained there due to the -limitations of the ProtonMail keyserver. - -For instance, searching the SKS keyserver pool for the term "gnupg" -produces hundreds of results from any time the word appears in any -part of a user ID. Performing the same search on the ProtonMail -keyserver returns zero results, even though there are at least two -test accounts which include it as part of the username. - -The cause of this discrepancy is the deliberate configuration of that -server by ProtonMail to require an exact match of the full email -address of the ProtonMail user whose key is being requested. -Presumably this is intended to reduce breaches of privacy of their -users as an email address must already be known before a key for that -address can be obtained. - - -\begin{enumerate} -\item Import from ProtonMail via HKP for Python Example no. 1 -\label{sec:org4ffaa43} -The following script is available with the rest of the examples under -the somewhat less than original name, \texttt{pmkey-import-hkp.py}. - -\begin{verbatim} -import gpg -import hkp4py -import os.path -import sys - -print(""" -This script searches the ProtonMail key server for the specified key and -imports it. - -Usage: pmkey-import-hkp.py [search strings] -""") - -c = gpg.Context(armor=True) -server = hkp4py.KeyServer("hkps://api.protonmail.ch") -keyterms = [] -ksearch = [] -allkeys = [] -results = [] -paradox = [] -homeless = None - -if len(sys.argv) > 2: - keyterms = sys.argv[1:] -elif len(sys.argv) == 2: - keyterm = sys.argv[1] - keyterms.append(keyterm) -else: - key_term = input("Enter the key ID, UID or search string: ") - keyterms = key_term.split() - -for keyterm in keyterms: - 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: - print("Checking for key for: {0}".format(k)) - try: - keys = server.search(k) - if isinstance(keys, list) is True: - for key in keys: - allkeys.append(key) - try: - import_result = c.key_import(key.key_blob) - except Exception as e: - import_result = c.key_import(key.key) - else: - paradox.append(keys) - import_result = None - except Exception as e: - import_result = None - results.append(import_result) - -for result in results: - 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: - pass -\end{verbatim} - - -\item Import from ProtonMail via HKP for Python Example no. 2 -\label{sec:org62d6113} -Like its counterpart above, this script can also be found with the -rest of the examples, by the name pmkey-import-hkp-alt.py. - -With this script a modicum of effort has been made to treat anything -passed as a \texttt{homedir} which either does not exist or which is not a -directory, as also being a pssible user ID to check for. It's not -guaranteed to pick up on all such cases, but it should cover most of -them. - -\begin{verbatim} -import gpg -import hkp4py -import os.path -import sys - -print(""" -This script searches the ProtonMail key server for the specified key and -imports it. Optionally enables specifying a different GnuPG home directory. - -Usage: pmkey-import-hkp.py [homedir] [search string] - or: pmkey-import-hkp.py [search string] -""") - -c = gpg.Context(armor=True) -server = hkp4py.KeyServer("hkps://api.protonmail.ch") -keyterms = [] -ksearch = [] -allkeys = [] -results = [] -paradox = [] -homeless = None - -if len(sys.argv) > 3: - homedir = sys.argv[1] - keyterms = sys.argv[2:] -elif len(sys.argv) == 3: - homedir = sys.argv[1] - keyterm = sys.argv[2] - keyterms.append(keyterm) -elif len(sys.argv) == 2: - homedir = "" - keyterm = sys.argv[1] - keyterms.append(keyterm) -else: - keyterm = input("Enter the key ID, UID or search string: ") - homedir = input("Enter the GPG configuration directory path (optional): ") - keyterms.append(keyterm) - -if len(homedir) == 0: - homedir = None - homeless = False - -if homedir is not None: - if homedir.startswith("~"): - if os.path.exists(os.path.expanduser(homedir)) is True: - if os.path.isdir(os.path.expanduser(homedir)) is True: - c.home_dir = os.path.realpath(os.path.expanduser(homedir)) - else: - homeless = True - else: - homeless = True - elif os.path.exists(os.path.realpath(homedir)) is True: - if os.path.isdir(os.path.realpath(homedir)) is True: - c.home_dir = os.path.realpath(homedir) - else: - homeless = True - else: - homeless = True - -# First check to see if the homedir really is a homedir and if not, treat it as -# a search string. -if homeless is True: - keyterms.append(homedir) - c.home_dir = None -else: - pass - -for keyterm in keyterms: - 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: - print("Checking for key for: {0}".format(k)) - try: - keys = server.search(k) - if isinstance(keys, list) is True: - for key in keys: - allkeys.append(key) - try: - import_result = c.key_import(key.key_blob) - except Exception as e: - import_result = c.key_import(key.key) - else: - paradox.append(keys) - import_result = None - except Exception as e: - import_result = None - results.append(import_result) - -for result in results: - 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: - pass -\end{verbatim} -\end{enumerate} - - -\subsection{Exporting keys} -\label{sec:orgceb798d} -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. - - -\subsubsection{Exporting public keys} -\label{sec:orga5e633a} -There are two methods of exporting public keys, both of which are very -similar to the other. The default method, \texttt{key\_export()}, will export -a public key or keys matching a specified pattern as normal. The -alternative, the \texttt{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{verbatim} -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{verbatim} - -It should be noted that the result will only return \texttt{None} when a -search pattern has been entered, but has not matched any keys. When -the search pattern itself is set to \texttt{None} this triggers the exporting -of the entire public keybox. - -\begin{verbatim} -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{verbatim} - - -\subsubsection{Exporting secret keys} -\label{sec:org732fce1} -Exporting secret keys is, functionally, very similar to exporting -public keys; save for the invocation of \texttt{pinentry} via \texttt{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{verbatim} -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 len(homedir) == 0: - homedir = None -elif homedir.startswith("~"): - userdir = os.path.expanduser(homedir) - if os.path.exists(userdir) is True: - homedir = os.path.realpath(userdir) - else: - homedir = None -else: - homedir = os.path.realpath(homedir) - -if os.path.exists(homedir) is False: - homedir = None -else: - if os.path.isdir(homedir) is False: - homedir = None - else: - pass - -if homedir is not None: - 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{verbatim} - -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 (\texttt{.gpg}) and ASCII armoured -(\texttt{.asc}) files. - -\begin{verbatim} -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 len(homedir) == 0: - homedir = None -elif homedir.startswith("~"): - userdir = os.path.expanduser(homedir) - if os.path.exists(userdir) is True: - homedir = os.path.realpath(userdir) - else: - homedir = None -else: - homedir = os.path.realpath(homedir) - -if os.path.exists(homedir) is False: - homedir = None -else: - if os.path.isdir(homedir) is False: - homedir = None - else: - pass - -if homedir is not None: - 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: - try: - hd = subprocess.getoutput(gpgconfcmd) - except: - process = subprocess.Popen(gpgconfcmd.split(), - stdout=subprocess.PIPE) - procom = process.communicate() - if sys.version_info[0] == 2: - hd = procom[0].strip() - else: - hd = procom[0].decode().strip() - 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{verbatim} - - -\subsubsection{Sending public keys to the SKS Keyservers} -\label{sec:orgffa4d7d} -As with the previous section on importing keys, the \texttt{hkp4py} module -adds another option with exporting keys in order to send them to the -public keyservers. - -The following example demonstrates how this may be done. - -\begin{verbatim} -import gpg -import hkp4py -import os.path -import sys - -print(""" -This script sends one or more public keys to the SKS keyservers and is -essentially a slight variation on the export-key.py script. -""") - -c = gpg.Context(armor=True) -server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") - -if len(sys.argv) > 2: - logrus = " ".join(sys.argv[1:]) -elif len(sys.argv) == 2: - logrus = sys.argv[1] -else: - logrus = input("Enter the UID matching the key(s) to send: ") - -if len(logrus) > 0: - try: - export_result = c.key_export(pattern=logrus) - except Exception as e: - print(e) - export_result = None -else: - export_result = c.key_export(pattern=None) - -if export_result is not None: - try: - try: - send_result = server.add(export_result) - except: - send_result = server.add(export_result.decode()) - if send_result is not None: - print(send_result) - else: - pass - except Exception as e: - print(e) -else: - pass -\end{verbatim} - -An expanded version of this script with additional functions for -specifying an alternative homedir location is in the examples -directory as \texttt{send-key-to-keyserver.py}. - -The \texttt{hkp4py} module appears to handle both string and byte literal text -data equally well, but the GPGME bindings deal primarily with byte -literal data only and so this script sends in that format first, then -tries the string literal form. - - -\section{Basic Functions} -\label{sec:org777ef00} -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. - - -\subsection{Encryption} -\label{sec:org793824e} -Encrypting is very straight forward. In the first example below the -message, \texttt{text}, is encrypted to a single recipient's key. In the -second example the message will be encrypted to multiple recipients. - - -\subsubsection{Encrypting to one key} -\label{sec:org3aede2e} -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 \texttt{gpg.Context().encrypt()} method. - -Those keyword arguments are: \texttt{recipients}, a list of keys encrypted to -(covered in greater detail in the following section); \texttt{sign}, whether -or not to sign the plaintext data, see subsequent sections on signing -and verifying signatures below (defaults to \texttt{True}); \texttt{sink}, to write -results or partial results to a secure sink instead of returning it -(defaults to \texttt{None}); \texttt{passphrase}, only used when utilising symmetric -encryption (defaults to \texttt{None}); \texttt{always\_trust}, used to override the -trust model settings for recipient keys (defaults to \texttt{False}); -\texttt{add\_encrypt\_to}, utilises any preconfigured \texttt{encrypt-to} or -\texttt{default-key} settings in the user's \texttt{gpg.conf} file (defaults to -\texttt{False}); \texttt{prepare}, prepare for encryption (defaults to \texttt{False}); -\texttt{expect\_sign}, prepare for signing (defaults to \texttt{False}); \texttt{compress}, -compresses the plaintext prior to encryption (defaults to \texttt{True}). - -\begin{verbatim} -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{verbatim} - -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 \texttt{gpg.conf} file: - -\begin{verbatim} -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{verbatim} - -If the \texttt{recipients} parameter is empty then the plaintext is encrypted -symmetrically. If no \texttt{passphrase} is supplied as a parameter or via a -callback registered with the \texttt{Context()} then an out-of-band prompt -for the passphrase via pinentry will be invoked. - - -\subsubsection{Encrypting to multiple keys} -\label{sec:org076362a} -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 (\texttt{text}) to everyone with an -email address on the \texttt{gnupg.org} domain,\footnote{You probably don't really want to do this. Searching the -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.} but does \emph{not} encrypt -to a default key or other key which is configured to normally encrypt -to. - -\begin{verbatim} -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{verbatim} - -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 \texttt{c.encrypt} line to this: - -\begin{verbatim} -ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, - always_trust=True, - add_encrypt_to=True) -\end{verbatim} - -The only keyword arguments requiring modification are those for which -the default values are changing. The default value of \texttt{sign} is -\texttt{True}, the default of \texttt{always\_trust} is \texttt{False}, the default of -\texttt{add\_encrypt\_to} is \texttt{False}. - -If \texttt{always\_trust} is not set to \texttt{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{verbatim} -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{verbatim} - -This will attempt to encrypt to all the keys searched for, then remove -invalid recipients if it fails and try again. - - -\subsection{Decryption} -\label{sec:orgabfeed8} -Decrypting something encrypted to a key in one's secret keyring is -fairly straight forward. - -In this example code, however, preconfiguring either \texttt{gpg.Context()} -or \texttt{gpg.core.Context()} as \texttt{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 \texttt{c} simply adds lines for no -gain. - -\begin{verbatim} -import gpg - -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) - -if plaintext is not None: - with open(newfile, "wb") as nfile: - nfile.write(plaintext) - else: - pass -\end{verbatim} - -The data available in \texttt{plaintext} in this example is the decrypted -content as a byte object, the recipient key IDs and algorithms in -\texttt{result} and the results of verifying any signatures of the data in -\texttt{verify\_result}. - -If \texttt{gpg.Context().decrypt(cfile, verify=False)} is called instead, -then \texttt{verify\_result} will be returned as \texttt{None} and the rest remains -as described here. - - -\subsection{Signing text and files} -\label{sec:orge3099cc} -The following sections demonstrate how to specify keys to sign with. - - -\subsubsection{Signing key selection} -\label{sec:org98a49d4} -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{verbatim} -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{verbatim} - -The signing examples in the following sections include the explicitly -designated \texttt{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. - - -\subsubsection{Normal or default signing messages or files} -\label{sec:orgde09e64} -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 \texttt{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. - -\begin{verbatim} -import gpg - -text0 = """Declaration of ... something. - -""" -text = text0.encode() - -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{verbatim} - -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{verbatim} -import gpg - -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) - -with open("/path/to/statement.txt.sig", "wb") as afile: - afile.write(signed_data) -\end{verbatim} - - -\subsubsection{Detached signing messages and files} -\label{sec:orgbcaa288} -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{verbatim} -import gpg - -text0 = """Declaration of ... something. - -""" -text = text0.encode() - -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{verbatim} - -As with normal signatures, detached signatures are best handled as -byte literals, even when the output is ASCII armoured. - -\begin{verbatim} -import gpg - -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) - -with open("/path/to/statement.txt.sig", "wb") as afile: - afile.write(signed_data) -\end{verbatim} - - -\subsubsection{Clearsigning messages or text} -\label{sec:orga3ad66e} -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{verbatim} -import gpg - -text0 = """Declaration of ... something. - -""" -text = text0.encode() - -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{verbatim} - -In spite of the appearance of a clear-signed message, the data handled -by GPGME in signing it must still be byte literals. - -\begin{verbatim} -import gpg - -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) - -with open("/path/to/statement.txt.asc", "wb") as afile: - afile.write(signed_data) -\end{verbatim} - - -\subsection{Signature verification} -\label{sec:orgdc5ac08} -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{verbatim} -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{verbatim} - -Whereas this next example, which is almost identical would work with -normal ASCII armoured files and with clear-signed files: - -\begin{verbatim} -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{verbatim} - -In both of the previous examples it is also possible to compare the -original data that was signed against the signed data in \texttt{data} to see -if it matches with something like this: - -\begin{verbatim} -with open(filename, "rb") as afile: - text = afile.read() - -if text == data: - print("Good signature.") -else: - pass -\end{verbatim} - -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 \texttt{c.verify}. So \texttt{data} is \texttt{None} and only the information -in \texttt{result} is available. - -\begin{verbatim} -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{verbatim} - -\begin{verbatim} -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{verbatim} - - -\section{Creating keys and subkeys} -\label{sec:orgddce733} -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 \texttt{SECRET} level -clearance, so his keys will be 3072-bit keys. - -The pre-configured \texttt{gpg.conf} file which sets cipher, digest and other -preferences contains the following configuration parameters: - -\begin{verbatim} -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{verbatim} - - -\subsection{Primary key} -\label{sec:org8aad39f} -Generating a primary key uses the \texttt{create\_key} method in a Context. -It contains multiple arguments and keyword arguments, including: -\texttt{userid}, \texttt{algorithm}, \texttt{expires\_in}, \texttt{expires}, \texttt{sign}, \texttt{encrypt}, -\texttt{certify}, \texttt{authenticate}, \texttt{passphrase} and \texttt{force}. The defaults for -all of those except \texttt{userid}, \texttt{algorithm}, \texttt{expires\_in}, \texttt{expires} and -\texttt{passphrase} is \texttt{False}. The defaults for \texttt{algorithm} and -\texttt{passphrase} is \texttt{None}. The default for \texttt{expires\_in} is \texttt{0}. The -default for \texttt{expires} is \texttt{True}. There is no default for \texttt{userid}. - -If \texttt{passphrase} is left as \texttt{None} then the key will not be generated -with a passphrase, if \texttt{passphrase} is set to a string then that will -be the passphrase and if \texttt{passphrase} is set to \texttt{True} then gpg-agent -will launch pinentry to prompt for a passphrase. For the sake of -convenience, these examples will keep \texttt{passphrase} set to \texttt{None}. - -\begin{verbatim} -import gpg - -c = gpg.Context() - -c.home_dir = "~/.gnupg-dm" -userid = "Danger Mouse <dm@secret.example.net>" - -dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, - sign=True, certify=True) -\end{verbatim} - -One thing to note here is the use of setting the \texttt{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, \texttt{\textasciitilde{}/.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 \texttt{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 -\texttt{GenkeyResult} object, which includes the following data: - -\begin{verbatim} -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{verbatim} - -Alternatively the information can be confirmed using the command line -program: - -\begin{verbatim} -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 <dm@secret.example.net> - -bash-4.4$ -\end{verbatim} - -As with generating keys manually, to preconfigure expanded preferences -for the cipher, digest and compression algorithms, the \texttt{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 \texttt{gpg.conf} -file in order to be able to generate this: - -\begin{verbatim} -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 <dm@secret.example.net> - -[ultimate] (1). Danger Mouse <dm@secret.example.net> - 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{verbatim} - - -\subsection{Subkeys} -\label{sec:org1686912} -Adding subkeys to a primary key is fairly similar to creating the -primary key with the \texttt{create\_subkey} method. Most of the arguments -are the same, but not quite all. Instead of the \texttt{userid} argument -there is now a \texttt{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{verbatim} -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{verbatim} - -As with the primary key, the results here can be checked with: - -\begin{verbatim} -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{verbatim} - -As well as on the command line with: - -\begin{verbatim} -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 <dm@secret.example.net> -ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] - -bash-4.4$ -\end{verbatim} - - -\subsection{User IDs} -\label{sec:org2786b54} -\subsubsection{Adding User IDs} -\label{sec:org35fc1e8} -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 -\texttt{key\_add\_uid} and the only arguments it takes are for the \texttt{key} and -the new \texttt{uid}. - -\begin{verbatim} -import gpg - -c = gpg.Context() -c.home_dir = "~/.gnupg-dm" - -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -key = c.get_key(dmfpr, secret=True) -uid = "Danger Mouse <danger.mouse@secret.example.net>" - -c.key_add_uid(key, uid) -\end{verbatim} - -Unsurprisingly the result of this is: - -\begin{verbatim} -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 <danger.mouse@secret.example.net> -uid [ultimate] Danger Mouse <dm@secret.example.net> -ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] - -bash-4.4$ -\end{verbatim} - - -\subsubsection{Revoking User IDs} -\label{sec:org45e3e8f} -Revoking a user ID is a fairly similar process, except that it uses -the \texttt{key\_revoke\_uid} method. - -\begin{verbatim} -import gpg - -c = gpg.Context() -c.home_dir = "~/.gnupg-dm" - -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -key = c.get_key(dmfpr, secret=True) -uid = "Danger Mouse <danger.mouse@secret.example.net>" - -c.key_revoke_uid(key, uid) -\end{verbatim} - - -\subsection{Key certification} -\label{sec:org14fd1c0} -Since key certification is more frequently referred to as key signing, -the method used to perform this function is \texttt{key\_sign}. - -The \texttt{key\_sign} method takes four arguments: \texttt{key}, \texttt{uids}, -\texttt{expires\_in} and \texttt{local}. The default value of \texttt{uids} is \texttt{None} and -which results in all user IDs being selected. The default value of -both \texttt{expires\_in} and \texttt{local} is \texttt{False}; which results in the -signature never expiring and being able to be exported. - -The \texttt{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 \texttt{uids} value is not \texttt{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: - -\begin{verbatim} -import gpg - -c = gpg.Context() -uid = "Danger Mouse <dm@secret.example.net>" - -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -key = c.get_key(dmfpr, secret=True) -c.key_sign(key, uids=uid, expires_in=2764800) -\end{verbatim} - - -\subsubsection{Verifying key certifications} -\label{sec:org1ab2f58} -\begin{verbatim} -import gpg -import time - -c = gpg.Context() -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -keys = list(c.keylist(pattern=dmuid, mode=gpg.constants.keylist.mode.SIGS)) -key = keys[0] - -for user in key.uids: - for sig in user.signatures: - print("0x{0}".format(sig.keyid), "", time.ctime(sig.timestamp), "", - sig.uid) -\end{verbatim} - -Which for Danger Mouse displays the following: - -\begin{verbatim} -0x92E3F6115435C65A Thu Mar 15 13:17:44 2018 Danger Mouse <dm@secret.example.net> -0x321E4E2373590E5D Mon Nov 26 12:46:05 2018 Ben McGinnes <ben@adversary.org> -\end{verbatim} - -The two key signatures listed are for the self-certification of Danger -Mouse's key made when the key was created in March, 2018; and the -second is a signature made by the author and set to expire at the end -of the year. Note that the second signature was made with the -following code (including the preceding code to display the output of -the certifications or key signatures): - -\begin{verbatim} -import gpg -import math -import pendulum -import time - -hd = "/home/dm/.gnupg" -c = gpg.Context() -d = gpg.Context(home_dir=hd) -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -dmuid = "Danger Mouse <dm@secret.example.net>" -dkeys = list(c.keylist(pattern=dmuid)) -dmkey = dkeys[0] - -c.key_import(d.key_export(pattern=None)) - -tp = pendulum.period(pendulum.now(tz="local"), pendulum.datetime(2019, 1, 1)) -ts = tp.total_seconds() -total_secs = math.ceil(ts) -c.key_sign(dmkey, uids=dmuid, expires_in=total_secs) - -d.key_import(c.key_export(pattern=dmuid)) -keys = list(c.keylist(pattern=dmuid, mode=gpg.constants.keylist.mode.SIGS)) -key = keys[0] - -for user in key.uids: - for sig in user.signatures: - print("0x{0}".format(sig.keyid), "", time.ctime(sig.timestamp), "", - sig.uid) -\end{verbatim} - -Note that this final code block includes the use of a module which is -\emph{not} part of Python's standard library, the \href{https://pendulum.eustace.io/}{pendulum module}. Unlike -the standard datetime module, pendulum makes working with dates and -times significantly easier in Python; just as the requests module -makes working with HTTP and HTTPS easier than the builtin modules do. - -Though neither requests nor pendulum are required modules for using -the GPGME Python bindings, they are both highly recommended more -generally. - - -\section{Advanced or Experimental Use Cases} -\label{sec:org261e415} -\subsection{C plus Python plus SWIG plus Cython} -\label{sec:orgeda6cec} -In spite of the apparent incongruence of using Python bindings to a C -interface only to generate more C from the Python; it is in fact quite -possible to use the GPGME bindings with \href{http://docs.cython.org/en/latest/index.html}{Cython}. Though in many cases -the benefits may not be obvious since the most computationally -intensive work never leaves the level of the C code with which GPGME -itself is interacting with. - -Nevertheless, there are some situations where the benefits are -demonstrable. One of the better and easier examples being the one of -the early examples in this HOWTO, the \hyperref[sec:orga4f8d07]{key counting} code. Running that -example as an executable Python script, \texttt{keycount.py} (available in -the \texttt{examples/howto/} directory), will take a noticeable amount of time -to run on most systems where the public keybox or keyring contains a -few thousand public keys. - -Earlier in the evening, prior to starting this section, I ran that -script on my laptop; as I tend to do periodically and timed it using -\texttt{time} utility, with the following results: - -\begin{verbatim} -bash-4.4$ time keycount.py - -Number of secret keys: 23 -Number of public keys: 12112 - - -real 11m52.945s -user 0m0.913s -sys 0m0.752s - -bash-4.4$ -\end{verbatim} - -Sometime after that I imported another key and followed it with a -little test of Cython. This test was kept fairly basic, essentially -lifting the material from the \href{http://docs.cython.org/en/latest/src/tutorial/cython\_tutorial.html}{Cython Basic Tutorial} to demonstrate -compiling Python code to C. The first step was to take the example -key counting code quoted previously, essentially from the importing of -the \texttt{gpg} module to the end of the script: - -\begin{verbatim} -import gpg - -c = gpg.Context() -seckeys = c.keylist(pattern=None, secret=True) -pubkeys = c.keylist(pattern=None, secret=False) - -seclist = list(seckeys) -secnum = len(seclist) - -publist = list(pubkeys) -pubnum = len(publist) - -print(""" - Number of secret keys: {0} - Number of public keys: {1} - -""".format(secnum, pubnum)) -\end{verbatim} - -Save that into a file called \texttt{keycount.pyx} and then create a -\texttt{setup.py} file which contains this: - -\begin{verbatim} -from distutils.core import setup -from Cython.Build import cythonize - -setup( - ext_modules = cythonize("keycount.pyx") -) -\end{verbatim} - -Compile it: - -\begin{verbatim} -bash-4.4$ python setup.py build_ext --inplace -bash-4.4$ -\end{verbatim} - -Then run it in a similar manner to \texttt{keycount.py}: - -\begin{verbatim} -bash-4.4$ time python3.7 -c "import keycount" - -Number of secret keys: 23 -Number of public keys: 12113 - - -real 6m47.905s -user 0m0.785s -sys 0m0.331s - -bash-4.4$ -\end{verbatim} - -Cython turned \texttt{keycount.pyx} into an 81KB \texttt{keycount.o} file in the -\texttt{build/} directory, a 24KB \texttt{keycount.cpython-37m-darwin.so} file to be -imported into Python 3.7 and a 113KB \texttt{keycount.c} generated C source -code file of nearly three thousand lines. Quite a bit bigger than the -314 bytes of the \texttt{keycount.pyx} file or the full 1,452 bytes of the -full executable \texttt{keycount.py} example script. - -On the other hand it ran in nearly half the time; taking 6 minutes and -47.905 seconds to run. As opposed to the 11 minutes and 52.945 seconds -which the CPython script alone took. - -The \texttt{keycount.pyx} and \texttt{setup.py} files used to generate this example -have been added to the \texttt{examples/howto/advanced/cython/} directory -The example versions include some additional options to annotate the -existing code and to detect Cython's use. The latter comes from the -\href{http://docs.cython.org/en/latest/src/tutorial/pure.html\#magic-attributes-within-the-pxd}{Magic Attributes} section of the Cython documentation. - - -\section{Miscellaneous extras and work-arounds} -\label{sec:orgc2820d3} -Most of the things in the following sections are here simply because -there was no better place to put them, even though some are only -peripherally related to the GPGME Python bindings. Some are also -workarounds for functions not integrated with GPGME as yet. This is -especially true of the first of these, dealing with \hyperref[sec:org8ea6368]{group lines}. - - -\subsection{Group lines} -\label{sec:org8ea6368} -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. - -\begin{verbatim} -import subprocess -import sys - -if sys.platform == "win32": - gpgconfcmd = "gpgconf.exe --list-options gpg" -else: - gpgconfcmd = "gpgconf --list-options gpg" - -process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE) -procom = process.communicate() - -if sys.version_info[0] == 2: - lines = procom[0].splitlines() -else: - lines = procom[0].decode().splitlines() - -for line in lines: - if line.startswith("group") is True: - break - -groups = line.split(":")[-1].replace('"', '').split(',') - -group_lines = [] -group_lists = [] - -for i in range(len(groups)): - group_lines.append(groups[i].split("=")) - group_lists.append(groups[i].split("=")) - -for i in range(len(group_lists)): - group_lists[i][1] = group_lists[i][1].split() -\end{verbatim} - -The result of that code is that \texttt{group\_lines} is a list of lists where -\texttt{group\_lines[i][0]} is the name of the group and \texttt{group\_lines[i][1]} -is the key IDs of the group as a string. - -The \texttt{group\_lists} result is very similar in that it is a list of -lists. The first part, \texttt{group\_lists[i][0]} matches -\texttt{group\_lines[i][0]} as the name of the group, but \texttt{group\_lists[i][1]} -is the key IDs of the group as a list. - -A demonstration of using the \texttt{groups.py} module is also available in -the form of the executable \texttt{mutt-groups.py} script. This second -script reads all the group entries in a user's \texttt{gpg.conf} file and -converts them into crypt-hooks suitable for use with the Mutt and -Neomutt mail clients. - - -\subsection{Keyserver access for Python} -\label{sec:orgba396cc} -The \href{https://github.com/Selfnet/hkp4py}{hkp4py} module by Marcel Fest was originally a port of the old -\href{https://github.com/dgladkov/python-hkp}{python-hkp} module from Python 2 to Python 3 and updated to use the -\href{http://docs.python-requests.org/en/latest/index.html}{requests} module instead. It has since been modified to provide -support for Python 2.7 as well and is available via PyPI. - -Since it rewrites the \texttt{hkp} protocol prefix as \texttt{http} and \texttt{hkps} as -\texttt{https}, the module is able to be used even with servers which do not -support the full scope of keyserver functions.\footnote{Such as with ProtonMail servers. This also means that -restricted servers which only advertise either HTTP or HTTPS end -points and not HKP or HKPS end points must still be identified as as -HKP or HKPS within the Python Code. The \texttt{hkp4py} module will rewrite -these appropriately when the connection is made to the server.} It also works quite -readily when incorporated into a \hyperref[sec:orgeda6cec]{Cython} generated and compiled version -of any code. - - -\subsubsection{Key import format} -\label{sec:orgfa04352} -The hkp4py module returns key data via requests as string literals -(\texttt{r.text}) instead of byte literals (\texttt{r.content}). This means that -the retrurned key data must be encoded to UTF-8 when importing that -key material using a \texttt{gpg.Context().key\_import()} method. - -For this reason an alternative method has been added to the \texttt{search} -function of \texttt{hkp4py.KeyServer()} which returns the key in the correct -format as expected by \texttt{key\_import}. When importing using this module, -it is now possible to import with this: - -\begin{verbatim} -for key in keys: - if key.revoked is False: - gpg.Context().key_import(key.key_blob) - else: - pass -\end{verbatim} - -Without that recent addition it would have been necessary to encode -the contents of each \texttt{hkp4py.KeyServer().search()[i].key} in -\texttt{hkp4py.KeyServer().search()} before trying to import it. - -An example of this is included in the \hyperref[sec:org5a4abec]{Importing Keys} section of this -HOWTO and the corresponding executable version of that example is -available in the \texttt{lang/python/examples/howto} directory as normal; the -executable version is the \texttt{import-keys-hkp.py} file. - - -\subsection{GPGME version checking} -\label{sec:org64c0457} -For various reasons it may be necessary to check which version of -GPGME the bindings have been built against; including whether a -minimum required version of GPGME is in use. - -For the most part the \texttt{gpg.version.versionstr} and -\texttt{gpg.version.versionlist} methods have been quite sufficient. The -former returns the same string as \texttt{gpgme-config -{}-version}, while the -latter returns the major, minor and patch values in a list. - -To check if the installed bindings have actually been built against -the current installed libgpgme version, this check can be performed: - -\begin{verbatim} -import gpg -import subprocess -import sys - -gpgme_version_call = subprocess.Popen(["gpgme-config", "--version"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) -gpgme_version_str = gpgme_version_call.communicate() - -if sys.version_info[0] == 2: - gpgme_version = gpgme_version_str[0].strip() -elif sys.version_info[0] >= 3: - gpgme_version = gpgme_version_str[0].decode().strip() -else: - gpgme_version = None - -if gpgme_version is not None: - if gpgme_version == gpg.version.versionstr: - print("The GPGME Python bindings match libgpgme.") - else: - print("The GPGME Python bindings do NOT match libgpgme.") -else: - print("Upgrade Python and reinstall the GPGME Python bindings.") -\end{verbatim} - -For many developers, however, the preferred checking means checking -for a minimum version or point release. This is now readily available -via the \texttt{gpg.version.versionintlist} method (added in version -\texttt{1.12.1-beta79}). It is also now possible to easily check whether the -installed GPGME Python bindings were built from a development or beta -branch of the GPGME source code. - -The following code demonstrates how both of those methods may be used: - -\begin{verbatim} -import gpg - -try: - if gpg.version.is_beta is True: - print("The installed GPGME Python bindings were built from beta code.") - else: - print("The installed GPGME Python bindings are a released version.") -except Exception as e: - print(e) - -try: - if gpg.version.versionintlist[0] == 1: - if gpg.version.versionintlist[1] == 12: - if gpg.version.versionintlist[2] == 1: - print("This is the minimum version for using versionintlist.") - elif gpg.version.versionintlist[2] > 1: - print("The versionintlist method is available.") - else: - pass - elif gpg.version.versionintlist[1] > 12: - print("The versionintlist method is available.") - else: - pass - elif gpg.version.versionintlist[0] > 1: - print("The versionintlist method is available.") - else: - pass -except Exception as e: - print(e) -\end{verbatim} - -The points where \texttt{pass} is used in the above example will most likely -also produce an \texttt{Exception} error since those results should only -occur in versions which do not have the \texttt{gpgme.version.is\_beta} and -\texttt{gpgme.version.versionintlist} methods available. - - -\section{Copyright and Licensing} -\label{sec:org7fd28fa} -\subsection{Copyright} -\label{sec:orga2eb61b} -Copyright © The GnuPG Project, 2018. - -Copyright (C) The GnuPG Project, 2018. - - -\subsection{Draft Editions of this HOWTO} -\label{sec:org1cd0343} -Draft editions of this HOWTO may be periodically available directly -from the author at any of the following URLs: - -\begin{itemize} -\item \href{https://files.au.adversary.org/crypto/gpgme-python-howto.html}{GPGME Python Bindings HOWTO draft (XHTML single file, AWS S3 SSL)} -\item \href{http://files.au.adversary.org/crypto/gpgme-python-howto.html}{GPGME Python Bindings HOWTO draft (XHTML single file, AWS S3 no SSL)} -\item \href{https://files.au.adversary.org/crypto/gpgme-python-howto-split/index.html}{GPGME Python Bindings HOWTO draft (XHTML multiple files, AWS S3 SSL)} -\item \href{http://files.au.adversary.org/crypto/gpgme-python-howto/index.html}{GPGME Python Bindings HOWTO draft (XHTML multiple files, AWS S3 no SSL)} -\end{itemize} - -All of these draft versions except for one have been generated from -this document via GNU Emacs \href{https://orgmode.org/}{Org mode} and \href{https://www.gnu.org/software/texinfo/}{GNU Texinfo}. Though it is -likely that the specific \href{https://files.au.adversary.org/crypto/gpgme-python-howto}{file} \href{http://files.au.adversary.org/crypto/gpgme-python-howto.org}{version} used will be on the same server -with the generated output formats. - -The GNU Texinfo and reStructured Text versions ship with the software, -while the GNU Emacs Info verseion is generated from the Texinfo -version using GNU Texinfo or GNU Makeinfo. The Texinfo format is -generated from the original Org mode source file in Org mode itself -either within GNU Emacs or via the command line by invoking Emacs in -batch mode: - -\begin{verbatim} -emacs gpgme-python-howto.org --batch -f org-texinfo-export-to-texinfo --kill -emacs gpgme-python-howto --batch -f org-texinfo-export-to-texinfo --kill -\end{verbatim} - -The reStructuredText format is also generated from the Org-mode source -file, except it is generated using \href{https://pandoc.org}{Pandoc} with either of the following -commands: - -\begin{verbatim} -pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org -pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto -\end{verbatim} - -In addition to these there is a significantly less frequently updated -version as a HTML \href{https://files.au.adversary.org/crypto/gpgme-python/dita/webhelp/index.html}{WebHelp site} (AWS S3 SSL); generated from DITA XML -source files, which can be found in \href{https://dev.gnupg.org/source/gpgme/browse/ben\%252Fhowto-dita/}{an alternative branch} of the GPGME -git repository. - -Various generated output formats may occasionally be found in -subdirectories of the \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python}{gpgme-python} directory. In particular within -the \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/dita}{DITA}, \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/rst}{reStructuredText} and \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/texinfo}{Texinfo} subdirectories. The \texttt{rst} -directory contains output files generated with Sphix and may include a -considerable number of its possible output formats. - -These draft editions are not official documents and the version of -documentation in the master branch or which ships with released -versions is the only official documentation. Nevertheless, these -draft editions may occasionally be of use by providing more accessible -web versions which are updated between releases. They are provided on -the understanding that they may contain errors or may contain content -subject to change prior to an official release. - - -\subsection{License GPL compatible} -\label{sec:org071f98e} -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. -\end{document} diff --git a/lang/python/doc/src/gpgme-python-howto.tex~ b/lang/python/doc/src/gpgme-python-howto.tex~ deleted file mode 100644 index ca89a4c..0000000 --- a/lang/python/doc/src/gpgme-python-howto.tex~ +++ /dev/null @@ -1,3110 +0,0 @@ -% Created 2019-01-18 Fri 10:10 -% Intended LaTeX compiler: xelatex -\documentclass[12pt]{article} -\usepackage{graphicx} -\usepackage{grffile} -\usepackage{longtable} -\usepackage{wrapfig} -\usepackage{rotating} -\usepackage[normalem]{ulem} -\usepackage{amsmath} -\usepackage{textcomp} -\usepackage{amssymb} -\usepackage{capt-of} -\usepackage{hyperref} -\usepackage{xltxtra} -\usepackage[margin=1in]{geometry} -\setmainfont[Ligatures={Common}]{Times New Roman} -\author{Ben McGinnes <ben@gnupg.org>} -\author{Ben McGinnes} -\date{\today} -\title{GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)} -\hypersetup{ - pdfauthor={Ben McGinnes}, - pdftitle={GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)}, - pdfkeywords={}, - pdfsubject={}, - pdfcreator={Emacs 26.1 (Org mode 9.1.14)}, - pdflang={English}} -\begin{document} - -\maketitle -\tableofcontents - - - -\section{Introduction} -\label{sec:org8e5fa02} -\begin{center} -\begin{tabular}{ll} -Version: & 0.1.4\\ -GPGME Version: & 1.12.1\\ -Author: & Ben McGinnes <ben@gnupg.org>\\ -Author GPG Key: & DB4724E6FA4286C92B4E55C4321E4E2373590E5D\\ -Language: & Australian English, British English\\ -Language codes: & en-AU, en-GB, en\\ -\end{tabular} -\end{center} - -This document provides basic instruction in how to use the GPGME -Python bindings to programmatically leverage the GPGME library. - - -\subsection{Python 2 versus Python 3} -\label{sec:org517cbf4} -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. - -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. - - -\subsection{Examples} -\label{sec:org2c08d53} -All of the examples found in this document can be found as Python 3 -scripts in the \texttt{lang/python/examples/howto} directory. - - -\subsection{Unofficial Drafts} -\label{sec:orgceb2216} -In addition to shipping with each release of GPGME, there is a section -on locations to read or download \hyperref[sec:org080a94a]{draft editions} of this document from -at the end of it. These are unofficial versions produced in between -major releases. - - -\subsection{What's New} -\label{sec:orga79ddc2} -Full details of what is new are now available in the \href{what-is-new.org}{What's New} file -and archives of the preceding \emph{What's New} sections are available in -the \href{what-was-new}{What Was New} file. - - -\subsubsection{New in GPGME 1·13·0} -\label{sec:org746b75d} -See the \href{what-is-new\#new-stuff-1-13-0}{What's New} document for what is new in version 1.13.0. - - -\subsubsection{New in GPGME 1·12·0} -\label{sec:org7adcade} -See the \href{what-was-new\#new-stuff-1-12-0}{What Was New} document for what was new in version 1.12.0. - - -\section{GPGME Concepts} -\label{sec:orgb257fc1} -\subsection{A C API} -\label{sec:org4fe1dfa} -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 \texttt{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. - - -\subsection{Python bindings} -\label{sec:org0e49e52} -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 -\texttt{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 -\texttt{gpgme.h}. - - -\subsection{Difference between the Python bindings and other GnuPG Python packages} -\label{sec:org97acfd7} -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. - - -\subsubsection{The python-gnupg package maintained by Vinay Sajip} -\label{sec:org0136847} -This is arguably the most popular means of integrating GPG with -Python. The package utilises the \texttt{subprocess} module to implement -wrappers for the \texttt{gpg} and \texttt{gpg2} executables normally invoked on the -command line (\texttt{gpg.exe} and \texttt{gpg2.exe} on Windows). - -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 \texttt{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. - - -\subsubsection{The gnupg package created and maintained by Isis Lovecruft} -\label{sec:orgdc03987} -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 \texttt{gpg} or \texttt{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 gnupg package is available under the GNU General Public License -version 3.0 (or any later version). - - -\subsubsection{The PyME package maintained by Martin Albrecht} -\label{sec:org715600b} -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 \href{short-history.org}{Short History} document.\footnote{\texttt{short-history} and/or \texttt{short-history.html}.} - -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 -\texttt{python-gnupg} or \texttt{gnupg} packages. - -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. - -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). - - -\section{GPGME Python bindings installation} -\label{sec:org73d8d6f} -\subsection{No PyPI} -\label{sec:orgec9a038} -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. - -This is because the bindings use SWIG to dynamically generate C -bindings against \texttt{gpgme.h} and \texttt{gpgme.h} is generated from -\texttt{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. - -See the additional notes regarding \hyperref[sec:org645735a]{CFFI and SWIG} at the end of this -section for further details. - - -\subsection{Requirements} -\label{sec:org2f7f06c} -The GPGME Python bindings only have three requirements: - -\begin{enumerate} -\item A suitable version of Python 2 or Python 3. With Python 2 that -means CPython 2.7 and with Python 3 that means CPython 3.4 or -higher. -\item \href{https://www.swig.org}{SWIG}. -\item GPGME itself. Which also means that all of GPGME's dependencies -must be installed too. -\end{enumerate} - - -\subsubsection{Recommended Additions} -\label{sec:org25bd7d4} -Though none of the following are absolute requirements, they are all -recommended for use with the Python bindings. In some cases these -recommendations refer to which version(s) of CPython to use the -bindings with, while others refer to third party modules which provide -a significant advantage in some way. - -\begin{enumerate} -\item If possible, use Python 3 instead of 2. -\item Favour a more recent version of Python since even 3.4 is due to -reach EOL soon. In production systems and services, Python 3.6 -should be robust enough to be relied on. -\item If possible add the following Python modules which are not part of -the standard library: \href{http://docs.python-requests.org/en/latest/index.html}{Requests}, \href{https://cython.org/}{Cython}, \href{https://pendulum.eustace.io/}{Pendulum} and \href{https://github.com/Selfnet/hkp4py}{hkp4py}. -\end{enumerate} - -Chances are quite high that at least the first one and maybe two of -those will already be installed. - -Note that, as with Cython, some of advanced use case scenarios will -bring with them additional requirements. Most of these will be fairly -well known and commonly installed ones, however, which are in many -cases likely to have already been installed on many systems or be -familiar to Python programmers. - - -\subsection{Installation} -\label{sec:orgf1c7587} -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 \texttt{\$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 \texttt{\$PATH}. It specifically checks for the \texttt{python} and \texttt{python3} -executables first and then checks for specific version numbers. - -For Python 2 it checks for these executables in this order: \texttt{python}, -\texttt{python2} and \texttt{python2.7}. - -For Python 3 it checks for these executables in this order: \texttt{python3}, - \texttt{python3.7}, \texttt{python3.6}, \texttt{python3.5} and \texttt{python3.4}.\footnote{With no issues reported specific to Python 3.7, the release of -Python 3.7.1 at around the same time as GPGME 1.12.0 and the testing -with Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of -3.6 now. Production environments with more conservative requirements -will always enforce their own policies anyway and installation to each -supported minor release is quite possible too.} - -On systems where \texttt{python} is actually \texttt{python3} and not \texttt{python2} it -may be possible that \texttt{python2} may be overlooked, but there have been -no reports of that actually occurring as yet. - -In the three months or so since the release of Python 3.7.0 there has -been extensive testing and work with these bindings with no issues -specifically relating to the new version of Python or any of the new -features of either the language or the bindings. This has also been -the case with Python 3.7.1rc1. With that in mind and given the -release of Python 3.7.1 is scheduled for around the same time as GPGME -1.12.0, the order of preferred Python versions has been changed to -move Python 3.7 ahead of Python 3.6. - - -\subsubsection{Installing GPGME} -\label{sec:org903bd67} -See the GPGME \texttt{README} file for details of how to install GPGME from -source. - - -\subsection{Known Issues} -\label{sec:org0d2a332} -There are a few known issues with the current build process and the -Python bindings. For the most part these are easily addressed should -they be encountered. - - -\subsubsection{Breaking Builds} -\label{sec:orgbac75f3} -Occasionally when installing GPGME with the Python bindings included -it may be observed that the \texttt{make} portion of that process induces a -large very number of warnings and, eventually errors which end that -part of the build process. Yet following that with \texttt{make check} and -\texttt{make install} appears to work seamlessly. - -The cause of this is related to the way SWIG needs to be called to -dynamically generate the C bindings for GPGME in the first place. So -the entire process will always produce \texttt{lang/python/python2-gpg/} and -\texttt{lang/python/python3-gpg/} directories. These should contain the -build output generated during compilation, including the complete -bindings and module installed into \texttt{site-packages}. - -Occasionally the errors in the early part or some other conflict -(e.g. not installing as \textbf{\emph{root}} or \textbf{\emph{su}}) may result in nothing -being installed to the relevant \texttt{site-packages} directory and the -build directory missing a lot of expected files. Even when this -occurs, the solution is actually quite simple and will always work. - -That solution is simply to run the following commands as either the -\textbf{root} user or prepended with \texttt{sudo -H}\footnote{Yes, even if you use virtualenv with everything you do in -Python. If you want to install this module as just your user account -then you will need to manually configure, compile and install the -\emph{entire} GnuPG stack as that user as well. This includes libraries -which are not often installed that way. It can be done and there are -circumstances under which it is worthwhile, but generally only on -POSIX systems which utilise single user mode (some even require it).} in the \texttt{lang/python/} -directory: - -\begin{verbatim} -/path/to/pythonX.Y setup.py build -/path/to/pythonX.Y setup.py build -/path/to/pythonX.Y setup.py install -\end{verbatim} - -Yes, the build command does need to be run twice. Yes, you still need -to run the potentially failing or incomplete steps during the -\texttt{configure}, \texttt{make} and \texttt{make install} steps with installing GPGME. -This is because those steps generate a lot of essential files needed, -both by and in order to create, the bindings (including both the -\texttt{setup.py} and \texttt{gpgme.h} files). - - -\begin{enumerate} -\item IMPORTANT Note -\label{sec:org37f3d4a} -If specifying a selected number of languages to create bindings for, -try to leave Python last. Currently the majority of the other -language bindings are also preceding Python of either version when -listed alphabetically (not counting the Qt bindings). - -If Python is set to precede one of the other languages then it is -possible that the errors described here may interrupt the build -process before generating bindings for those other languages. In -these cases it may be preferable to configure all preferred language -bindings separately with alternative \texttt{configure} steps for GPGME using -the \texttt{-{}-enable-languages=\$LANGUAGE} option. - -Alternatively \texttt{make} (or \texttt{gmake}, depending on your platform) may be -run with the the \texttt{-k} option, which tells make to keep going even if -errors are encountered. In that case the failure of one language's -set of bindings to build should not hamper another language's bindings -to build. -\end{enumerate} - - -\subsubsection{Reinstalling Responsibly} -\label{sec:org21e4dec} -Regardless of whether you're installing for one version of Python or -several, there will come a point where reinstallation is required. -With most Python module installations, the installed files go into the -relevant site-packages directory and are then forgotten about. Then -the module is upgraded, the new files are copied over the old and -that's the end of the matter. - -While the same is true of these bindings, there have been intermittent -issues observed on some platforms which have benefited significantly -from removing all the previous installations of the bindings before -installing the updated versions. - -Removing the previous version(s) is simply a matter of changing to the -relevant \texttt{site-packages} directory for the version of Python in -question and removing the \texttt{gpg/} directory and any accompanying -egg-info files for that module. - -In most cases this will require root or administration privileges on -the system, but the same is true of installing the module in the first -place. - - -\subsubsection{Multiple installations} -\label{sec:org30b18bd} -For a variety of reasons it may be either necessary or just preferable -to install the bindings to alternative installed Python versions which -meet the requirements of these bindings. - -On POSIX systems this will generally be most simply achieved by -running the manual installation commands (build, build, install) as -described in the previous section for each Python installation the -bindings need to be installed to. - -As per the SWIG documentation: the compilers, libraries and runtime -used to build GPGME and the Python Bindings \textbf{must} match those used to -compile Python itself, including the version number(s) (at least going -by major version numbers and probably minor numbers too). - -On most POSIX systems, including OS X, this will very likely be the -case in most, if not all, cases. - -Note that from GPGME \href{https://dev.gnupg.org/rMff6ff616aea6f59b7f2ce1176492850ecdf3851e}{1.12.1} the default installation installs to each -version of Python it can find first. That is that it will currently -install for the first copies of Python versions 2.7, 3.4, 3.5, 3.6, -3.7 and 3.8 (dev branch) that it finds. Usually this will be in the -same prefix as GPGME itself, but is dictated by the \texttt{\$PATH} when the -installation is performed. The above instructions can still be -performed on other python installations which the installer does not -find, including alternative prefixes. - - - -\subsubsection{Won't Work With Windows} -\label{sec:org0ebae3a} -There are semi-regular reports of Windows users having considerable -difficulty in installing and using the Python bindings at all. Very -often, possibly even always, these reports come from Cygwin users -and/or MinGW users and/or Msys2 users. Though not all of them have -been confirmed, it appears that these reports have also come from -people who installed Python using the Windows installer files from the -\href{https://python.org}{Python website} (i.e. mostly MSI installers, sometimes self-extracting -\texttt{.exe} files). - -The Windows versions of Python are not built using Cygwin, MinGW or -Msys2; they're built using Microsoft Visual Studio. Furthermore the -version used is \emph{considerably} more advanced than the version which -MinGW obtained a small number of files from many years ago in order to -be able to compile anything at all. Not only that, but there are -changes to the version of Visual Studio between some micro releases, -though that is is particularly the case with Python 2.7, since it has -been kept around far longer than it should have been. - -There are two theoretical solutions to this issue: - -\begin{enumerate} -\item Compile and install the GnuPG stack, including GPGME and the -Python bindings using the same version of Microsoft Visual Studio -used by the Python Foundation to compile the version of Python -installed. - -If there are multiple versions of Python then this will need to be -done with each different version of Visual Studio used for those -versions of Python. - -\item Compile and install Python using the same tools used by choice, -such as MinGW or Msys2. -\end{enumerate} - -Do \textbf{not} use the official Windows installer for Python unless -following the first method. - -In this type of situation it may even be for the best to accept that -there are less limitations on permissive software than free software -and simply opt to use a recent version of the Community Edition of -Microsoft Visual Studio to compile and build all of it, no matter -what. - -Investigations into the extent or the limitations of this issue are -ongoing. - -The following table lists the version of Microsoft Visual Studio which -needs to be used when compiling GPGME and the Python bindings with -each version of the CPython binary released \href{https://www.python.org/downloads/windows/}{for Windows}: - -\begin{center} -\begin{tabular}{rll} -CPython & Microsoft product name & runtime filename\\ -2.7.6 & Visual Studio 2008 & MSVCR90.DLL\\ -3.4.0 & Visual Studio 2010 & MSVCR100.DLL\\ -3.5.0 & Visual Studio 2015 & \textbf{see below}\\ -3.6.0 & Visual Studio 2015 & \textbf{see below}\\ -3.7.0 & Visual Studio 2017* & \textbf{see below}\\ -\end{tabular} -\end{center} - -It is important to note that MingW and Msys2 ship with the Visual C -runtime from Microsoft Visual Studio 2005 and are thus \textbf{incompatible} -with all the versions of CPython which can be used with the GPGME -Python bindings. - -It is also important to note that from CPython 3.5 onwards, the Python -Foundation has adopted the reworking of the Visual C runtime which was -performed for Visual Studio 2015 and aimed at resolving many of these -kinds of issues. Much greater detail on these issues and the correct -file(s) to link to are available from Matthew Brett's invaluable page, -\href{https://matthew-brett.github.io/pydagogue/python\_msvc.html}{Using Microsoft Visual C with Python}. It is also worth reading the -Microsoft Developer Network blog post on \href{http://blogs.msdn.com/b/vcblog/archive/2015/03/03/introducing-the-universal-crt.aspx}{the universal CRT} and Steve -Dower's blog posts on Python extensions (\href{http://stevedower.id.au/blog/building-for-python-3-5}{part 1} and \href{http://stevedower.id.au/blog/building-for-python-3-5-part-two}{part 2}). - -The second of those two posts by Steve Dower contains the details of -specific configuration options required for compiling anything to be -used with official CPython releases. In addition to those -configuration and compiler settings to use, the versions of Visual -Studio prior to Visual Studio 2015 did not support 64-bit systems by -default. So compiling a 64-bit version of these bindings for a 64-bit -version of CPython 2.7 or 3.4 requires additional work. - -In addition to the blog posts, the \href{https://wiki.python.org/moin/WindowsCompilers}{Windows compilers} wiki page on the -CPython wiki is another essential reference on the relevant versions -of Visual Studio to use and the degree of compatibility with CPython -releases. - -Eventually someone will ask why there isn't an installable binary for -Windows, which the GPGME of the licenses do not preclude as long as -the source code is available in conjunction with such a release. - -The sheer number of versions of Visual Studio in conjunction with -differing configuration options depending on the target Windows -version and whether the architecture is 64-bit or 32-bit makes it -difficult to provide a correct binary installer for Windows users. At -the bare minimum doing so would require the GnuPG project compile ten -different versions of the bindings with each release; both 32-bit and -64-bit versions for CPython 2.7 and 3.4, with 64-bit versions for both -x86-64 (i.e. Intel and AMD) and ARM architectures for CPython 3.5, -3.6, 3.7 and later releases. That's the bare \textbf{minimum}, it'd probably -be higher. - -Additionally, with only a binary installation used in conjunction with -the CPython installer from \texttt{python.org} the advanced options available -which utilise \hyperref[sec:org3b53926]{Cython} will not be able to be used at all. Cython -depends on being able to compile the C code it generates and that too -would need to utilise a matching runtime to both the installed version -of CPython and these bindings in order to work with the bindings. - -Considering all of that, what do we recommend? - -\begin{enumerate} -\item Use a recent version of CPython; at least 3.5, but ideally 3.6 or -later. - -\item Use Visual Studio 2015 or the standalone build tools for Visual -Studio 2017 (or later). - -\item Compile both CPython and GPGME with these bindings using the tools -selected in step 2. - -\item Ignore MingW, Msys2 and the official CPython binary installers. - -\item Be thankful the answer to this question wasn't simply to say -something like, “install Linux” or “install FreeBSD” (or even -Apple's OS X). -\end{enumerate} - - -\subsubsection{CFFI is the Best™ and GPGME should use it instead of SWIG} -\label{sec:org645735a} -There are many reasons for favouring \href{https://cffi.readthedocs.io/en/latest/overview.html}{CFFI} and proponents of it are -quite happy to repeat these things as if all it would take to switch -from SWIG to CFFI is repeating that list as if it were a new concept. - -The fact is that there are things which Python's CFFI implementation -cannot handle in the GPGME C code. Beyond that there are features of -SWIG which are simply not available with CFFI at all. SWIG generates -the bindings to Python using the \texttt{gpgme.h} file, but that file is not -a single version shipped with each release, it too is generated when -GPGME is compiled. - -CFFI is currently unable to adapt to such a potentially mutable -codebase. If there were some means of applying SWIG's dynamic code -generation to produce the Python/CFFI API modes of accessing the GPGME -libraries (or the source source code directly), but such a thing does -not exist yet either and it currently appears that work is needed in -at least one of CFFI's dependencies before any of this can be -addressed. - -So if you're a massive fan of CFFI; that's great, but if you want this -project to switch to CFFI then rather than just insisting that it -should, I'd suggest you volunteer to bring CFFI up to the level this -project needs. - -If you're actually seriously considering doing so, then I'd suggest -taking the \texttt{gpgme-tool.c} file in the GPGME \texttt{src/} directory and -getting that to work with any of the CFFI API methods (not the ABI -methods, they'll work with pretty much anything). When you start -running into trouble with "ifdefs" then you'll know what sort of -things are lacking. That doesn't even take into account the amount of -work saved via SWIG's code generation techniques either. - - -\subsubsection{Virtualised Environments} -\label{sec:orgb0d56f5} -It is fairly common practice amongst Python developers to, as much as -possible, use packages like virtualenv to keep various things that are -to be installed from interfering with each other. Given how much of -the GPGME bindings is often at odds with the usual pythonic way of -doing things, it stands to reason that this would be called into -question too. - -As it happens the answer as to whether or not the bindings can be used -with virtualenv, the answer is both yes and no. - -In general we recommend installing to the relevant path and matching -prefix of GPGME itself. Which means that when GPGME, and ideally the -rest of the GnuPG stack, is installed to a prefix like \texttt{/usr/local} or -\texttt{/opt/local} then the bindings would need to be installed to the main -Python installation and not a virtualised abstraction. Attempts to -separate the two in the past have been known to cause weird and -intermittent errors ranging from minor annoyances to complete failures -in the build process. - -As a consequence we only recommend building with and installing to the -main Python installations within the same prefix as GPGME is installed -to or which are found by GPGME's configuration stage immediately prior -to running the make commands. Which is exactly what the compiling and -installing process of GPGME does by default. - -Once that is done, however, it appears that a copy of the compiled -module may be installed into a virtualenv of the same major and minor -version matching the build. Alternatively it is possible to utilise a -\texttt{sites.pth} file in the \texttt{site-packages/} directory of a virtualenv -installation, which links back to the system installations -corresponding directory in order to import anything installed system -wide. This may or may not be appropriate on a case by case basis. - -Though extensive testing of either of these options is not yet -complete, preliminary testing of them indicates that both are viable -as long as the main installation is complete. Which means that -certain other options normally restricted to virtual environments are -also available, including integration with pythonic test suites -(e.g. \href{https://docs.pytest.org/en/latest/index.html}{pytest}) and other large projects. - -That said, it is worth reiterating the warning regarding non-standard -installations. If one were to attempt to install the bindings only to -a virtual environment without somehow also including the full GnuPG -stack (or enough of it as to include GPGME) then it is highly likely -that errors would be encountered at some point and more than a little -likely that the build process itself would break. - -If a degree of separation from the main operating system is still -required in spite of these warnings, then consider other forms of -virtualisation. Either a virtual machine (e.g. \href{https://www.virtualbox.org/}{VirtualBox}), a -hardware emulation layer (e.g. \href{https://www.qemu.org/}{QEMU}) or an application container -(e.g. \href{https://www.docker.com/why-docker}{Docker}). - -Finally it should be noted that the limited tests conducted thus far -have been using the \texttt{virtualenv} command in a new directory to create -the virtual python environment. As opposed to the standard \texttt{python3 --m venv} and it is possible that this will make a difference depending -on the system and version of Python in use. Another option is to run -the command \texttt{python3 -m virtualenv /path/to/install/virtual/thingy} -instead. - - -\section{Fundamentals} -\label{sec:org832642e} -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. - - -\subsection{No REST} -\label{sec:org3d694ba} -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 \emph{\textbf{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. - -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 that this HOWTO deals with. - - -\subsection{Context} -\label{sec:org10e4004} -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. - -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. - - -\section{Working with keys} -\label{sec:orgff114ca} -\subsection{Key selection} -\label{sec:org6589217} -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. - -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: - -\begin{verbatim} -import gpg - -k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") -keys = list(k) -\end{verbatim} - -This is passable and very likely to be common: - -\begin{verbatim} -import gpg - -k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") -keys = list(k) -\end{verbatim} - -And this is a really bad idea: - -\begin{verbatim} -import gpg - -k = gpg.Context().keylist(pattern="0xDEADBEEF") -keys = list(k) -\end{verbatim} - -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{verbatim} -import gpg - -ncsc = gpg.Context().keylist(pattern="ncsc.mil") -nsa = list(ncsc) -\end{verbatim} - - -\subsubsection{Counting keys} -\label{sec:org63a3d62} -Counting the number of keys in your public keybox (\texttt{pubring.kbx}), the -format which has superseded the old keyring format (\texttt{pubring.gpg} and -\texttt{secring.gpg}), or the number of secret keys is a very simple task. - -\begin{verbatim} -import gpg - -c = gpg.Context() -seckeys = c.keylist(pattern=None, secret=True) -pubkeys = c.keylist(pattern=None, secret=False) - -seclist = list(seckeys) -secnum = len(seclist) - -publist = list(pubkeys) -pubnum = len(publist) - -print(""" - Number of secret keys: {0} - Number of public keys: {1} -""".format(secnum, pubnum)) -\end{verbatim} - -NOTE: The \hyperref[sec:org3b53926]{Cython} introduction in the \hyperref[sec:org944cc00]{Advanced and Experimental} -section uses this same key counting code with Cython to demonstrate -some areas where Cython can improve performance even with the -bindings. Users with large public keyrings or keyboxes, for instance, -should consider these options if they are comfortable with using -Cython. - - -\subsection{Get key} -\label{sec:org26adb2e} -An alternative method of getting a single key via its fingerprint is -available directly within a Context with \texttt{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. - -This first example demonstrates selecting the current key of Werner -Koch, which is due to expire at the end of 2018: - -\begin{verbatim} -import gpg - -fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" -key = gpg.Context().get_key(fingerprint) -\end{verbatim} - -Whereas this example demonstrates selecting the author's current key -with the \texttt{secret} key word argument set to \texttt{True}: - -\begin{verbatim} -import gpg - -fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" -key = gpg.Context().get_key(fingerprint, secret=True) -\end{verbatim} - -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. - - -\subsection{Importing keys} -\label{sec:org1ad598a} -Importing keys is possible with the \texttt{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{verbatim} -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{verbatim} - -NOTE: When searching for a key ID of any length or a fingerprint -(without spaces), the SKS servers require the the leading \texttt{0x} -indicative of hexadecimal be included. Also note that the old short -key IDs (e.g. \texttt{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). - -Testing for whether a string in any given search is or may be a -hexadecimal value which may be missing the leading \texttt{0x} is a simple -matter of using a try/except statement which attempts to convert the -string as hex to an integer and then back to hex; then using that to -search with. Raising a ValueError simply results in treating the -string as a string. This is the method and logic utilised in the -\texttt{import-keys-hkp.py} script (see below). - - -\subsubsection{Working with ProtonMail} -\label{sec:org27b0d6f} -Here is a variation on the example above which checks the constrained -ProtonMail keyserver for ProtonMail public keys. - -\begin{verbatim} -import gpg -import requests -import sys - -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) -\end{verbatim} - -Both the above example, \href{../examples/howto/pmkey-import.py}{pmkey-import.py}, and a version which prompts -for an alternative GnuPG home directory, \href{../examples/howto/pmkey-import-alt.py}{pmkey-import-alt.py}, are -available with the other examples and are executable scripts. - -Note that while the ProtonMail servers are based on the SKS servers, -their server is related more to their API and is not feature complete -by comparison to the servers in the SKS pool. One notable difference -being that the ProtonMail server does not permit non ProtonMail users -to update their own keys, which could be a vector for attacking -ProtonMail users who may not receive a key's revocation if it had been -compromised. - - -\subsubsection{Importing with HKP for Python} -\label{sec:org592d7ab} -Performing the same tasks with the \href{https://github.com/Selfnet/hkp4py}{hkp4py module} (available via PyPI) -is not too much different, but does provide a number of options of -benefit to end users. Not least of which being the ability to perform -some checks on a key before importing it or not. For instance it may -be the policy of a site or project to only import keys which have not -been revoked. The hkp4py module permits such checks prior to the -importing of the keys found. - -\begin{verbatim} -import gpg -import hkp4py -import sys - -c = gpg.Context() -server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") -results = [] -keys = [] - -if len(sys.argv) > 2: - pattern = " ".join(sys.argv[1:]) -elif len(sys.argv) == 2: - pattern = sys.argv[1] -else: - pattern = input("Enter the pattern to search for keys or user IDs: ") - - -if pattern is not None: - try: - key = server.search(hex(int(pattern, 16))) - keyed = True - except ValueError as ve: - key = server.search(pattern) - keyed = False - - if key is not None: - keys.append(key[0]) - if keyed is True: - try: - fob = server.search(pattern) - except: - fob = None - if fob is not None: - keys.append(fob[0]) - else: - pass - else: - pass - - for logrus in pattern.split(): - try: - key = server.search(hex(int(logrus, 16))) - hexed = True - except ValueError as ve: - key = server.search(logrus) - hexed = False - - if key is not None: - keys.append(key[0]) - if hexed is True: - try: - fob = server.search(logrus) - except: - fob = None - if fob is not None: - keys.append(fob[0]) - else: - pass - else: - pass - - -if len(keys) > 0: - for key in keys: - import_result = c.key_import(key.key_blob) - results.append(import_result) - -for result in results: - 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 -\end{verbatim} - -Since the hkp4py module handles multiple keys just as effectively as -one (\texttt{keys} is a list of responses per matching key), the example -above is able to do a little bit more with the returned data before -anything is actually imported. - - -\subsubsection{Importing from ProtonMail with HKP for Python} -\label{sec:org3966a76} -Though this can provide certain benefits even when working with -ProtonMail, the scope is somewhat constrained there due to the -limitations of the ProtonMail keyserver. - -For instance, searching the SKS keyserver pool for the term "gnupg" -produces hundreds of results from any time the word appears in any -part of a user ID. Performing the same search on the ProtonMail -keyserver returns zero results, even though there are at least two -test accounts which include it as part of the username. - -The cause of this discrepancy is the deliberate configuration of that -server by ProtonMail to require an exact match of the full email -address of the ProtonMail user whose key is being requested. -Presumably this is intended to reduce breaches of privacy of their -users as an email address must already be known before a key for that -address can be obtained. - - -\begin{enumerate} -\item Import from ProtonMail via HKP for Python Example no. 1 -\label{sec:org6cd15c3} -The following script is available with the rest of the examples under -the somewhat less than original name, \texttt{pmkey-import-hkp.py}. - -\begin{verbatim} -import gpg -import hkp4py -import os.path -import sys - -print(""" -This script searches the ProtonMail key server for the specified key and -imports it. - -Usage: pmkey-import-hkp.py [search strings] -""") - -c = gpg.Context(armor=True) -server = hkp4py.KeyServer("hkps://api.protonmail.ch") -keyterms = [] -ksearch = [] -allkeys = [] -results = [] -paradox = [] -homeless = None - -if len(sys.argv) > 2: - keyterms = sys.argv[1:] -elif len(sys.argv) == 2: - keyterm = sys.argv[1] - keyterms.append(keyterm) -else: - key_term = input("Enter the key ID, UID or search string: ") - keyterms = key_term.split() - -for keyterm in keyterms: - 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: - print("Checking for key for: {0}".format(k)) - try: - keys = server.search(k) - if isinstance(keys, list) is True: - for key in keys: - allkeys.append(key) - try: - import_result = c.key_import(key.key_blob) - except Exception as e: - import_result = c.key_import(key.key) - else: - paradox.append(keys) - import_result = None - except Exception as e: - import_result = None - results.append(import_result) - -for result in results: - 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: - pass -\end{verbatim} - - -\item Import from ProtonMail via HKP for Python Example no. 2 -\label{sec:org3b674e5} -Like its counterpart above, this script can also be found with the -rest of the examples, by the name pmkey-import-hkp-alt.py. - -With this script a modicum of effort has been made to treat anything -passed as a \texttt{homedir} which either does not exist or which is not a -directory, as also being a pssible user ID to check for. It's not -guaranteed to pick up on all such cases, but it should cover most of -them. - -\begin{verbatim} -import gpg -import hkp4py -import os.path -import sys - -print(""" -This script searches the ProtonMail key server for the specified key and -imports it. Optionally enables specifying a different GnuPG home directory. - -Usage: pmkey-import-hkp.py [homedir] [search string] - or: pmkey-import-hkp.py [search string] -""") - -c = gpg.Context(armor=True) -server = hkp4py.KeyServer("hkps://api.protonmail.ch") -keyterms = [] -ksearch = [] -allkeys = [] -results = [] -paradox = [] -homeless = None - -if len(sys.argv) > 3: - homedir = sys.argv[1] - keyterms = sys.argv[2:] -elif len(sys.argv) == 3: - homedir = sys.argv[1] - keyterm = sys.argv[2] - keyterms.append(keyterm) -elif len(sys.argv) == 2: - homedir = "" - keyterm = sys.argv[1] - keyterms.append(keyterm) -else: - keyterm = input("Enter the key ID, UID or search string: ") - homedir = input("Enter the GPG configuration directory path (optional): ") - keyterms.append(keyterm) - -if len(homedir) == 0: - homedir = None - homeless = False - -if homedir is not None: - if homedir.startswith("~"): - if os.path.exists(os.path.expanduser(homedir)) is True: - if os.path.isdir(os.path.expanduser(homedir)) is True: - c.home_dir = os.path.realpath(os.path.expanduser(homedir)) - else: - homeless = True - else: - homeless = True - elif os.path.exists(os.path.realpath(homedir)) is True: - if os.path.isdir(os.path.realpath(homedir)) is True: - c.home_dir = os.path.realpath(homedir) - else: - homeless = True - else: - homeless = True - -# First check to see if the homedir really is a homedir and if not, treat it as -# a search string. -if homeless is True: - keyterms.append(homedir) - c.home_dir = None -else: - pass - -for keyterm in keyterms: - 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: - print("Checking for key for: {0}".format(k)) - try: - keys = server.search(k) - if isinstance(keys, list) is True: - for key in keys: - allkeys.append(key) - try: - import_result = c.key_import(key.key_blob) - except Exception as e: - import_result = c.key_import(key.key) - else: - paradox.append(keys) - import_result = None - except Exception as e: - import_result = None - results.append(import_result) - -for result in results: - 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: - pass -\end{verbatim} -\end{enumerate} - - -\subsection{Exporting keys} -\label{sec:org4596a77} -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. - - -\subsubsection{Exporting public keys} -\label{sec:org873704f} -There are two methods of exporting public keys, both of which are very -similar to the other. The default method, \texttt{key\_export()}, will export -a public key or keys matching a specified pattern as normal. The -alternative, the \texttt{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{verbatim} -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{verbatim} - -It should be noted that the result will only return \texttt{None} when a -search pattern has been entered, but has not matched any keys. When -the search pattern itself is set to \texttt{None} this triggers the exporting -of the entire public keybox. - -\begin{verbatim} -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{verbatim} - - -\subsubsection{Exporting secret keys} -\label{sec:org6c28d3d} -Exporting secret keys is, functionally, very similar to exporting -public keys; save for the invocation of \texttt{pinentry} via \texttt{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{verbatim} -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 len(homedir) == 0: - homedir = None -elif homedir.startswith("~"): - userdir = os.path.expanduser(homedir) - if os.path.exists(userdir) is True: - homedir = os.path.realpath(userdir) - else: - homedir = None -else: - homedir = os.path.realpath(homedir) - -if os.path.exists(homedir) is False: - homedir = None -else: - if os.path.isdir(homedir) is False: - homedir = None - else: - pass - -if homedir is not None: - 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{verbatim} - -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 (\texttt{.gpg}) and ASCII armoured -(\texttt{.asc}) files. - -\begin{verbatim} -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 len(homedir) == 0: - homedir = None -elif homedir.startswith("~"): - userdir = os.path.expanduser(homedir) - if os.path.exists(userdir) is True: - homedir = os.path.realpath(userdir) - else: - homedir = None -else: - homedir = os.path.realpath(homedir) - -if os.path.exists(homedir) is False: - homedir = None -else: - if os.path.isdir(homedir) is False: - homedir = None - else: - pass - -if homedir is not None: - 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: - try: - hd = subprocess.getoutput(gpgconfcmd) - except: - process = subprocess.Popen(gpgconfcmd.split(), - stdout=subprocess.PIPE) - procom = process.communicate() - if sys.version_info[0] == 2: - hd = procom[0].strip() - else: - hd = procom[0].decode().strip() - 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{verbatim} - - -\subsubsection{Sending public keys to the SKS Keyservers} -\label{sec:org82d4958} -As with the previous section on importing keys, the \texttt{hkp4py} module -adds another option with exporting keys in order to send them to the -public keyservers. - -The following example demonstrates how this may be done. - -\begin{verbatim} -import gpg -import hkp4py -import os.path -import sys - -print(""" -This script sends one or more public keys to the SKS keyservers and is -essentially a slight variation on the export-key.py script. -""") - -c = gpg.Context(armor=True) -server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") - -if len(sys.argv) > 2: - logrus = " ".join(sys.argv[1:]) -elif len(sys.argv) == 2: - logrus = sys.argv[1] -else: - logrus = input("Enter the UID matching the key(s) to send: ") - -if len(logrus) > 0: - try: - export_result = c.key_export(pattern=logrus) - except Exception as e: - print(e) - export_result = None -else: - export_result = c.key_export(pattern=None) - -if export_result is not None: - try: - try: - send_result = server.add(export_result) - except: - send_result = server.add(export_result.decode()) - if send_result is not None: - print(send_result) - else: - pass - except Exception as e: - print(e) -else: - pass -\end{verbatim} - -An expanded version of this script with additional functions for -specifying an alternative homedir location is in the examples -directory as \texttt{send-key-to-keyserver.py}. - -The \texttt{hkp4py} module appears to handle both string and byte literal text -data equally well, but the GPGME bindings deal primarily with byte -literal data only and so this script sends in that format first, then -tries the string literal form. - - -\section{Basic Functions} -\label{sec:orgb40b488} -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. - - -\subsection{Encryption} -\label{sec:org3170365} -Encrypting is very straight forward. In the first example below the -message, \texttt{text}, is encrypted to a single recipient's key. In the -second example the message will be encrypted to multiple recipients. - - -\subsubsection{Encrypting to one key} -\label{sec:org8098c64} -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 \texttt{gpg.Context().encrypt()} method. - -Those keyword arguments are: \texttt{recipients}, a list of keys encrypted to -(covered in greater detail in the following section); \texttt{sign}, whether -or not to sign the plaintext data, see subsequent sections on signing -and verifying signatures below (defaults to \texttt{True}); \texttt{sink}, to write -results or partial results to a secure sink instead of returning it -(defaults to \texttt{None}); \texttt{passphrase}, only used when utilising symmetric -encryption (defaults to \texttt{None}); \texttt{always\_trust}, used to override the -trust model settings for recipient keys (defaults to \texttt{False}); -\texttt{add\_encrypt\_to}, utilises any preconfigured \texttt{encrypt-to} or -\texttt{default-key} settings in the user's \texttt{gpg.conf} file (defaults to -\texttt{False}); \texttt{prepare}, prepare for encryption (defaults to \texttt{False}); -\texttt{expect\_sign}, prepare for signing (defaults to \texttt{False}); \texttt{compress}, -compresses the plaintext prior to encryption (defaults to \texttt{True}). - -\begin{verbatim} -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{verbatim} - -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 \texttt{gpg.conf} file: - -\begin{verbatim} -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{verbatim} - -If the \texttt{recipients} parameter is empty then the plaintext is encrypted -symmetrically. If no \texttt{passphrase} is supplied as a parameter or via a -callback registered with the \texttt{Context()} then an out-of-band prompt -for the passphrase via pinentry will be invoked. - - -\subsubsection{Encrypting to multiple keys} -\label{sec:orgdcf6e47} -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 (\texttt{text}) to everyone with an -email address on the \texttt{gnupg.org} domain,\footnote{You probably don't really want to do this. Searching the -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.} but does \emph{not} encrypt -to a default key or other key which is configured to normally encrypt -to. - -\begin{verbatim} -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{verbatim} - -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 \texttt{c.encrypt} line to this: - -\begin{verbatim} -ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, - always_trust=True, - add_encrypt_to=True) -\end{verbatim} - -The only keyword arguments requiring modification are those for which -the default values are changing. The default value of \texttt{sign} is -\texttt{True}, the default of \texttt{always\_trust} is \texttt{False}, the default of -\texttt{add\_encrypt\_to} is \texttt{False}. - -If \texttt{always\_trust} is not set to \texttt{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{verbatim} -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{verbatim} - -This will attempt to encrypt to all the keys searched for, then remove -invalid recipients if it fails and try again. - - -\subsection{Decryption} -\label{sec:org1282b3d} -Decrypting something encrypted to a key in one's secret keyring is -fairly straight forward. - -In this example code, however, preconfiguring either \texttt{gpg.Context()} -or \texttt{gpg.core.Context()} as \texttt{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 \texttt{c} simply adds lines for no -gain. - -\begin{verbatim} -import gpg - -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) - -if plaintext is not None: - with open(newfile, "wb") as nfile: - nfile.write(plaintext) - else: - pass -\end{verbatim} - -The data available in \texttt{plaintext} in this example is the decrypted -content as a byte object, the recipient key IDs and algorithms in -\texttt{result} and the results of verifying any signatures of the data in -\texttt{verify\_result}. - -If \texttt{gpg.Context().decrypt(cfile, verify=False)} is called instead, -then \texttt{verify\_result} will be returned as \texttt{None} and the rest remains -as described here. - - -\subsection{Signing text and files} -\label{sec:org59f1262} -The following sections demonstrate how to specify keys to sign with. - - -\subsubsection{Signing key selection} -\label{sec:org909cb3c} -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{verbatim} -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{verbatim} - -The signing examples in the following sections include the explicitly -designated \texttt{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. - - -\subsubsection{Normal or default signing messages or files} -\label{sec:org6ee259d} -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 \texttt{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. - -\begin{verbatim} -import gpg - -text0 = """Declaration of ... something. - -""" -text = text0.encode() - -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{verbatim} - -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{verbatim} -import gpg - -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) - -with open("/path/to/statement.txt.sig", "wb") as afile: - afile.write(signed_data) -\end{verbatim} - - -\subsubsection{Detached signing messages and files} -\label{sec:org51049d4} -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{verbatim} -import gpg - -text0 = """Declaration of ... something. - -""" -text = text0.encode() - -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{verbatim} - -As with normal signatures, detached signatures are best handled as -byte literals, even when the output is ASCII armoured. - -\begin{verbatim} -import gpg - -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) - -with open("/path/to/statement.txt.sig", "wb") as afile: - afile.write(signed_data) -\end{verbatim} - - -\subsubsection{Clearsigning messages or text} -\label{sec:orgf528a20} -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{verbatim} -import gpg - -text0 = """Declaration of ... something. - -""" -text = text0.encode() - -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{verbatim} - -In spite of the appearance of a clear-signed message, the data handled -by GPGME in signing it must still be byte literals. - -\begin{verbatim} -import gpg - -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) - -with open("/path/to/statement.txt.asc", "wb") as afile: - afile.write(signed_data) -\end{verbatim} - - -\subsection{Signature verification} -\label{sec:org6cd81af} -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{verbatim} -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{verbatim} - -Whereas this next example, which is almost identical would work with -normal ASCII armoured files and with clear-signed files: - -\begin{verbatim} -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{verbatim} - -In both of the previous examples it is also possible to compare the -original data that was signed against the signed data in \texttt{data} to see -if it matches with something like this: - -\begin{verbatim} -with open(filename, "rb") as afile: - text = afile.read() - -if text == data: - print("Good signature.") -else: - pass -\end{verbatim} - -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 \texttt{c.verify}. So \texttt{data} is \texttt{None} and only the information -in \texttt{result} is available. - -\begin{verbatim} -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{verbatim} - -\begin{verbatim} -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{verbatim} - - -\section{Creating keys and subkeys} -\label{sec:org754c6df} -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 \texttt{SECRET} level -clearance, so his keys will be 3072-bit keys. - -The pre-configured \texttt{gpg.conf} file which sets cipher, digest and other -preferences contains the following configuration parameters: - -\begin{verbatim} -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{verbatim} - - -\subsection{Primary key} -\label{sec:org0185c23} -Generating a primary key uses the \texttt{create\_key} method in a Context. -It contains multiple arguments and keyword arguments, including: -\texttt{userid}, \texttt{algorithm}, \texttt{expires\_in}, \texttt{expires}, \texttt{sign}, \texttt{encrypt}, -\texttt{certify}, \texttt{authenticate}, \texttt{passphrase} and \texttt{force}. The defaults for -all of those except \texttt{userid}, \texttt{algorithm}, \texttt{expires\_in}, \texttt{expires} and -\texttt{passphrase} is \texttt{False}. The defaults for \texttt{algorithm} and -\texttt{passphrase} is \texttt{None}. The default for \texttt{expires\_in} is \texttt{0}. The -default for \texttt{expires} is \texttt{True}. There is no default for \texttt{userid}. - -If \texttt{passphrase} is left as \texttt{None} then the key will not be generated -with a passphrase, if \texttt{passphrase} is set to a string then that will -be the passphrase and if \texttt{passphrase} is set to \texttt{True} then gpg-agent -will launch pinentry to prompt for a passphrase. For the sake of -convenience, these examples will keep \texttt{passphrase} set to \texttt{None}. - -\begin{verbatim} -import gpg - -c = gpg.Context() - -c.home_dir = "~/.gnupg-dm" -userid = "Danger Mouse <dm@secret.example.net>" - -dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, - sign=True, certify=True) -\end{verbatim} - -One thing to note here is the use of setting the \texttt{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, \texttt{\textasciitilde{}/.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 \texttt{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 -\texttt{GenkeyResult} object, which includes the following data: - -\begin{verbatim} -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{verbatim} - -Alternatively the information can be confirmed using the command line -program: - -\begin{verbatim} -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 <dm@secret.example.net> - -bash-4.4$ -\end{verbatim} - -As with generating keys manually, to preconfigure expanded preferences -for the cipher, digest and compression algorithms, the \texttt{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 \texttt{gpg.conf} -file in order to be able to generate this: - -\begin{verbatim} -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 <dm@secret.example.net> - -[ultimate] (1). Danger Mouse <dm@secret.example.net> - 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{verbatim} - - -\subsection{Subkeys} -\label{sec:org5e2acbc} -Adding subkeys to a primary key is fairly similar to creating the -primary key with the \texttt{create\_subkey} method. Most of the arguments -are the same, but not quite all. Instead of the \texttt{userid} argument -there is now a \texttt{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{verbatim} -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{verbatim} - -As with the primary key, the results here can be checked with: - -\begin{verbatim} -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{verbatim} - -As well as on the command line with: - -\begin{verbatim} -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 <dm@secret.example.net> -ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] - -bash-4.4$ -\end{verbatim} - - -\subsection{User IDs} -\label{sec:org4869c58} -\subsubsection{Adding User IDs} -\label{sec:orgebb13ca} -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 -\texttt{key\_add\_uid} and the only arguments it takes are for the \texttt{key} and -the new \texttt{uid}. - -\begin{verbatim} -import gpg - -c = gpg.Context() -c.home_dir = "~/.gnupg-dm" - -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -key = c.get_key(dmfpr, secret=True) -uid = "Danger Mouse <danger.mouse@secret.example.net>" - -c.key_add_uid(key, uid) -\end{verbatim} - -Unsurprisingly the result of this is: - -\begin{verbatim} -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 <danger.mouse@secret.example.net> -uid [ultimate] Danger Mouse <dm@secret.example.net> -ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] - -bash-4.4$ -\end{verbatim} - - -\subsubsection{Revoking User IDs} -\label{sec:org1683695} -Revoking a user ID is a fairly similar process, except that it uses -the \texttt{key\_revoke\_uid} method. - -\begin{verbatim} -import gpg - -c = gpg.Context() -c.home_dir = "~/.gnupg-dm" - -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -key = c.get_key(dmfpr, secret=True) -uid = "Danger Mouse <danger.mouse@secret.example.net>" - -c.key_revoke_uid(key, uid) -\end{verbatim} - - -\subsection{Key certification} -\label{sec:orgf431bf8} -Since key certification is more frequently referred to as key signing, -the method used to perform this function is \texttt{key\_sign}. - -The \texttt{key\_sign} method takes four arguments: \texttt{key}, \texttt{uids}, -\texttt{expires\_in} and \texttt{local}. The default value of \texttt{uids} is \texttt{None} and -which results in all user IDs being selected. The default value of -both \texttt{expires\_in} and \texttt{local} is \texttt{False}; which results in the -signature never expiring and being able to be exported. - -The \texttt{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 \texttt{uids} value is not \texttt{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: - -\begin{verbatim} -import gpg - -c = gpg.Context() -uid = "Danger Mouse <dm@secret.example.net>" - -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -key = c.get_key(dmfpr, secret=True) -c.key_sign(key, uids=uid, expires_in=2764800) -\end{verbatim} - - -\subsubsection{Verifying key certifications} -\label{sec:org87abdd3} -\begin{verbatim} -import gpg -import time - -c = gpg.Context() -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -keys = list(c.keylist(pattern=dmuid, mode=gpg.constants.keylist.mode.SIGS)) -key = keys[0] - -for user in key.uids: - for sig in user.signatures: - print("0x{0}".format(sig.keyid), "", time.ctime(sig.timestamp), "", - sig.uid) -\end{verbatim} - -Which for Danger Mouse displays the following: - -\begin{verbatim} -0x92E3F6115435C65A Thu Mar 15 13:17:44 2018 Danger Mouse <dm@secret.example.net> -0x321E4E2373590E5D Mon Nov 26 12:46:05 2018 Ben McGinnes <ben@adversary.org> -\end{verbatim} - -The two key signatures listed are for the self-certification of Danger -Mouse's key made when the key was created in March, 2018; and the -second is a signature made by the author and set to expire at the end -of the year. Note that the second signature was made with the -following code (including the preceding code to display the output of -the certifications or key signatures): - -\begin{verbatim} -import gpg -import math -import pendulum -import time - -hd = "/home/dm/.gnupg" -c = gpg.Context() -d = gpg.Context(home_dir=hd) -dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" -dmuid = "Danger Mouse <dm@secret.example.net>" -dkeys = list(c.keylist(pattern=dmuid)) -dmkey = dkeys[0] - -c.key_import(d.key_export(pattern=None)) - -tp = pendulum.period(pendulum.now(tz="local"), pendulum.datetime(2019, 1, 1)) -ts = tp.total_seconds() -total_secs = math.ceil(ts) -c.key_sign(dmkey, uids=dmuid, expires_in=total_secs) - -d.key_import(c.key_export(pattern=dmuid)) -keys = list(c.keylist(pattern=dmuid, mode=gpg.constants.keylist.mode.SIGS)) -key = keys[0] - -for user in key.uids: - for sig in user.signatures: - print("0x{0}".format(sig.keyid), "", time.ctime(sig.timestamp), "", - sig.uid) -\end{verbatim} - -Note that this final code block includes the use of a module which is -\emph{not} part of Python's standard library, the \href{https://pendulum.eustace.io/}{pendulum module}. Unlike -the standard datetime module, pendulum makes working with dates and -times significantly easier in Python; just as the requests module -makes working with HTTP and HTTPS easier than the builtin modules do. - -Though neither requests nor pendulum are required modules for using -the GPGME Python bindings, they are both highly recommended more -generally. - - -\section{Advanced or Experimental Use Cases} -\label{sec:org944cc00} -\subsection{C plus Python plus SWIG plus Cython} -\label{sec:org3b53926} -In spite of the apparent incongruence of using Python bindings to a C -interface only to generate more C from the Python; it is in fact quite -possible to use the GPGME bindings with \href{http://docs.cython.org/en/latest/index.html}{Cython}. Though in many cases -the benefits may not be obvious since the most computationally -intensive work never leaves the level of the C code with which GPGME -itself is interacting with. - -Nevertheless, there are some situations where the benefits are -demonstrable. One of the better and easier examples being the one of -the early examples in this HOWTO, the \hyperref[sec:org63a3d62]{key counting} code. Running that -example as an executable Python script, \texttt{keycount.py} (available in -the \texttt{examples/howto/} directory), will take a noticeable amount of time -to run on most systems where the public keybox or keyring contains a -few thousand public keys. - -Earlier in the evening, prior to starting this section, I ran that -script on my laptop; as I tend to do periodically and timed it using -\texttt{time} utility, with the following results: - -\begin{verbatim} -bash-4.4$ time keycount.py - -Number of secret keys: 23 -Number of public keys: 12112 - - -real 11m52.945s -user 0m0.913s -sys 0m0.752s - -bash-4.4$ -\end{verbatim} - -Sometime after that I imported another key and followed it with a -little test of Cython. This test was kept fairly basic, essentially -lifting the material from the \href{http://docs.cython.org/en/latest/src/tutorial/cython\_tutorial.html}{Cython Basic Tutorial} to demonstrate -compiling Python code to C. The first step was to take the example -key counting code quoted previously, essentially from the importing of -the \texttt{gpg} module to the end of the script: - -\begin{verbatim} -import gpg - -c = gpg.Context() -seckeys = c.keylist(pattern=None, secret=True) -pubkeys = c.keylist(pattern=None, secret=False) - -seclist = list(seckeys) -secnum = len(seclist) - -publist = list(pubkeys) -pubnum = len(publist) - -print(""" - Number of secret keys: {0} - Number of public keys: {1} - -""".format(secnum, pubnum)) -\end{verbatim} - -Save that into a file called \texttt{keycount.pyx} and then create a -\texttt{setup.py} file which contains this: - -\begin{verbatim} -from distutils.core import setup -from Cython.Build import cythonize - -setup( - ext_modules = cythonize("keycount.pyx") -) -\end{verbatim} - -Compile it: - -\begin{verbatim} -bash-4.4$ python setup.py build_ext --inplace -bash-4.4$ -\end{verbatim} - -Then run it in a similar manner to \texttt{keycount.py}: - -\begin{verbatim} -bash-4.4$ time python3.7 -c "import keycount" - -Number of secret keys: 23 -Number of public keys: 12113 - - -real 6m47.905s -user 0m0.785s -sys 0m0.331s - -bash-4.4$ -\end{verbatim} - -Cython turned \texttt{keycount.pyx} into an 81KB \texttt{keycount.o} file in the -\texttt{build/} directory, a 24KB \texttt{keycount.cpython-37m-darwin.so} file to be -imported into Python 3.7 and a 113KB \texttt{keycount.c} generated C source -code file of nearly three thousand lines. Quite a bit bigger than the -314 bytes of the \texttt{keycount.pyx} file or the full 1,452 bytes of the -full executable \texttt{keycount.py} example script. - -On the other hand it ran in nearly half the time; taking 6 minutes and -47.905 seconds to run. As opposed to the 11 minutes and 52.945 seconds -which the CPython script alone took. - -The \texttt{keycount.pyx} and \texttt{setup.py} files used to generate this example -have been added to the \texttt{examples/howto/advanced/cython/} directory -The example versions include some additional options to annotate the -existing code and to detect Cython's use. The latter comes from the -\href{http://docs.cython.org/en/latest/src/tutorial/pure.html\#magic-attributes-within-the-pxd}{Magic Attributes} section of the Cython documentation. - - -\section{Miscellaneous extras and work-arounds} -\label{sec:orgd72f7de} -Most of the things in the following sections are here simply because -there was no better place to put them, even though some are only -peripherally related to the GPGME Python bindings. Some are also -workarounds for functions not integrated with GPGME as yet. This is -especially true of the first of these, dealing with \hyperref[sec:org83f4d00]{group lines}. - - -\subsection{Group lines} -\label{sec:org83f4d00} -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. - -\begin{verbatim} -import subprocess -import sys - -if sys.platform == "win32": - gpgconfcmd = "gpgconf.exe --list-options gpg" -else: - gpgconfcmd = "gpgconf --list-options gpg" - -process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE) -procom = process.communicate() - -if sys.version_info[0] == 2: - lines = procom[0].splitlines() -else: - lines = procom[0].decode().splitlines() - -for line in lines: - if line.startswith("group") is True: - break - -groups = line.split(":")[-1].replace('"', '').split(',') - -group_lines = [] -group_lists = [] - -for i in range(len(groups)): - group_lines.append(groups[i].split("=")) - group_lists.append(groups[i].split("=")) - -for i in range(len(group_lists)): - group_lists[i][1] = group_lists[i][1].split() -\end{verbatim} - -The result of that code is that \texttt{group\_lines} is a list of lists where -\texttt{group\_lines[i][0]} is the name of the group and \texttt{group\_lines[i][1]} -is the key IDs of the group as a string. - -The \texttt{group\_lists} result is very similar in that it is a list of -lists. The first part, \texttt{group\_lists[i][0]} matches -\texttt{group\_lines[i][0]} as the name of the group, but \texttt{group\_lists[i][1]} -is the key IDs of the group as a list. - -A demonstration of using the \texttt{groups.py} module is also available in -the form of the executable \texttt{mutt-groups.py} script. This second -script reads all the group entries in a user's \texttt{gpg.conf} file and -converts them into crypt-hooks suitable for use with the Mutt and -Neomutt mail clients. - - -\subsection{Keyserver access for Python} -\label{sec:org5ad99aa} -The \href{https://github.com/Selfnet/hkp4py}{hkp4py} module by Marcel Fest was originally a port of the old -\href{https://github.com/dgladkov/python-hkp}{python-hkp} module from Python 2 to Python 3 and updated to use the -\href{http://docs.python-requests.org/en/latest/index.html}{requests} module instead. It has since been modified to provide -support for Python 2.7 as well and is available via PyPI. - -Since it rewrites the \texttt{hkp} protocol prefix as \texttt{http} and \texttt{hkps} as -\texttt{https}, the module is able to be used even with servers which do not -support the full scope of keyserver functions.\footnote{Such as with ProtonMail servers. This also means that -restricted servers which only advertise either HTTP or HTTPS end -points and not HKP or HKPS end points must still be identified as as -HKP or HKPS within the Python Code. The \texttt{hkp4py} module will rewrite -these appropriately when the connection is made to the server.} It also works quite -readily when incorporated into a \hyperref[sec:org3b53926]{Cython} generated and compiled version -of any code. - - -\subsubsection{Key import format} -\label{sec:org1e47c97} -The hkp4py module returns key data via requests as string literals -(\texttt{r.text}) instead of byte literals (\texttt{r.content}). This means that -the retrurned key data must be encoded to UTF-8 when importing that -key material using a \texttt{gpg.Context().key\_import()} method. - -For this reason an alternative method has been added to the \texttt{search} -function of \texttt{hkp4py.KeyServer()} which returns the key in the correct -format as expected by \texttt{key\_import}. When importing using this module, -it is now possible to import with this: - -\begin{verbatim} -for key in keys: - if key.revoked is False: - gpg.Context().key_import(key.key_blob) - else: - pass -\end{verbatim} - -Without that recent addition it would have been necessary to encode -the contents of each \texttt{hkp4py.KeyServer().search()[i].key} in -\texttt{hkp4py.KeyServer().search()} before trying to import it. - -An example of this is included in the \hyperref[sec:org1ad598a]{Importing Keys} section of this -HOWTO and the corresponding executable version of that example is -available in the \texttt{lang/python/examples/howto} directory as normal; the -executable version is the \texttt{import-keys-hkp.py} file. - - -\subsection{GPGME version checking} -\label{sec:org18b8ef8} -For various reasons it may be necessary to check which version of -GPGME the bindings have been built against; including whether a -minimum required version of GPGME is in use. - -For the most part the \texttt{gpg.version.versionstr} and -\texttt{gpg.version.versionlist} methods have been quite sufficient. The -former returns the same string as \texttt{gpgme-config -{}-version}, while the -latter returns the major, minor and patch values in a list. - -To check if the installed bindings have actually been built against -the current installed libgpgme version, this check can be performed: - -\begin{verbatim} -import gpg -import subprocess -import sys - -gpgme_version_call = subprocess.Popen(["gpgme-config", "--version"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) -gpgme_version_str = gpgme_version_call.communicate() - -if sys.version_info[0] == 2: - gpgme_version = gpgme_version_str[0].strip() -elif sys.version_info[0] >= 3: - gpgme_version = gpgme_version_str[0].decode().strip() -else: - gpgme_version = None - -if gpgme_version is not None: - if gpgme_version == gpg.version.versionstr: - print("The GPGME Python bindings match libgpgme.") - else: - print("The GPGME Python bindings do NOT match libgpgme.") -else: - print("Upgrade Python and reinstall the GPGME Python bindings.") -\end{verbatim} - -For many developers, however, the preferred checking means checking -for a minimum version or point release. This is now readily available -via the \texttt{gpg.version.versionintlist} method (added in version -\texttt{1.12.1-beta79}). It is also now possible to easily check whether the -installed GPGME Python bindings were built from a development or beta -branch of the GPGME source code. - -The following code demonstrates how both of those methods may be used: - -\begin{verbatim} -import gpg - -try: - if gpg.version.is_beta is True: - print("The installed GPGME Python bindings were built from beta code.") - else: - print("The installed GPGME Python bindings are a released version.") -except Exception as e: - print(e) - -try: - if gpg.version.versionintlist[0] == 1: - if gpg.version.versionintlist[1] == 12: - if gpg.version.versionintlist[2] == 1: - print("This is the minimum version for using versionintlist.") - elif gpg.version.versionintlist[2] > 1: - print("The versionintlist method is available.") - else: - pass - elif gpg.version.versionintlist[1] > 12: - print("The versionintlist method is available.") - else: - pass - elif gpg.version.versionintlist[0] > 1: - print("The versionintlist method is available.") - else: - pass -except Exception as e: - print(e) -\end{verbatim} - -The points where \texttt{pass} is used in the above example will most likely -also produce an \texttt{Exception} error since those results should only -occur in versions which do not have the \texttt{gpgme.version.is\_beta} and -\texttt{gpgme.version.versionintlist} methods available. - - -\section{Copyright and Licensing} -\label{sec:org9d52a23} -\subsection{Copyright} -\label{sec:org26ed04d} -Copyright © The GnuPG Project, 2018. - -Copyright (C) The GnuPG Project, 2018. - - -\subsection{Draft Editions of this HOWTO} -\label{sec:org080a94a} -Draft editions of this HOWTO may be periodically available directly -from the author at any of the following URLs: - -\begin{itemize} -\item \href{https://files.au.adversary.org/crypto/gpgme-python-howto.html}{GPGME Python Bindings HOWTO draft (XHTML single file, AWS S3 SSL)} -\item \href{http://files.au.adversary.org/crypto/gpgme-python-howto.html}{GPGME Python Bindings HOWTO draft (XHTML single file, AWS S3 no SSL)} -\item \href{https://files.au.adversary.org/crypto/gpgme-python-howto-split/index.html}{GPGME Python Bindings HOWTO draft (XHTML multiple files, AWS S3 SSL)} -\item \href{http://files.au.adversary.org/crypto/gpgme-python-howto/index.html}{GPGME Python Bindings HOWTO draft (XHTML multiple files, AWS S3 no SSL)} -\end{itemize} - -All of these draft versions except for one have been generated from -this document via GNU Emacs \href{https://orgmode.org/}{Org mode} and \href{https://www.gnu.org/software/texinfo/}{GNU Texinfo}. Though it is -likely that the specific \href{https://files.au.adversary.org/crypto/gpgme-python-howto}{file} \href{http://files.au.adversary.org/crypto/gpgme-python-howto.org}{version} used will be on the same server -with the generated output formats. - -The GNU Texinfo and reStructured Text versions ship with the software, -while the GNU Emacs Info verseion is generated from the Texinfo -version using GNU Texinfo or GNU Makeinfo. The Texinfo format is -generated from the original Org mode source file in Org mode itself -either within GNU Emacs or via the command line by invoking Emacs in -batch mode: - -\begin{verbatim} -emacs gpgme-python-howto.org --batch -f org-texinfo-export-to-texinfo --kill -emacs gpgme-python-howto --batch -f org-texinfo-export-to-texinfo --kill -\end{verbatim} - -The reStructuredText format is also generated from the Org-mode source -file, except it is generated using \href{https://pandoc.org}{Pandoc} with either of the following -commands: - -\begin{verbatim} -pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org -pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto -\end{verbatim} - -In addition to these there is a significantly less frequently updated -version as a HTML \href{https://files.au.adversary.org/crypto/gpgme-python/dita/webhelp/index.html}{WebHelp site} (AWS S3 SSL); generated from DITA XML -source files, which can be found in \href{https://dev.gnupg.org/source/gpgme/browse/ben\%252Fhowto-dita/}{an alternative branch} of the GPGME -git repository. - -Various generated output formats may occasionally be found in -subdirectories of the \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python}{gpgme-python} directory. In particular within -the \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/dita}{DITA}, \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/rst}{reStructuredText} and \href{https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/texinfo}{Texinfo} subdirectories. The \texttt{rst} -directory contains output files generated with Sphix and may include a -considerable number of its possible output formats. - -These draft editions are not official documents and the version of -documentation in the master branch or which ships with released -versions is the only official documentation. Nevertheless, these -draft editions may occasionally be of use by providing more accessible -web versions which are updated between releases. They are provided on -the understanding that they may contain errors or may contain content -subject to change prior to an official release. - - -\subsection{License GPL compatible} -\label{sec:orge78f3b7} -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. -\end{document} diff --git a/lang/python/doc/texinfo/gpgme-python-howto.aux b/lang/python/doc/texinfo/gpgme-python-howto.aux deleted file mode 100644 index e69de29..0000000 --- a/lang/python/doc/texinfo/gpgme-python-howto.aux +++ /dev/null diff --git a/lang/python/doc/texinfo/gpgme-python-howto.log b/lang/python/doc/texinfo/gpgme-python-howto.log deleted file mode 100644 index 3414a0f..0000000 --- a/lang/python/doc/texinfo/gpgme-python-howto.log +++ /dev/null @@ -1,519 +0,0 @@ -This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=pdfetex 2018.3.2) 18 JAN 2019 10:08 -entering extended mode - restricted \write18 enabled. - file:line:error style messages enabled. - %&-line parsing enabled. -**\input ./gpgme-python-howto.texi -(./gpgme-python-howto.texi -(/home/wk/s/gpgme/lang/python/doc/texinfo/texinfo.tex -Loading texinfo [version 2007-12-02.17]: -\bindingoffset=\dimen16 -\normaloffset=\dimen17 -\pagewidth=\dimen18 -\pageheight=\dimen19 -\outerhsize=\dimen20 -\outervsize=\dimen21 -\cornerlong=\dimen22 -\cornerthick=\dimen23 -\topandbottommargin=\dimen24 -\headlinebox=\box16 -\footlinebox=\box17 -\margin=\insert252 -\EMsimple=\toks13 -\groupbox=\box18 -\groupinvalidhelp=\toks14 -\mil=\dimen25 -\exdentamount=\skip18 -\inmarginspacing=\skip19 - pdf, -\tempnum=\count27 -\lnkcount=\count28 -\filename=\toks15 -\filenamelength=\count29 -\pgn=\count30 -\toksA=\toks16 -\toksB=\toks17 -\toksC=\toks18 -\toksD=\toks19 -\boxA=\box19 -\countA=\count31 -\nopdfimagehelp=\toks20 - fonts, -\sffam=\fam8 -\textleading=\dimen26 -\fontdepth=\count32 - page headings, -\titlepagetopglue=\skip20 -\titlepagebottomglue=\skip21 -\evenheadline=\toks21 -\oddheadline=\toks22 -\evenfootline=\toks23 -\oddfootline=\toks24 - tables, -\tableindent=\dimen27 -\itemindent=\dimen28 -\itemmargin=\dimen29 -\itemmax=\dimen30 -\itemno=\count33 -\multitableparskip=\skip22 -\multitableparindent=\skip23 -\multitablecolspace=\dimen31 -\multitablelinespace=\skip24 -\colcount=\count34 -\everytab=\toks25 - -conditionals, -\doignorecount=\count35 - indexing, -\whatsitskip=\skip25 -\whatsitpenalty=\count36 -\secondaryindent=\skip26 -\partialpage=\box20 -\doublecolumnhsize=\dimen32 - sectioning, -\unnumberedno=\count37 -\chapno=\count38 -\secno=\count39 -\subsecno=\count40 -\subsubsecno=\count41 -\appendixno=\count42 -\absseclevel=\count43 -\secbase=\count44 -\chapheadingskip=\skip27 -\secheadingskip=\skip28 -\subsecheadingskip=\skip29 - toc, -\tocfile=\write0 -\contentsrightmargin=\skip30 -\savepageno=\count45 -\lastnegativepageno=\count46 -\tocindent=\dimen33 - environments, -\errorbox=\box21 -\lispnarrowing=\skip31 -\envskipamount=\skip32 -\circthick=\dimen34 -\cartouter=\dimen35 -\cartinner=\dimen36 -\normbskip=\skip33 -\normpskip=\skip34 -\normlskip=\skip35 -\lskip=\skip36 -\rskip=\skip37 -\tabw=\dimen37 - defuns, -\defbodyindent=\skip38 -\defargsindent=\skip39 -\deflastargmargin=\skip40 -\defunpenalty=\count47 -\parencount=\count48 -\brackcount=\count49 - macros, -\paramno=\count50 -\macname=\toks26 - -cross references, -\auxfile=\write1 -\savesfregister=\count51 - insertions, -\footnoteno=\count52 -\SAVEfootins=\box22 -\SAVEmargin=\box23 - -(/usr/share/texlive/texmf-dist/tex/generic/epsf/epsf.tex -This is `epsf.tex' v2.7.4 <14 February 2011> -\epsffilein=\read1 -\epsfframemargin=\dimen38 -\epsfframethickness=\dimen39 -\epsfrsize=\dimen40 -\epsftmp=\dimen41 -\epsftsize=\dimen42 -\epsfxsize=\dimen43 -\epsfysize=\dimen44 -\pspoints=\dimen45 -) -\noepsfhelp=\toks27 - localization, -\nolanghelp=\toks28 -\countUTFx=\count53 -\countUTFy=\count54 -\countUTFz=\count55 - formatting, -\defaultparindent=\dimen46 - and turning on texinfo input format.) -\openout1 = `gpgme-python-howto.aux'. - -@cpindfile=@write2 -@fnindfile=@write3 -@vrindfile=@write4 -@tpindfile=@write5 -@kyindfile=@write6 -@pgindfile=@write7 - defining Unicode char U+00A0 (decimal 160) - defining Unicode char U+00A1 (decimal 161) - defining Unicode char U+00A3 (decimal 163) - defining Unicode char U+00A8 (decimal 168) - defining Unicode char U+00A9 (decimal 169) - defining Unicode char U+00AA (decimal 170) - defining Unicode char U+00AB (decimal 171) - defining Unicode char U+00AD (decimal 173) - defining Unicode char U+00AE (decimal 174) - defining Unicode char U+00AF (decimal 175) - defining Unicode char U+00B0 (decimal 176) - defining Unicode char U+00B4 (decimal 180) - defining Unicode char U+00B8 (decimal 184) - defining Unicode char U+00BA (decimal 186) - defining Unicode char U+00BB (decimal 187) - defining Unicode char U+00BF (decimal 191) - defining Unicode char U+00C0 (decimal 192) - defining Unicode char U+00C1 (decimal 193) - defining Unicode char U+00C2 (decimal 194) - defining Unicode char U+00C3 (decimal 195) - defining Unicode char U+00C4 (decimal 196) - defining Unicode char U+00C5 (decimal 197) - defining Unicode char U+00C6 (decimal 198) - defining Unicode char U+00C7 (decimal 199) - defining Unicode char U+00C8 (decimal 200) - defining Unicode char U+00C9 (decimal 201) - defining Unicode char U+00CA (decimal 202) - defining Unicode char U+00CB (decimal 203) - defining Unicode char U+00CC (decimal 204) - defining Unicode char U+00CD (decimal 205) - defining Unicode char U+00CE (decimal 206) - defining Unicode char U+00CF (decimal 207) - defining Unicode char U+00D1 (decimal 209) - defining Unicode char U+00D2 (decimal 210) - defining Unicode char U+00D3 (decimal 211) - defining Unicode char U+00D4 (decimal 212) - defining Unicode char U+00D5 (decimal 213) - defining Unicode char U+00D6 (decimal 214) - defining Unicode char U+00D8 (decimal 216) - defining Unicode char U+00D9 (decimal 217) - defining Unicode char U+00DA (decimal 218) - defining Unicode char U+00DB (decimal 219) - defining Unicode char U+00DC (decimal 220) - defining Unicode char U+00DD (decimal 221) - defining Unicode char U+00DF (decimal 223) - defining Unicode char U+00E0 (decimal 224) - defining Unicode char U+00E1 (decimal 225) - defining Unicode char U+00E2 (decimal 226) - defining Unicode char U+00E3 (decimal 227) - defining Unicode char U+00E4 (decimal 228) - defining Unicode char U+00E5 (decimal 229) - defining Unicode char U+00E6 (decimal 230) - defining Unicode char U+00E7 (decimal 231) - defining Unicode char U+00E8 (decimal 232) - defining Unicode char U+00E9 (decimal 233) - defining Unicode char U+00EA (decimal 234) - defining Unicode char U+00EB (decimal 235) - defining Unicode char U+00EC (decimal 236) - defining Unicode char U+00ED (decimal 237) - defining Unicode char U+00EE (decimal 238) - defining Unicode char U+00EF (decimal 239) - defining Unicode char U+00F1 (decimal 241) - defining Unicode char U+00F2 (decimal 242) - defining Unicode char U+00F3 (decimal 243) - defining Unicode char U+00F4 (decimal 244) - defining Unicode char U+00F5 (decimal 245) - defining Unicode char U+00F6 (decimal 246) - defining Unicode char U+00F8 (decimal 248) - defining Unicode char U+00F9 (decimal 249) - defining Unicode char U+00FA (decimal 250) - defining Unicode char U+00FB (decimal 251) - defining Unicode char U+00FC (decimal 252) - defining Unicode char U+00FD (decimal 253) - defining Unicode char U+00FF (decimal 255) - defining Unicode char U+0100 (decimal 256) - defining Unicode char U+0101 (decimal 257) - defining Unicode char U+0102 (decimal 258) - defining Unicode char U+0103 (decimal 259) - defining Unicode char U+0106 (decimal 262) - defining Unicode char U+0107 (decimal 263) - defining Unicode char U+0108 (decimal 264) - defining Unicode char U+0109 (decimal 265) - defining Unicode char U+010A (decimal 266) - defining Unicode char U+010B (decimal 267) - defining Unicode char U+010C (decimal 268) - defining Unicode char U+010D (decimal 269) - defining Unicode char U+010E (decimal 270) - defining Unicode char U+0112 (decimal 274) - defining Unicode char U+0113 (decimal 275) - defining Unicode char U+0114 (decimal 276) - defining Unicode char U+0115 (decimal 277) - defining Unicode char U+0116 (decimal 278) - defining Unicode char U+0117 (decimal 279) - defining Unicode char U+011A (decimal 282) - defining Unicode char U+011B (decimal 283) - defining Unicode char U+011C (decimal 284) - defining Unicode char U+011D (decimal 285) - defining Unicode char U+011E (decimal 286) - defining Unicode char U+011F (decimal 287) - defining Unicode char U+0120 (decimal 288) - defining Unicode char U+0121 (decimal 289) - defining Unicode char U+0124 (decimal 292) - defining Unicode char U+0125 (decimal 293) - defining Unicode char U+0128 (decimal 296) - defining Unicode char U+0129 (decimal 297) - defining Unicode char U+012A (decimal 298) - defining Unicode char U+012B (decimal 299) - defining Unicode char U+012C (decimal 300) - defining Unicode char U+012D (decimal 301) - defining Unicode char U+0130 (decimal 304) - defining Unicode char U+0131 (decimal 305) - defining Unicode char U+0132 (decimal 306) - defining Unicode char U+0133 (decimal 307) - defining Unicode char U+0134 (decimal 308) - defining Unicode char U+0135 (decimal 309) - defining Unicode char U+0139 (decimal 313) - defining Unicode char U+013A (decimal 314) - defining Unicode char U+0141 (decimal 321) - defining Unicode char U+0142 (decimal 322) - defining Unicode char U+0143 (decimal 323) - defining Unicode char U+0144 (decimal 324) - defining Unicode char U+0147 (decimal 327) - defining Unicode char U+0148 (decimal 328) - defining Unicode char U+014C (decimal 332) - defining Unicode char U+014D (decimal 333) - defining Unicode char U+014E (decimal 334) - defining Unicode char U+014F (decimal 335) - defining Unicode char U+0150 (decimal 336) - defining Unicode char U+0151 (decimal 337) - defining Unicode char U+0152 (decimal 338) - defining Unicode char U+0153 (decimal 339) - defining Unicode char U+0154 (decimal 340) - defining Unicode char U+0155 (decimal 341) - defining Unicode char U+0158 (decimal 344) - defining Unicode char U+0159 (decimal 345) - defining Unicode char U+015A (decimal 346) - defining Unicode char U+015B (decimal 347) - defining Unicode char U+015C (decimal 348) - defining Unicode char U+015D (decimal 349) - defining Unicode char U+015E (decimal 350) - defining Unicode char U+015F (decimal 351) - defining Unicode char U+0160 (decimal 352) - defining Unicode char U+0161 (decimal 353) - defining Unicode char U+0162 (decimal 354) - defining Unicode char U+0163 (decimal 355) - defining Unicode char U+0164 (decimal 356) - defining Unicode char U+0168 (decimal 360) - defining Unicode char U+0169 (decimal 361) - defining Unicode char U+016A (decimal 362) - defining Unicode char U+016B (decimal 363) - defining Unicode char U+016C (decimal 364) - defining Unicode char U+016D (decimal 365) - defining Unicode char U+016E (decimal 366) - defining Unicode char U+016F (decimal 367) - defining Unicode char U+0170 (decimal 368) - defining Unicode char U+0171 (decimal 369) - defining Unicode char U+0174 (decimal 372) - defining Unicode char U+0175 (decimal 373) - defining Unicode char U+0176 (decimal 374) - defining Unicode char U+0177 (decimal 375) - defining Unicode char U+0178 (decimal 376) - defining Unicode char U+0179 (decimal 377) - defining Unicode char U+017A (decimal 378) - defining Unicode char U+017B (decimal 379) - defining Unicode char U+017C (decimal 380) - defining Unicode char U+017D (decimal 381) - defining Unicode char U+017E (decimal 382) - defining Unicode char U+01C4 (decimal 452) - defining Unicode char U+01C5 (decimal 453) - defining Unicode char U+01C6 (decimal 454) - defining Unicode char U+01C7 (decimal 455) - defining Unicode char U+01C8 (decimal 456) - defining Unicode char U+01C9 (decimal 457) - defining Unicode char U+01CA (decimal 458) - defining Unicode char U+01CB (decimal 459) - defining Unicode char U+01CC (decimal 460) - defining Unicode char U+01CD (decimal 461) - defining Unicode char U+01CE (decimal 462) - defining Unicode char U+01CF (decimal 463) - defining Unicode char U+01D0 (decimal 464) - defining Unicode char U+01D1 (decimal 465) - defining Unicode char U+01D2 (decimal 466) - defining Unicode char U+01D3 (decimal 467) - defining Unicode char U+01D4 (decimal 468) - defining Unicode char U+01E2 (decimal 482) - defining Unicode char U+01E3 (decimal 483) - defining Unicode char U+01E6 (decimal 486) - defining Unicode char U+01E7 (decimal 487) - defining Unicode char U+01E8 (decimal 488) - defining Unicode char U+01E9 (decimal 489) - defining Unicode char U+01F0 (decimal 496) - defining Unicode char U+01F1 (decimal 497) - defining Unicode char U+01F2 (decimal 498) - defining Unicode char U+01F3 (decimal 499) - defining Unicode char U+01F4 (decimal 500) - defining Unicode char U+01F5 (decimal 501) - defining Unicode char U+01F8 (decimal 504) - defining Unicode char U+01F9 (decimal 505) - defining Unicode char U+01FC (decimal 508) - defining Unicode char U+01FD (decimal 509) - defining Unicode char U+01FE (decimal 510) - defining Unicode char U+01FF (decimal 511) - defining Unicode char U+021E (decimal 542) - defining Unicode char U+021F (decimal 543) - defining Unicode char U+0226 (decimal 550) - defining Unicode char U+0227 (decimal 551) - defining Unicode char U+0228 (decimal 552) - defining Unicode char U+0229 (decimal 553) - defining Unicode char U+022E (decimal 558) - defining Unicode char U+022F (decimal 559) - defining Unicode char U+0232 (decimal 562) - defining Unicode char U+0233 (decimal 563) - defining Unicode char U+0237 (decimal 567) - defining Unicode char U+1E02 (decimal 7682) - defining Unicode char U+1E03 (decimal 7683) - defining Unicode char U+1E04 (decimal 7684) - defining Unicode char U+1E05 (decimal 7685) - defining Unicode char U+1E06 (decimal 7686) - defining Unicode char U+1E07 (decimal 7687) - defining Unicode char U+1E0A (decimal 7690) - defining Unicode char U+1E0B (decimal 7691) - defining Unicode char U+1E0C (decimal 7692) - defining Unicode char U+1E0D (decimal 7693) - defining Unicode char U+1E0E (decimal 7694) - defining Unicode char U+1E0F (decimal 7695) - defining Unicode char U+1E1E (decimal 7710) - defining Unicode char U+1E1F (decimal 7711) - defining Unicode char U+1E20 (decimal 7712) - defining Unicode char U+1E21 (decimal 7713) - defining Unicode char U+1E22 (decimal 7714) - defining Unicode char U+1E23 (decimal 7715) - defining Unicode char U+1E24 (decimal 7716) - defining Unicode char U+1E25 (decimal 7717) - defining Unicode char U+1E26 (decimal 7718) - defining Unicode char U+1E27 (decimal 7719) - defining Unicode char U+1E30 (decimal 7728) - defining Unicode char U+1E31 (decimal 7729) - defining Unicode char U+1E32 (decimal 7730) - defining Unicode char U+1E33 (decimal 7731) - defining Unicode char U+1E34 (decimal 7732) - defining Unicode char U+1E35 (decimal 7733) - defining Unicode char U+1E36 (decimal 7734) - defining Unicode char U+1E37 (decimal 7735) - defining Unicode char U+1E3A (decimal 7738) - defining Unicode char U+1E3B (decimal 7739) - defining Unicode char U+1E3E (decimal 7742) - defining Unicode char U+1E3F (decimal 7743) - defining Unicode char U+1E40 (decimal 7744) - defining Unicode char U+1E41 (decimal 7745) - defining Unicode char U+1E42 (decimal 7746) - defining Unicode char U+1E43 (decimal 7747) - defining Unicode char U+1E44 (decimal 7748) - defining Unicode char U+1E45 (decimal 7749) - defining Unicode char U+1E46 (decimal 7750) - defining Unicode char U+1E47 (decimal 7751) - defining Unicode char U+1E48 (decimal 7752) - defining Unicode char U+1E49 (decimal 7753) - defining Unicode char U+1E54 (decimal 7764) - defining Unicode char U+1E55 (decimal 7765) - defining Unicode char U+1E56 (decimal 7766) - defining Unicode char U+1E57 (decimal 7767) - defining Unicode char U+1E58 (decimal 7768) - defining Unicode char U+1E59 (decimal 7769) - defining Unicode char U+1E5A (decimal 7770) - defining Unicode char U+1E5B (decimal 7771) - defining Unicode char U+1E5E (decimal 7774) - defining Unicode char U+1E5F (decimal 7775) - defining Unicode char U+1E60 (decimal 7776) - defining Unicode char U+1E61 (decimal 7777) - defining Unicode char U+1E62 (decimal 7778) - defining Unicode char U+1E63 (decimal 7779) - defining Unicode char U+1E6A (decimal 7786) - defining Unicode char U+1E6B (decimal 7787) - defining Unicode char U+1E6C (decimal 7788) - defining Unicode char U+1E6D (decimal 7789) - defining Unicode char U+1E6E (decimal 7790) - defining Unicode char U+1E6F (decimal 7791) - defining Unicode char U+1E7C (decimal 7804) - defining Unicode char U+1E7D (decimal 7805) - defining Unicode char U+1E7E (decimal 7806) - defining Unicode char U+1E7F (decimal 7807) - defining Unicode char U+1E80 (decimal 7808) - defining Unicode char U+1E81 (decimal 7809) - defining Unicode char U+1E82 (decimal 7810) - defining Unicode char U+1E83 (decimal 7811) - defining Unicode char U+1E84 (decimal 7812) - defining Unicode char U+1E85 (decimal 7813) - defining Unicode char U+1E86 (decimal 7814) - defining Unicode char U+1E87 (decimal 7815) - defining Unicode char U+1E88 (decimal 7816) - defining Unicode char U+1E89 (decimal 7817) - defining Unicode char U+1E8A (decimal 7818) - defining Unicode char U+1E8B (decimal 7819) - defining Unicode char U+1E8C (decimal 7820) - defining Unicode char U+1E8D (decimal 7821) - defining Unicode char U+1E8E (decimal 7822) - defining Unicode char U+1E8F (decimal 7823) - defining Unicode char U+1E90 (decimal 7824) - defining Unicode char U+1E91 (decimal 7825) - defining Unicode char U+1E92 (decimal 7826) - defining Unicode char U+1E93 (decimal 7827) - defining Unicode char U+1E94 (decimal 7828) - defining Unicode char U+1E95 (decimal 7829) - defining Unicode char U+1E96 (decimal 7830) - defining Unicode char U+1E97 (decimal 7831) - defining Unicode char U+1E98 (decimal 7832) - defining Unicode char U+1E99 (decimal 7833) - defining Unicode char U+1EA0 (decimal 7840) - defining Unicode char U+1EA1 (decimal 7841) - defining Unicode char U+1EB8 (decimal 7864) - defining Unicode char U+1EB9 (decimal 7865) - defining Unicode char U+1EBC (decimal 7868) - defining Unicode char U+1EBD (decimal 7869) - defining Unicode char U+1ECA (decimal 7882) - defining Unicode char U+1ECB (decimal 7883) - defining Unicode char U+1ECC (decimal 7884) - defining Unicode char U+1ECD (decimal 7885) - defining Unicode char U+1EE4 (decimal 7908) - defining Unicode char U+1EE5 (decimal 7909) - defining Unicode char U+1EF2 (decimal 7922) - defining Unicode char U+1EF3 (decimal 7923) - defining Unicode char U+1EF4 (decimal 7924) - defining Unicode char U+1EF8 (decimal 7928) - defining Unicode char U+1EF9 (decimal 7929) - defining Unicode char U+2013 (decimal 8211) - defining Unicode char U+2014 (decimal 8212) - defining Unicode char U+2018 (decimal 8216) - defining Unicode char U+2019 (decimal 8217) - defining Unicode char U+201A (decimal 8218) - defining Unicode char U+201C (decimal 8220) - defining Unicode char U+201D (decimal 8221) - defining Unicode char U+201E (decimal 8222) - defining Unicode char U+2022 (decimal 8226) - defining Unicode char U+2026 (decimal 8230) - defining Unicode char U+2039 (decimal 8249) - defining Unicode char U+203A (decimal 8250) - defining Unicode char U+20AC (decimal 8364) - defining Unicode char U+2192 (decimal 8594) - defining Unicode char U+21D2 (decimal 8658) - defining Unicode char U+2212 (decimal 8722) - defining Unicode char U+2217 (decimal 8727) - defining Unicode char U+2261 (decimal 8801) - -(/usr/share/texmf/tex/texinfo/txi-en.tex -/usr/share/texmf/tex/texinfo/txi-en.tex:22: Undefined control sequence. -l.22 \txisetlanguage - {USenglish}{2}{3} -? -/usr/share/texmf/tex/texinfo/txi-en.tex:22: Interruption. -<to be read again> - { -l.22 \txisetlanguage{ - USenglish}{2}{3} -? m -Type <return> to proceed, S to scroll future error messages, -R to run without stopping, Q to run quietly, -I to insert something, E to edit your file, -H for help, X to quit. -? -/usr/share/texmf/tex/texinfo/txi-en.tex:22: Emergency stop. -<to be read again> - { -l.22 \txisetlanguage{ - USenglish}{2}{3} -End of file on the terminal! - -/usr/share/texmf/tex/texinfo/txi-en.tex:22: ==> Fatal error occurred, no outpu -t PDF file produced! diff --git a/lang/python/doc/texinfo/texput.log b/lang/python/doc/texinfo/texput.log deleted file mode 100644 index af7e1d6..0000000 --- a/lang/python/doc/texinfo/texput.log +++ /dev/null @@ -1,13 +0,0 @@ -This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=pdfetex 2018.3.2) 18 JAN 2019 10:09 -entering extended mode - restricted \write18 enabled. - file:line:error style messages enabled. - %&-line parsing enabled. -**\input ./. - -! Emergency stop. -<*> \input ./. - -End of file on the terminal! - -! ==> Fatal error occurred, no output PDF file produced! diff --git a/lang/python/examples/Makefile.am b/lang/python/examples/Makefile.am new file mode 100644 index 0000000..bd4319e --- /dev/null +++ b/lang/python/examples/Makefile.am @@ -0,0 +1,74 @@ +# Makefile.am for the Python bindings. +# Copyright (C) 2019 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 <https://gnu.org/licenses/>. +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Created by: +# find . -type f -print | sed 's/^.\// /;$q;s/$/ \\/' | sort +EXTRA_DIST = assuan.py \ + decryption-filter.py \ + delkey.py \ + exportimport.py \ + genkey.py \ + howto/add-userid.py \ + howto/advanced/cython/keycount.pyx \ + howto/advanced/cython/requirements.txt \ + howto/advanced/cython/setup.py \ + howto/clear-sign-file.py \ + howto/create-key.py \ + howto/decrypt-file.py \ + howto/detach-sign-file.py \ + howto/encrypt-file.py \ + howto/encrypt-sign-file.py \ + howto/encrypt-to-group-gullible.py \ + howto/encrypt-to-group.py \ + howto/encrypt-to-group-trustno1.py \ + howto/export-key.py \ + howto/export-minimised-key.py \ + howto/export-secret-key.py \ + howto/export-secret-keys.py \ + howto/groups.py \ + howto/import-keybasekey.py \ + howto/import-key.py \ + howto/import-keys-hkp.py \ + howto/import-keys.py \ + howto/import-mailvelope-keys.py \ + howto/keycount.py \ + howto/local-sign-group.py \ + howto/mutt-groups.py \ + howto/pmkey-import-alt.py \ + howto/pmkey-import-hkp-alt.py \ + howto/pmkey-import-hkp.py \ + howto/pmkey-import.py \ + howto/post_installer.py \ + howto/README.org \ + howto/requirements.txt \ + howto/revoke-userid.py \ + howto/send-key-to-keyserver.py \ + howto/sign-file.py \ + howto/sign-key.py \ + howto/symcrypt-file.py \ + howto/temp-homedir-config.py \ + howto/verify-signatures.py \ + howto/verify-signed-file.py \ + inter-edit.py \ + low_level-encrypt_to_all.py \ + sign.py \ + signverify.py \ + simple.py \ + testCMSgetkey.py \ + verifydetails.py diff --git a/lang/python/examples/Makefile.in b/lang/python/examples/Makefile.in new file mode 100644 index 0000000..7300016 --- /dev/null +++ b/lang/python/examples/Makefile.in @@ -0,0 +1,600 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program 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. + +@SET_MAKE@ + +# Makefile.am for the Python bindings. +# Copyright (C) 2019 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 <https://gnu.org/licenses/>. +# SPDX-License-Identifier: LGPL-2.1-or-later +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = lang/python/examples +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_pkg_swig.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \ + $(top_srcdir)/m4/gnupg-ttyname.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/conf/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLED_LANGUAGES = @ENABLED_LANGUAGES@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@ +GLIBC21 = @GLIBC21@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@ +GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@ +GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@ +GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@ +GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@ +GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@ +GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@ +GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@ +GPGME_QT_LIBS = @GPGME_QT_LIBS@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GRAPHVIZ = @GRAPHVIZ@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +HAVE_DOT = @HAVE_DOT@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDADD_FOR_TESTS_KLUDGE = @LDADD_FOR_TESTS_KLUDGE@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@ +LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@ +LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@ +LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@ +LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@ +LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@ +LIBOBJS = @LIBOBJS@ +LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@ +LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@ +LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MOC = @MOC@ +MOC2 = @MOC2@ +NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PYTHON = @PYTHON@ +PYTHONS = @PYTHONS@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_LDFLAGS = @PYTHON_LDFLAGS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +QTCHOOSER = @QTCHOOSER@ +RANLIB = @RANLIB@ +RC = @RC@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SWIG = @SWIG@ +SWIG_LIB = @SWIG_LIB@ +SYSROOT = @SYSROOT@ +VERSION = @VERSION@ +VERSION_MAJOR = @VERSION_MAJOR@ +VERSION_MICRO = @VERSION_MICRO@ +VERSION_MINOR = @VERSION_MINOR@ +VERSION_NUMBER = @VERSION_NUMBER@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +emacs_local_vars_begin = @emacs_local_vars_begin@ +emacs_local_vars_end = @emacs_local_vars_end@ +emacs_local_vars_read_only = @emacs_local_vars_read_only@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ + +# Created by: +# find . -type f -print | sed 's/^.\// /;$q;s/$/ \\/' | sort +EXTRA_DIST = assuan.py \ + decryption-filter.py \ + delkey.py \ + exportimport.py \ + genkey.py \ + howto/add-userid.py \ + howto/advanced/cython/keycount.pyx \ + howto/advanced/cython/requirements.txt \ + howto/advanced/cython/setup.py \ + howto/clear-sign-file.py \ + howto/create-key.py \ + howto/decrypt-file.py \ + howto/detach-sign-file.py \ + howto/encrypt-file.py \ + howto/encrypt-sign-file.py \ + howto/encrypt-to-group-gullible.py \ + howto/encrypt-to-group.py \ + howto/encrypt-to-group-trustno1.py \ + howto/export-key.py \ + howto/export-minimised-key.py \ + howto/export-secret-key.py \ + howto/export-secret-keys.py \ + howto/groups.py \ + howto/import-keybasekey.py \ + howto/import-key.py \ + howto/import-keys-hkp.py \ + howto/import-keys.py \ + howto/import-mailvelope-keys.py \ + howto/keycount.py \ + howto/local-sign-group.py \ + howto/mutt-groups.py \ + howto/pmkey-import-alt.py \ + howto/pmkey-import-hkp-alt.py \ + howto/pmkey-import-hkp.py \ + howto/pmkey-import.py \ + howto/post_installer.py \ + howto/README.org \ + howto/requirements.txt \ + howto/revoke-userid.py \ + howto/send-key-to-keyserver.py \ + howto/sign-file.py \ + howto/sign-key.py \ + howto/symcrypt-file.py \ + howto/temp-homedir-config.py \ + howto/verify-signatures.py \ + howto/verify-signed-file.py \ + inter-edit.py \ + low_level-encrypt_to_all.py \ + sign.py \ + signverify.py \ + simple.py \ + testCMSgetkey.py \ + verifydetails.py + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/python/examples/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu lang/python/examples/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lang/python/src/Makefile.am b/lang/python/src/Makefile.am new file mode 100644 index 0000000..dc7f768 --- /dev/null +++ b/lang/python/src/Makefile.am @@ -0,0 +1,47 @@ +# Makefile.am for the Python bindings. +# Copyright (C) 2019 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 <https://gnu.org/licenses/>. +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Created by: +# find . -type f -print | sed 's/^.\// /;$q;s/$/ \\/' | sort +EXTRA_DIST = callbacks.py \ + constants/create.py \ + constants/data/encoding.py \ + constants/data/__init__.py \ + constants/event.py \ + constants/import_type.py \ + constants/__init__.py \ + constants/keylist/__init__.py \ + constants/keylist/mode.py \ + constants/keysign.py \ + constants/md.py \ + constants/pk.py \ + constants/protocol.py \ + constants/sig/__init__.py \ + constants/sig/mode.py \ + constants/sig/notation.py \ + constants/sigsum.py \ + constants/status.py \ + constants/tofu/__init__.py \ + constants/tofu/policy.py \ + constants/validity.py \ + core.py \ + errors.py \ + __init__.py \ + results.py \ + util.py diff --git a/lang/python/src/Makefile.in b/lang/python/src/Makefile.in new file mode 100644 index 0000000..3a0f3ed --- /dev/null +++ b/lang/python/src/Makefile.in @@ -0,0 +1,573 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program 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. + +@SET_MAKE@ + +# Makefile.am for the Python bindings. +# Copyright (C) 2019 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 <https://gnu.org/licenses/>. +# SPDX-License-Identifier: LGPL-2.1-or-later +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = lang/python/src +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_pkg_swig.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \ + $(top_srcdir)/m4/gnupg-ttyname.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/conf/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLED_LANGUAGES = @ENABLED_LANGUAGES@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@ +GLIBC21 = @GLIBC21@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@ +GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@ +GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@ +GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@ +GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@ +GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@ +GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@ +GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@ +GPGME_QT_LIBS = @GPGME_QT_LIBS@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GRAPHVIZ = @GRAPHVIZ@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +HAVE_DOT = @HAVE_DOT@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDADD_FOR_TESTS_KLUDGE = @LDADD_FOR_TESTS_KLUDGE@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@ +LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@ +LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@ +LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@ +LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@ +LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@ +LIBOBJS = @LIBOBJS@ +LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@ +LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@ +LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MOC = @MOC@ +MOC2 = @MOC2@ +NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PYTHON = @PYTHON@ +PYTHONS = @PYTHONS@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_LDFLAGS = @PYTHON_LDFLAGS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +QTCHOOSER = @QTCHOOSER@ +RANLIB = @RANLIB@ +RC = @RC@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SWIG = @SWIG@ +SWIG_LIB = @SWIG_LIB@ +SYSROOT = @SYSROOT@ +VERSION = @VERSION@ +VERSION_MAJOR = @VERSION_MAJOR@ +VERSION_MICRO = @VERSION_MICRO@ +VERSION_MINOR = @VERSION_MINOR@ +VERSION_NUMBER = @VERSION_NUMBER@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +emacs_local_vars_begin = @emacs_local_vars_begin@ +emacs_local_vars_end = @emacs_local_vars_end@ +emacs_local_vars_read_only = @emacs_local_vars_read_only@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ + +# Created by: +# find . -type f -print | sed 's/^.\// /;$q;s/$/ \\/' | sort +EXTRA_DIST = callbacks.py \ + constants/create.py \ + constants/data/encoding.py \ + constants/data/__init__.py \ + constants/event.py \ + constants/import_type.py \ + constants/__init__.py \ + constants/keylist/__init__.py \ + constants/keylist/mode.py \ + constants/keysign.py \ + constants/md.py \ + constants/pk.py \ + constants/protocol.py \ + constants/sig/__init__.py \ + constants/sig/mode.py \ + constants/sig/notation.py \ + constants/sigsum.py \ + constants/status.py \ + constants/tofu/__init__.py \ + constants/tofu/policy.py \ + constants/validity.py \ + core.py \ + errors.py \ + __init__.py \ + results.py \ + util.py + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/python/src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu lang/python/src/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lang/python/src/core.py b/lang/python/src/core.py index c096ee7..996c3b0 100644 --- a/lang/python/src/core.py +++ b/lang/python/src/core.py @@ -342,10 +342,12 @@ class Context(GpgmeWrapper): Decrypt the given ciphertext and verify any signatures. If VERIFY is an iterable of keys, the ciphertext must be signed - by all those keys, otherwise an error is raised. Note: if - VERIFY is an empty iterable, that is treated the same as - passing verify=True (that is, do verify signatures, but no - specific keys are required). + by all those keys, otherwise a MissingSignatures error is + raised. Note: if VERIFY is an empty iterable, that is treated + the same as passing verify=True (that is, verify signatures + and return data about any valid signatures found, but no + signatures are required and no MissingSignatures error will be + raised). If the ciphertext is symmetrically encrypted using a passphrase, that passphrase can be given as parameter, using a @@ -353,21 +355,21 @@ class Context(GpgmeWrapper): pinentry. Keyword arguments: - sink -- write result to sink instead of returning it - passphrase -- for symmetric decryption - verify -- check signatures (boolean or iterable of keys, + sink -- write result to sink instead of returning it + passphrase -- for symmetric decryption + verify -- check signatures (boolean or iterable of keys, see above) (default True) Returns: - plaintext -- the decrypted data (or None if sink is given) - result -- additional information about the decryption - verify_result -- additional information about the signature(s) + plaintext -- the decrypted data (or None if sink is given) + result -- additional information about the decryption + verify_result -- additional information about the valid + signature(s) found Raises: UnsupportedAlgorithm -- if an unsupported algorithm was used - BadSignatures -- if a bad signature is encountered - MissingSignatures -- if expected signatures are missing or bad - GPGMEError -- as signaled by the underlying library + MissingSignatures -- if expected signatures are missing or bad + GPGMEError -- as signaled by the underlying library """ do_sig_verification = False @@ -430,13 +432,8 @@ class Context(GpgmeWrapper): results=results) if do_sig_verification: - # FIXME: should we really throw BadSignature, even if - # we've encountered some good signatures? as above, once - # we hit this error, there is no way to accept it and - # continue to process the remaining signatures. - if any(s.status != errors.NO_ERROR - for s in verify_result.signatures): - raise errors.BadSignatures(verify_result, results=results) + # filter out all invalid signatures + verify_result.signatures = list(filter(lambda s: s.status == errors.NO_ERROR, verify_result.signatures)) if required_keys is not None: missing = [] for key in required_keys: diff --git a/lang/python/src/errors.py b/lang/python/src/errors.py index 9c7f037..5a58dea 100644 --- a/lang/python/src/errors.py +++ b/lang/python/src/errors.py @@ -163,13 +163,13 @@ class InvalidRecipients(EncryptionError): for r in self.recipients) -class DeryptionError(GpgError): +class DecryptionError(GpgError): pass -class UnsupportedAlgorithm(DeryptionError): +class UnsupportedAlgorithm(DecryptionError): def __init__(self, algorithm, **kwargs): - DeryptionError.__init__(self, **kwargs) + DecryptionError.__init__(self, **kwargs) self.algorithm = algorithm def __str__(self): diff --git a/lang/python/tests/Makefile.am b/lang/python/tests/Makefile.am index d5b6e00..026df03 100644 --- a/lang/python/tests/Makefile.am +++ b/lang/python/tests/Makefile.am @@ -28,7 +28,7 @@ TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) \ srcdir=$(srcdir) \ LD_LIBRARY_PATH="../../../src/.libs:$(LD_LIBRARY_PATH)" -py_tests = t-wrapper.py \ +py_tests ?= t-wrapper.py \ t-callbacks.py \ t-data.py \ t-encrypt.py \ @@ -113,8 +113,7 @@ pubring-stamp: $(test_srcdir)/pubdemo.asc gpg-sample.stamp gpg.conf: # This is required for t-sig-notations. echo no-force-v3-sigs > ./gpg.conf - echo ignore-invalid-option agent-program >> ./gpg.conf - echo "agent-program `which $(GPG_AGENT)`|--debug-quick-random" >> ./gpg.conf + echo default-key A0FF4590BB6122EDEF6E3C542D727CC768697734 >> ./gpg.conf gpg-agent.conf: # This is required for gpg2, which does not support command fd. diff --git a/lang/python/tests/Makefile.in b/lang/python/tests/Makefile.in index 3e1f9dd..e41c71f 100644 --- a/lang/python/tests/Makefile.in +++ b/lang/python/tests/Makefile.in @@ -355,34 +355,6 @@ TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) \ srcdir=$(srcdir) \ LD_LIBRARY_PATH="../../../src/.libs:$(LD_LIBRARY_PATH)" -py_tests = t-wrapper.py \ - t-callbacks.py \ - t-data.py \ - t-encrypt.py \ - t-encrypt-sym.py \ - t-encrypt-sign.py \ - t-sign.py \ - t-signers.py \ - t-decrypt.py \ - t-verify.py \ - t-decrypt-verify.py \ - t-sig-notation.py \ - t-export.py \ - t-import.py \ - t-trustlist.py \ - t-edit.py \ - t-keylist.py \ - t-keylist-from-data.py \ - t-wait.py \ - t-encrypt-large.py \ - t-file-name.py \ - t-idiomatic.py \ - t-protocol-assuan.py \ - t-quick-key-creation.py \ - t-quick-subkey-creation.py \ - t-quick-key-manipulation.py \ - t-quick-key-signing.py - XTESTS = initial.py $(py_tests) final.py EXTRA_DIST = support.py $(XTESTS) encrypt-only.asc sign-only.asc \ run-tests.py @@ -599,6 +571,34 @@ uninstall-am: .PRECIOUS: Makefile +py_tests ?= t-wrapper.py \ + t-callbacks.py \ + t-data.py \ + t-encrypt.py \ + t-encrypt-sym.py \ + t-encrypt-sign.py \ + t-sign.py \ + t-signers.py \ + t-decrypt.py \ + t-verify.py \ + t-decrypt-verify.py \ + t-sig-notation.py \ + t-export.py \ + t-import.py \ + t-trustlist.py \ + t-edit.py \ + t-keylist.py \ + t-keylist-from-data.py \ + t-wait.py \ + t-encrypt-large.py \ + t-file-name.py \ + t-idiomatic.py \ + t-protocol-assuan.py \ + t-quick-key-creation.py \ + t-quick-subkey-creation.py \ + t-quick-key-manipulation.py \ + t-quick-key-signing.py + # XXX: Currently, one cannot override automake's 'check' target. As a # workaround, we avoid defining 'TESTS', thus automake will not emit # the 'check' target. For extra robustness, we merely define a @@ -636,8 +636,7 @@ pubring-stamp: $(test_srcdir)/pubdemo.asc gpg-sample.stamp gpg.conf: # This is required for t-sig-notations. echo no-force-v3-sigs > ./gpg.conf - echo ignore-invalid-option agent-program >> ./gpg.conf - echo "agent-program `which $(GPG_AGENT)`|--debug-quick-random" >> ./gpg.conf + echo default-key A0FF4590BB6122EDEF6E3C542D727CC768697734 >> ./gpg.conf gpg-agent.conf: # This is required for gpg2, which does not support command fd. diff --git a/lang/python/tests/t-decrypt-verify.py b/lang/python/tests/t-decrypt-verify.py index a0049a0..5307da7 100755 --- a/lang/python/tests/t-decrypt-verify.py +++ b/lang/python/tests/t-decrypt-verify.py @@ -75,3 +75,15 @@ with gpg.Context() as c: assert e.missing[0] == bob else: assert False, "Expected an error, got none" + +# plaintext, _, verify_result = c.decrypt(open(support.make_filename("cipher-no-sig.asc"))) +# assert len(plaintext) > 0 +# assert len(verify_result.signatures) == 0 +# assert plaintext.find(b'Viscosity Dispersal Thimble Saturday Flaxseed Deflected') >= 0, \ +# 'unsigned Plaintext was not found' +# +# plaintext, _, verify_result = c.decrypt(open(support.make_filename("cipher-3.asc"))) +# assert len(plaintext) > 0 +# assert len(verify_result.signatures) == 1 +# assert plaintext.find(b'Reenact Studied Thermos Bonehead Unclasp Opposing') >= 0, \ +# 'second Plaintext not found' diff --git a/lang/python/tests/t-decrypt.py b/lang/python/tests/t-decrypt.py index c72b51a..9900274 100755 --- a/lang/python/tests/t-decrypt.py +++ b/lang/python/tests/t-decrypt.py @@ -42,3 +42,13 @@ with gpg.Context() as c: assert len(plaintext) > 0 assert plaintext.find(b'Wenn Sie dies lesen k') >= 0, \ 'Plaintext not found' + + plaintext, _, _ = c.decrypt(open(support.make_filename("cipher-3.asc")), verify=False) + assert len(plaintext) > 0 + assert plaintext.find(b'Reenact Studied Thermos Bonehead Unclasp Opposing') >= 0, \ + 'second Plaintext not found' + + plaintext, _, _ = c.decrypt(open(support.make_filename("cipher-no-sig.asc")), verify=False) + assert len(plaintext) > 0 + assert plaintext.find(b'Viscosity Dispersal Thimble Saturday Flaxseed Deflected') >= 0, \ + 'third Plaintext was not found' diff --git a/src/assuan-support.c b/src/assuan-support.c index 925aeba..0ddf29b 100644 --- a/src/assuan-support.c +++ b/src/assuan-support.c @@ -54,7 +54,7 @@ _gpgme_assuan_log_cb (assuan_context_t ctx, void *hook, if (msg == NULL) return 1; - _gpgme_debug (DEBUG_ASSUAN, -1, NULL, NULL, NULL, "%s", msg); + _gpgme_debug (NULL, DEBUG_ASSUAN, -1, NULL, NULL, NULL, "%s", msg); return 0; } @@ -234,7 +234,7 @@ my_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, break; } logger_fd = strtol (argv[loc], &tail, 10); - if (tail == argv[loc] || logger_fd <= 0) + if (tail == argv[loc] || logger_fd < 0) { err = GPG_ERR_INV_ARG; break; diff --git a/src/cJSON.c b/src/cJSON.c index 101d556..f233848 100644 --- a/src/cJSON.c +++ b/src/cJSON.c @@ -40,6 +40,7 @@ #include <stdio.h> #include <math.h> #include <stdlib.h> +#include <stdint.h> #include <float.h> #include <limits.h> #include <ctype.h> @@ -146,8 +147,13 @@ cJSON_Delete (cJSON * c) static const char * parse_number (cJSON * item, const char *num) { - double n = 0, sign = 1, scale = 0; - int subscale = 0, signsubscale = 1; + int subscale = 0; + int signsubscale = 1; + double n = 0; + double sign = 1; + double scale = 0; + double dblmin = INT32_MIN; + double dblmax = INT32_MAX; if (*num == '-') sign = -1, num++; /* Has sign? */ @@ -169,17 +175,37 @@ parse_number (cJSON * item, const char *num) num++; if (*num == '+') num++; - else if (*num == '-') - signsubscale = -1, num++; /* With sign? */ + else if (*num == '-') /* With sign? */ + signsubscale = -1, num++; while (*num >= '0' && *num <= '9') - subscale = (subscale * 10) + (*num++ - '0'); /* Number? */ + { + if ((10 * (double)subscale) > dblmax) + break; + subscale = (subscale * 10) + (*num++ - '0'); + } } /* number = +/- number.fraction * 10^+/- exponent */ n = sign * n * pow (10.0, (scale + subscale * signsubscale)); - item->valuedouble = n; - item->valueint = (int) n; + /* For NAN set both parts to 0. For out of range values let the + * integer part be 0. */ + if (isnan (n) || isinf (n)) + { + item->valuedouble = 0; + item->valueint = 0; + } + else if (n > dblmax || n < dblmin) + { + item->valuedouble = n; + item->valueint = 0; + } + else + { + item->valuedouble = n; + item->valueint = (int)n; + } + item->type = cJSON_Number; return num; } @@ -190,6 +216,8 @@ print_number (cJSON * item) { char *str; double d = item->valuedouble; + int i; + if (fabs (((double) item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) { @@ -203,7 +231,11 @@ print_number (cJSON * item) str = xtrymalloc (64); /* This is a nice tradeoff. */ if (str) { - if (fabs (floor (d) - d) <= DBL_EPSILON && fabs (d) < 1.0e60) + if (isnan (d)) + strcpy (str, "nan"); + else if ((i = isinf (d))) + strcpy (str, i > 0? "inf" : ":-inf"); + else if (fabs (floor (d) - d) <= DBL_EPSILON && fabs (d) < 1.0e60) sprintf (str, "%.0f", d); else if (fabs (d) < 1.0e-6 || fabs (d) > 1.0e9) sprintf (str, "%e", d); diff --git a/src/data-compat.c b/src/data-compat.c index 64ed2d2..4960bf4 100644 --- a/src/data-compat.c +++ b/src/data-compat.c @@ -160,7 +160,7 @@ gpgme_error_to_errno (gpgme_error_t err) break; } } - TRACE (DEBUG_DATA, "gpgme:gpgme_error_to_errno", 0, + TRACE (DEBUG_DATA, "gpgme:gpgme_error_to_errno", NULL, "mapping %s <%s> to: %s", gpgme_strerror (err), gpgme_strsource (err), strerror (res)); gpg_err_set_errno (res); diff --git a/src/data-fd.c b/src/data-fd.c index 5c68130..4bc8f61 100644 --- a/src/data-fd.c +++ b/src/data-fd.c @@ -75,7 +75,7 @@ gpgme_error_t gpgme_data_new_from_fd (gpgme_data_t *r_dh, int fd) { gpgme_error_t err; - TRACE_BEG (DEBUG_DATA, "gpgme_data_new_from_fd", r_dh, "fd=0x%x", fd); + TRACE_BEG (DEBUG_DATA, "gpgme_data_new_from_fd", r_dh, "fd=%d", fd); err = _gpgme_data_new (r_dh, &fd_cbs); if (err) diff --git a/src/data-mem.c b/src/data-mem.c index f51d2fd..539b453 100644 --- a/src/data-mem.c +++ b/src/data-mem.c @@ -296,7 +296,7 @@ gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) void gpgme_free (void *buffer) { - TRACE (DEBUG_DATA, "gpgme_free", buffer, ""); + TRACE (DEBUG_DATA, "gpgme_free", NULL, "p=%p", buffer); if (buffer) free (buffer); @@ -574,7 +574,7 @@ _gpgme_data_inbound_handler (void *opaque, int fd) char *bufp = buffer; gpgme_ssize_t buflen; TRACE_BEG (DEBUG_CTX, "_gpgme_data_inbound_handler", dh, - "fd=0x%x", fd); + "fd=%d", fd); buflen = _gpgme_io_read (fd, buffer, BUFFER_SIZE); if (buflen < 0) @@ -605,7 +605,7 @@ _gpgme_data_outbound_handler (void *opaque, int fd) gpgme_data_t dh = (gpgme_data_t) data->handler_value; gpgme_ssize_t nwritten; TRACE_BEG (DEBUG_CTX, "_gpgme_data_outbound_handler", dh, - "fd=0x%x", fd); + "fd=%d", fd); if (!dh->pending_len) { diff --git a/src/debug.c b/src/debug.c index 81c2a90..d5d11bd 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1,6 +1,6 @@ /* debug.c - helpful output in desperate situations * Copyright (C) 2000 Werner Koch (dd9jn) - * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH + * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009, 2019 g10 Code GmbH * * This file is part of GPGME. * @@ -50,10 +50,6 @@ #include "debug.h" -/* Lock to serialize initialization of the debug output subsystem and - output of actual debug messages. */ -DEFINE_STATIC_LOCK (debug_lock); - /* The amount of detail requested by the user, per environment variable GPGME_DEBUG. */ static int debug_level; @@ -135,7 +131,6 @@ debug_init (void) { static int initialized; - LOCK (debug_lock); if (!initialized) { gpgme_error_t err; @@ -152,10 +147,7 @@ debug_init (void) { err = _gpgme_getenv ("GPGME_DEBUG", &e); if (err) - { - UNLOCK (debug_lock); - return; - } + return; } initialized = 1; @@ -201,16 +193,15 @@ debug_init (void) free (e); } } - UNLOCK (debug_lock); if (debug_level > 0) { - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme_debug: level=%d\n", debug_level); #ifdef HAVE_W32_SYSTEM { const char *name = _gpgme_get_inst_dir (); - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme_debug: gpgme='%s'\n", name? name: "?"); } #endif @@ -219,9 +210,9 @@ debug_init (void) -/* This should be called as soon as the locks are initialized. It is - required so that the assuan logging gets conncted to the gpgme log - stream as early as possible. */ +/* This should be called as soon as possible. It is required so that + * the assuan logging gets connected to the gpgme log stream as early + * as possible. */ void _gpgme_debug_subsystem_init (void) { @@ -240,6 +231,10 @@ _gpgme_debug_subsystem_init (void) * 2 = debug a function (used by macro TRACE_LOG) * 3 = leave a function (used by macro TRACE_SUC) * + * If LINE is not NULL the output will be stored in that variabale but + * without a LF. _gpgme_debug_add can be used to add more and + * _gpgme_debug_end to finally output it. + * * Returns: 0 * * Note that we always return 0 because the old TRACE macro evaluated @@ -249,96 +244,96 @@ _gpgme_debug_subsystem_init (void) * value from the TRACE macros are actually used somewhere. */ int -_gpgme_debug (int level, int mode, const char *func, const char *tagname, +_gpgme_debug (void **line, int level, int mode, + const char *func, const char *tagname, const char *tagvalue, const char *format, ...) { va_list arg_ptr; int saved_errno; int need_lf; + int indent; + char *prefix, *stdinfo, *userinfo; + const char *modestr; + int no_userinfo = 0; if (debug_level < level) return 0; +#ifdef FRAME_NR + indent = frame_nr > 0? (2 * (frame_nr - 1)):0; +#else + indent = 0; +#endif + saved_errno = errno; va_start (arg_ptr, format); - LOCK (debug_lock); { struct tm *tp; time_t atime = time (NULL); tp = localtime (&atime); - fprintf (errfp, "GPGME %04d-%02d-%02d %02d:%02d:%02d <0x%04llx> ", - 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, - tp->tm_hour, tp->tm_min, tp->tm_sec, - (unsigned long long) ath_self ()); + prefix = gpgrt_bsprintf ("GPGME %04d%02d%02dT%02d%02d%02d %04llX %*s", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec, + (unsigned long long) ath_self (), + indent < 40? indent : 40, ""); } -#ifdef FRAME_NR - { - int indent; - - indent = frame_nr > 0? (2 * (frame_nr - 1)):0; - fprintf (errfp, "%*s", indent < 40? indent : 40, ""); - } -#endif - need_lf = 0; switch (mode) { - case -1: /* Do nothing. */ - break; - case 0: - fprintf (errfp, "%s: call: %s=%p ", func, tagname, tagvalue); - break; - case 1: - fprintf (errfp, "%s: enter: %s=%p ", func, tagname, tagvalue); - break; - case 2: - fprintf (errfp, "%s: check: %s=%p ", func, tagname, tagvalue); - break; - case 3: - if (tagname) - fprintf (errfp, "%s: leave: %s=%p ", func, tagname, tagvalue); - else - fprintf (errfp, "%s: leave: ", func); - break; - default: - fprintf (errfp, "%s: ?(mode=%d): %s=%p ", func, mode, tagname, tagvalue); - break; + case -1: modestr = NULL; break; /* Do nothing. */ + case 0: modestr = "call"; break; + case 1: modestr = "enter"; break; + case 2: modestr = "check"; break; + case 3: modestr = "leave"; break; + default: modestr = "mode?"; break; } - need_lf = (mode != -1 && (!format || !*format)); - - vfprintf (errfp, format, arg_ptr); - va_end (arg_ptr); - if (need_lf || (format && *format && format[strlen (format) - 1] != '\n')) - putc ('\n', errfp); - UNLOCK (debug_lock); - fflush (errfp); - - gpg_err_set_errno (saved_errno); - return 0; -} + if (!modestr) + stdinfo = NULL; + else if (tagname && strcmp (tagname, XSTRINGIFY (NULL))) + stdinfo = gpgrt_bsprintf ("%s: %s: %s=%p ", func,modestr,tagname,tagvalue); + else + stdinfo = gpgrt_bsprintf ("%s: %s: ", func, modestr); -/* Start a new debug line in *LINE, logged at level LEVEL or higher, - and starting with the formatted string FORMAT. */ -void -_gpgme_debug_begin (void **line, int level, const char *format, ...) -{ - va_list arg_ptr; - int res; + if (format && *format) + userinfo = gpgrt_vbsprintf (format, arg_ptr); + else + { + userinfo = NULL; + no_userinfo = 1; + } + va_end (arg_ptr); - if (debug_level < level) + if (mode != -1 && (!format || !*format)) + need_lf = 1; + else if (userinfo && *userinfo && userinfo[strlen (userinfo) - 1] != '\n') + need_lf = 1; + else + need_lf = 0; + + if (line) + *line = gpgrt_bsprintf ("%s%s%s", + prefix? prefix : "GPGME out-of-core ", + !modestr? "" : stdinfo? stdinfo : + (!format || !*format)? "" :"out-of-core ", + userinfo? userinfo : "out-of-core"); + else { - /* Disable logging of this line. */ - *line = NULL; - return; + fprintf (errfp, "%s%s%s%s", + prefix? prefix : "GPGME out-of-core ", + !modestr? "" : stdinfo? stdinfo : + (!format || !*format)? "" :"out-of-core ", + userinfo? userinfo : no_userinfo? "" : "out-of-core", + need_lf? "\n":""); + fflush (errfp); } - va_start (arg_ptr, format); - res = gpgrt_vasprintf ((char **) line, format, arg_ptr); - va_end (arg_ptr); - if (res < 0) - *line = NULL; + gpgrt_free (userinfo); + gpgrt_free (stdinfo); + gpgrt_free (prefix); + gpg_err_set_errno (saved_errno); + return 0; } @@ -377,12 +372,16 @@ _gpgme_debug_add (void **line, const char *format, ...) void _gpgme_debug_end (void **line) { + const char *string; + if (!*line) return; + string = *line; - /* The smallest possible level is 1, so force logging here by - using that. */ - _gpgme_debug (1, -1, NULL, NULL, NULL, "%s", (char*)*line); + fprintf (errfp, "%s%s", + string, + (*string && string[strlen (string)-1] != '\n')? "\n":""); + fflush (errfp); gpgrt_free (*line); *line = NULL; } @@ -404,33 +403,65 @@ _gpgme_debug_buffer (int lvl, const char *const fmt, if (!buffer) return; - while (idx < len) + if (lvl > 9) { - char str[51]; - char *strp = str; - char *strp2 = &str[34]; + while (idx < len) + { + char str[51]; + char *strp = str; + char *strp2 = &str[34]; - for (j = 0; j < 16; j++) - { - unsigned char val; - if (idx < len) - { - val = buffer[idx++]; - *(strp++) = TOHEX (val >> 4); - *(strp++) = TOHEX (val % 16); - *(strp2++) = isprint (val) ? val : '.'; - } - else - { - *(strp++) = ' '; - *(strp++) = ' '; - } - if (j == 7) - *(strp++) = ' '; - } - *(strp++) = ' '; - *(strp2) = '\0'; + for (j = 0; j < 16; j++) + { + unsigned char val; + if (idx < len) + { + val = buffer[idx++]; + *(strp++) = TOHEX (val >> 4); + *(strp++) = TOHEX (val % 16); + *(strp2++) = isprint (val)? val : '.'; + } + else + { + *(strp++) = ' '; + *(strp++) = ' '; + } + if (j == 7) + *(strp++) = ' '; + } + *(strp++) = ' '; + *(strp2) = '\0'; - _gpgme_debug (lvl, -1, NULL, NULL, NULL, fmt, func, str); + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, fmt, func, str); + } + } + else + { + while (idx < len) + { + char str[48+4+1]; + char *strp = str; + + for (j = 0; j < 48; j++) + { + unsigned char val; + if (idx < len) + { + val = buffer[idx++]; + if (val == '\n') + { + *strp++ = '<'; + *strp++ = 'L'; + *strp++ = 'F'; + *strp++ = '>'; + break; + } + *strp++ = (val > 31 && val < 127)? val : '.'; + } + } + *strp = 0; + + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, fmt, func, str); + } } } diff --git a/src/debug.h b/src/debug.h index 7ef8cf2..fa0bfc6 100644 --- a/src/debug.h +++ b/src/debug.h @@ -25,6 +25,7 @@ #ifdef HAVE_STDINT_H #include <stdint.h> #endif +#include <errno.h> #include "gpgme.h" /* Required for gpgme_error stuff. */ @@ -68,15 +69,11 @@ int _gpgme_debug_set_debug_envvar (const char *value); void _gpgme_debug_subsystem_init (void); /* Log the formatted string FORMAT at debug level LEVEL or higher. */ -int _gpgme_debug (int level, int mode, +int _gpgme_debug (void **line, int level, int mode, const char *func, const char *tagname, const char *tagvalue, - const char *format, ...) GPGRT_ATTR_PRINTF(6,7); + const char *format, ...) GPGRT_ATTR_PRINTF(7,8); -/* Start a new debug line in *LINE, logged at level LEVEL or higher, - and starting with the formatted string FORMAT. */ -void _gpgme_debug_begin (void **helper, int level, const char *format, ...); - /* Add the formatted string FORMAT to the debug line *LINE. */ void _gpgme_debug_add (void **helper, const char *format, ...); @@ -94,7 +91,7 @@ int _gpgme_debug_frame_end (void); static inline gpgme_error_t _gpgme_trace_gpgme_error (gpgme_error_t err, const char *file, int line) { - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "%s:%d: returning error: %s\n", _gpgme_debug_srcname (file), line, gpgme_strerror (err)); return err; @@ -116,13 +113,13 @@ _gpgme_trace_gpgme_error (gpgme_error_t err, const char *file, int line) /* Note: We can't protect this with a do-while block. */ #define TRACE_BEG(lvl, name, tag, ...) \ _TRACE (lvl, name, tag); \ - _gpgme_debug (_gpgme_trace_level, 1, \ + _gpgme_debug (NULL, _gpgme_trace_level, 1, \ _gpgme_trace_func, _gpgme_trace_tagname, _gpgme_trace_tag, \ __VA_ARGS__) #define TRACE(lvl, name, tag, ...) do { \ _gpgme_debug_frame_begin (); \ - _gpgme_debug (lvl, 0, name, STRINGIFY (tag), (void *)(uintptr_t)tag, \ + _gpgme_debug (NULL, lvl, 0, name, STRINGIFY (tag), (void *)(uintptr_t)tag, \ __VA_ARGS__); \ _gpgme_debug_frame_end (); \ } while (0) @@ -135,9 +132,9 @@ static inline gpg_error_t _trace_err (gpg_error_t err, int lvl, const char *func, int line) { if (!err) - _gpgme_debug (lvl, 3, func, NULL, NULL, ""); + _gpgme_debug (NULL, lvl, 3, func, NULL, NULL, ""); else - _gpgme_debug (lvl, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, "%s:%d: error: %s <%s>\n", func, line, gpgme_strerror (err), gpgme_strsource (err)); _gpgme_debug_frame_end (); @@ -151,11 +148,11 @@ static inline int _trace_sysres (int res, int lvl, const char *func, int line) { if (res >= 0) - _gpgme_debug (lvl, 3, func, NULL, NULL, "result=%d", res); + _gpgme_debug (NULL, lvl, 3, func, NULL, NULL, "result=%d", res); else - _gpgme_debug (lvl, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, "%s:%d: error: %s (%d)\n", - func, line, strerror (res), res); + func, line, strerror (errno), errno); _gpgme_debug_frame_end (); return res; } @@ -167,9 +164,9 @@ static inline int _trace_syserr (int rc, int lvl, const char *func, int line) { if (!rc) - _gpgme_debug (lvl, 3, func, NULL, NULL, "result=0"); + _gpgme_debug (NULL, lvl, 3, func, NULL, NULL, "result=0"); else - _gpgme_debug (lvl, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, "%s:%d: error: %s (%d)\n", func, line, strerror (rc), rc); _gpgme_debug_frame_end (); @@ -177,13 +174,13 @@ _trace_syserr (int rc, int lvl, const char *func, int line) } #define TRACE_SUC(...) do { \ - _gpgme_debug (_gpgme_trace_level, 3, _gpgme_trace_func, NULL, NULL, \ + _gpgme_debug (NULL, _gpgme_trace_level, 3, _gpgme_trace_func, NULL, NULL, \ __VA_ARGS__); \ _gpgme_debug_frame_end (); \ } while (0) #define TRACE_LOG(...) do { \ - _gpgme_debug (_gpgme_trace_level, 2, \ + _gpgme_debug (NULL, _gpgme_trace_level, 2, \ _gpgme_trace_func, _gpgme_trace_tagname, _gpgme_trace_tag, \ __VA_ARGS__); \ } while (0) @@ -198,10 +195,9 @@ _trace_syserr (int rc, int lvl, const char *func, int line) _gpgme_trace_func, buf, len); \ } while (0) -#define TRACE_SEQ(hlp,fmt) do { \ - _gpgme_debug_begin (&(hlp), _gpgme_trace_level, \ - "%s: check: %s=%p, " fmt, _gpgme_trace_func, \ - _gpgme_trace_tagname, _gpgme_trace_tag); \ +#define TRACE_SEQ(hlp,...) do { \ + _gpgme_debug (&(hlp), _gpgme_trace_level, 2, _gpgme_trace_func, \ + _gpgme_trace_tagname, _gpgme_trace_tag, __VA_ARGS__); \ } while (0) #define TRACE_ADD0(hlp,fmt) \ diff --git a/src/dirinfo.c b/src/dirinfo.c index d481199..c4f0e4a 100644 --- a/src/dirinfo.c +++ b/src/dirinfo.c @@ -260,15 +260,15 @@ get_gpgconf_item (int what) char *pgmname; pgmname = dirinfo.disable_gpgconf? NULL : _gpgme_get_gpgconf_path (); - if (pgmname && access (pgmname, F_OK)) + if (pgmname && _gpgme_access (pgmname, F_OK)) { - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpgconf='%s' [not installed]\n", pgmname); free (pgmname); pgmname = NULL; /* Not available. */ } else - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpgconf='%s'\n", pgmname? pgmname : "[null]"); if (!pgmname) @@ -295,35 +295,35 @@ get_gpgconf_item (int what) allocated. */ dirinfo.valid = 1; if (dirinfo.gpg_name) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpg='%s'\n", dirinfo.gpg_name); if (dirinfo.g13_name) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: g13='%s'\n", dirinfo.g13_name); if (dirinfo.gpgsm_name) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpgsm='%s'\n", dirinfo.gpgsm_name); if (dirinfo.homedir) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: homedir='%s'\n", dirinfo.homedir); if (dirinfo.agent_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL,DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: agent='%s'\n", dirinfo.agent_socket); if (dirinfo.agent_ssh_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: ssh='%s'\n", dirinfo.agent_ssh_socket); if (dirinfo.dirmngr_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: dirmngr='%s'\n", dirinfo.dirmngr_socket); if (dirinfo.uisrv_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: uisrv='%s'\n", dirinfo.uisrv_socket); } diff --git a/src/engine-assuan.c b/src/engine-assuan.c index 79e826e..497397d 100644 --- a/src/engine-assuan.c +++ b/src/engine-assuan.c @@ -658,7 +658,7 @@ add_io_cb (engine_llass_t llass, iocb_data_t *iocbd, gpgme_io_cb_t handler) gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-assuan:add_io_cb", llass, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*llass->io_cbs.add) (llass->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); diff --git a/src/engine-g13.c b/src/engine-g13.c index edb8d54..19dd8f4 100644 --- a/src/engine-g13.c +++ b/src/engine-g13.c @@ -639,7 +639,7 @@ add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler) gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-g13:add_io_cb", g13, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*g13->io_cbs.add) (g13->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 31d219a..dc2d945 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -2075,6 +2075,8 @@ append_args_from_recipients_string (engine_gpg_t gpg, file = 0; flags = orig_flags; } + else if (!ignore && n > 2 && !memcmp (string, "--", 2)) + err = gpg_error (GPG_ERR_UNKNOWN_OPTION); else if (n) /* Not empty - use it. */ { err = add_arg (gpg, file? (hidden? "-F":"-f") : (hidden? "-R":"-r")); diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index 396f19c..ae5d8ef 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -549,8 +549,6 @@ gpgsm_new (void **engine, const char *file_name, const char *home_dir, || _gpgme_io_set_close_notify (gpgsm->output_cb.fd, close_notify_handler, gpgsm) || _gpgme_io_set_close_notify (gpgsm->message_cb.fd, - close_notify_handler, gpgsm) - || _gpgme_io_set_close_notify (gpgsm->diag_cb.fd, close_notify_handler, gpgsm))) { err = gpg_error (GPG_ERR_GENERAL); @@ -1129,7 +1127,7 @@ add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler) gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", gpgsm, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); @@ -1409,11 +1407,12 @@ set_recipients_from_string (engine_gpgsm_t gpgsm, const char *string) { gpg_error_t err = 0; char *line = NULL; - int no_pubkey = 0; + int ignore = 0; + int any = 0; const char *s; int n; - for (;;) + do { while (*string == ' ' || *string == '\t') string++; @@ -1428,25 +1427,32 @@ set_recipients_from_string (engine_gpgsm_t gpgsm, const char *string) while (n && (string[n-1] == ' ' || string[n-1] == '\t')) n--; - gpgrt_free (line); - if (gpgrt_asprintf (&line, "RECIPIENT %.*s", n, string) < 0) + if (!ignore && n == 2 && !memcmp (string, "--", 2)) + ignore = 1; + else if (!ignore && n > 2 && !memcmp (string, "--", 2)) + err = gpg_error (GPG_ERR_UNKNOWN_OPTION); + else if (n) /* Not empty - use it. */ { - err = gpg_error_from_syserror (); - break; + gpgrt_free (line); + if (gpgrt_asprintf (&line, "RECIPIENT %.*s", n, string) < 0) + err = gpg_error_from_syserror (); + else + { + err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc, + gpgsm->status.fnc_value); + if (!err) + any = 1; + } } - string += n + !!s; - - err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc, - gpgsm->status.fnc_value); - /* Fixme: Improve error reporting. */ - if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) - no_pubkey++; - else if (err) - break; + string += n + !!s; } + while (!err); + + if (!err && !any) + err = gpg_error (GPG_ERR_MISSING_KEY); gpgrt_free (line); - return err? err : no_pubkey? gpg_error (GPG_ERR_NO_PUBKEY) : 0; + return err; } @@ -1460,7 +1466,7 @@ gpgsm_encrypt (void *engine, gpgme_key_t recp[], const char *recpstring, if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); - if (!recp) + if (!recp && !recpstring) /* Symmetric only */ return gpg_error (GPG_ERR_NOT_IMPLEMENTED); if ((flags & GPGME_ENCRYPT_NO_ENCRYPT_TO)) diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c index 62c4e5b..cb8e155 100644 --- a/src/engine-uiserver.c +++ b/src/engine-uiserver.c @@ -879,7 +879,7 @@ add_io_cb (engine_uiserver_t uiserver, iocb_data_t *iocbd, gpgme_io_cb_t handler gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-uiserver:add_io_cb", uiserver, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*uiserver->io_cbs.add) (uiserver->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); diff --git a/src/engine.c b/src/engine.c index b3df01a..05979c1 100644 --- a/src/engine.c +++ b/src/engine.c @@ -461,6 +461,7 @@ _gpgme_set_engine_info (gpgme_engine_info_t info, gpgme_protocol_t proto, { free (new_file_name); free (new_home_dir); + return gpg_error_from_syserror (); } } diff --git a/src/posix-io.c b/src/posix-io.c index be08431..e712ef2 100644 --- a/src/posix-io.c +++ b/src/posix-io.c @@ -62,6 +62,70 @@ #include "debug.h" +#ifdef USE_LINUX_GETDENTS +/* This is not declared in public headers; getdents64(2) says that we must + * define it ourselves. */ +struct linux_dirent64 +{ + ino64_t d_ino; + off64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; +}; + +# define DIR_BUF_SIZE 1024 +#endif /*USE_LINUX_GETDENTS*/ + + +/* Return true if FD is valid file descriptor. */ +#if 0 +int +_gpgme_is_fd_valid (int fd) +{ + int dir_fd; + char dir_buf[DIR_BUF_SIZE]; + struct linux_dirent64 *dir_entry; + int r, pos, x; + const char *s; + int result = 0; + + dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY); + if (dir_fd != -1) + { + for (;;) + { + r = syscall(SYS_getdents64, dir_fd, dir_buf, DIR_BUF_SIZE); + if (r == -1) + break; /* Ooops */ + if (r == 0) + break; + + for (pos = 0; pos < r; pos += dir_entry->d_reclen) + { + dir_entry = (struct linux_dirent64 *) (dir_buf + pos); + s = dir_entry->d_name; + if (*s < '0' || *s > '9') + continue; + /* atoi is not guaranteed to be async-signal-safe. */ + for (x = 0; *s >= '0' && *s <= '9'; s++) + x = x * 10 + (*s - '0'); + if (*s) + continue; /* Does not look like a file descriptor. */ + if (x == fd) + { + result = 1; + goto leave; + } + } + } + leave: + close (dir_fd); + } + return result; +} +#endif /*0*/ + void _gpgme_io_subsystem_init (void) @@ -109,8 +173,8 @@ int _gpgme_io_read (int fd, void *buffer, size_t count) { int nread; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_read", fd, - "buffer=%p, count=%zu", buffer, count); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_read", NULL, + "fd=%d buffer=%p count=%zu", fd, buffer, count); do { @@ -127,8 +191,8 @@ int _gpgme_io_write (int fd, const void *buffer, size_t count) { int nwritten; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_write", fd, - "buffer=%p, count=%zu", buffer, count); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_write", NULL, + "fd=%d buffer=%p count=%zu", fd, buffer, count); TRACE_LOGBUFX (buffer, count); do @@ -146,7 +210,7 @@ _gpgme_io_pipe (int filedes[2], int inherit_idx) { int saved_errno; int err; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", filedes, + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", NULL, "inherit_idx=%i (GPGME uses it for %s)", inherit_idx, inherit_idx ? "reading" : "writing"); @@ -166,7 +230,7 @@ _gpgme_io_pipe (int filedes[2], int inherit_idx) if (err) return TRACE_SYSRES (err); - TRACE_SUC ("read=0x%x, write=0x%x", filedes[0], filedes[1]); + TRACE_SUC ("read fd=%d write fd=%d", filedes[0], filedes[1]); return 0; } @@ -179,7 +243,7 @@ _gpgme_io_close (int fd) void *handler_value; int idx; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", fd, ""); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", NULL, "fd=%d", fd); if (fd == -1) { @@ -221,8 +285,8 @@ _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler, int res = 0; int idx; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_close_notify", fd, - "close_handler=%p/%p", handler, value); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_close_notify", NULL, + "fd=%d close_handler=%p/%p", fd, handler, value); assert (fd != -1); @@ -272,7 +336,7 @@ _gpgme_io_set_nonblocking (int fd) { int flags; int res; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", fd, ""); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", NULL, "fd=%d", fd); flags = fcntl (fd, F_GETFL, 0); if (flags == -1) @@ -283,22 +347,6 @@ _gpgme_io_set_nonblocking (int fd) } -#ifdef USE_LINUX_GETDENTS -/* This is not declared in public headers; getdents64(2) says that we must - * define it ourselves. */ -struct linux_dirent64 -{ - ino64_t d_ino; - off64_t d_off; - unsigned short d_reclen; - unsigned char d_type; - char d_name[]; -}; - -# define DIR_BUF_SIZE 1024 -#endif /*USE_LINUX_GETDENTS*/ - - static long int get_max_fds (void) { @@ -428,7 +476,7 @@ get_max_fds (void) } #endif - TRACE (DEBUG_SYSIO, "gpgme:max_fds", 0, "max fds=%ld (%s)", fds, source); + TRACE (DEBUG_SYSIO, "gpgme:max_fds", NULL, "max fds=%ld (%s)", fds, source); return fds; } @@ -474,7 +522,7 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags, int status; int signo; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", path, + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", NULL, "path=%s", path); i = 0; while (argv[i]) @@ -483,10 +531,12 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags, i++; } for (i = 0; fd_list[i].fd != -1; i++) - if (fd_list[i].dup_to == -1) - TRACE_LOG ("fd[%i] = 0x%x", i, fd_list[i].fd); - else - TRACE_LOG ("fd[%i] = 0x%x -> 0x%x", i, fd_list[i].fd, fd_list[i].dup_to); + { + if (fd_list[i].dup_to == -1) + TRACE_LOG ("fd[%i] = 0x%x", i, fd_list[i].fd); + else + TRACE_LOG ("fd[%i] = 0x%x -> 0x%x", i,fd_list[i].fd,fd_list[i].dup_to); + } pid = fork (); if (pid == -1) @@ -654,7 +704,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock) /* Use a 1s timeout. */ struct timeval timeout = { 1, 0 }; void *dbg_help = NULL; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", fds, + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", NULL, "nfds=%zu, nonblock=%u", nfds, nonblock); FD_ZERO (&readfds); @@ -682,8 +732,8 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock) FD_SET (fds[i].fd, &readfds); if (fds[i].fd > max_fd) max_fd = fds[i].fd; - TRACE_ADD1 (dbg_help, "r0x%x ", fds[i].fd); - any = 1; + TRACE_ADD1 (dbg_help, "r=%d ", fds[i].fd); + any = 1; } else if (fds[i].for_write) { @@ -697,7 +747,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock) FD_SET (fds[i].fd, &writefds); if (fds[i].fd > max_fd) max_fd = fds[i].fd; - TRACE_ADD1 (dbg_help, "w0x%x ", fds[i].fd); + TRACE_ADD1 (dbg_help, "w=%d ", fds[i].fd); any = 1; } fds[i].signaled = 0; @@ -721,9 +771,9 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock) for (i = 0; i <= max_fd; i++) { if (FD_ISSET (i, &readfds)) - TRACE_ADD1 (dbg_help, "r0x%x ", i); + TRACE_ADD1 (dbg_help, "r=%d ", i); if (FD_ISSET (i, &writefds)) - TRACE_ADD1 (dbg_help, "w0x%x ", i); + TRACE_ADD1 (dbg_help, "w=%d ", i); } TRACE_END (dbg_help, "]"); } @@ -760,8 +810,8 @@ _gpgme_io_recvmsg (int fd, struct msghdr *msg, int flags) int nread; int saved_errno; struct iovec *iov; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_recvmsg", fd, - "msg=%p, flags=%i", msg, flags); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_recvmsg", NULL, + "fd=%d msg=%p flags=%i", fd, msg, flags); nread = 0; iov = msg->msg_iov; @@ -802,8 +852,8 @@ _gpgme_io_sendmsg (int fd, const struct msghdr *msg, int flags) { int nwritten; struct iovec *iov; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_sendmsg", fd, - "msg=%p, flags=%i", msg, flags); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_sendmsg", NULL, + "fd=%d msg=%p flags=%i", fd, msg, flags); nwritten = 0; iov = msg->msg_iov; @@ -841,7 +891,7 @@ _gpgme_io_dup (int fd) new_fd = dup (fd); while (new_fd == -1 && errno == EINTR); - TRACE (DEBUG_SYSIO, "_gpgme_io_dup", fd, "new fd==%i", new_fd); + TRACE (DEBUG_SYSIO, "_gpgme_io_dup", NULL, "fd=%d -> fd=%d", fd, new_fd); return new_fd; } @@ -852,8 +902,8 @@ _gpgme_io_socket (int domain, int type, int proto) { int res; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_socket", domain, - "type=%i, proto=%i", type, proto); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_socket", NULL, + "domain=%d type=%i proto=%i", domain, type, proto); res = socket (domain, type, proto); @@ -866,8 +916,8 @@ _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen) { int res; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_connect", fd, - "addr=%p, addrlen=%i", addr, addrlen); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_connect", NULL, + "fd=%d addr=%p addrlen=%i", fd, addr, addrlen); do res = ath_connect (fd, addr, addrlen); diff --git a/src/posix-util.c b/src/posix-util.c index 881856c..5c4f339 100644 --- a/src/posix-util.c +++ b/src/posix-util.c @@ -114,7 +114,7 @@ walk_path (const char *pgm) path = s + 1; } - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "gpgme-walk_path: '%s' not found in '%s'", pgm, orig_path); @@ -157,3 +157,10 @@ _gpgme_allow_set_foreground_window (pid_t pid) (void)pid; /* Not needed. */ } + +/* See w32-util.c */ +int +_gpgme_access (const char *path, int mode) +{ + return access (path, mode); +} diff --git a/src/sys-util.h b/src/sys-util.h index 6cb2224..e537613 100644 --- a/src/sys-util.h +++ b/src/sys-util.h @@ -28,9 +28,22 @@ int _gpgme_set_override_inst_dir (const char *dir); char *_gpgme_get_gpg_path (void); char *_gpgme_get_gpgconf_path (void); +int _gpgme_access (const char *path_utf8, int mode); + #ifdef HAVE_W32_SYSTEM const char *_gpgme_get_inst_dir (void); void _gpgme_w32_cancel_synchronous_io (HANDLE thread); +/* See CreateProcessA returns true on success */ +int _gpgme_create_process_utf8 (const char *application_name_utf8, + char *command_line_utf8, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + void *lpEnvironment, + char *working_directory_utf8, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation); #endif #endif /* SYS_UTIL_H */ diff --git a/src/version.c b/src/version.c index 3bf12e9..5beb63a 100644 --- a/src/version.c +++ b/src/version.c @@ -202,7 +202,7 @@ gpgme_check_version (const char *req_version) before using the trace facility. If we won't the trace would automagically initialize the debug system with out the locks being initialized and missing the assuan log level setting. */ - TRACE (DEBUG_INIT, "gpgme_check_version", 0, + TRACE (DEBUG_INIT, "gpgme_check_version", NULL, "req_version=%s, VERSION=%s", req_version? req_version:"(null)", VERSION); @@ -229,13 +229,13 @@ gpgme_check_version_internal (const char *req_version, return result; /* Catch-22, see above. */ - TRACE (DEBUG_INIT, "gpgme_check_version_internal", 0, + TRACE (DEBUG_INIT, "gpgme_check_version_internal", NULL, "req_version=%s, offset_sig_validity=%zu", req_version ? req_version : "(null)", offset_sig_validity); if (offset_sig_validity != offsetof (struct _gpgme_signature, validity)) { - TRACE (DEBUG_INIT, "gpgme_check_version_internal", 0, + TRACE (DEBUG_INIT, "gpgme_check_version_internal", NULL, "offset_sig_validity mismatch: expected %i", (int)offsetof (struct _gpgme_signature, validity)); _gpgme_selftest = GPG_ERR_SELFTEST_FAILED; diff --git a/src/w32-glib-io.c b/src/w32-glib-io.c index 8c8722c..09ffffa 100644 --- a/src/w32-glib-io.c +++ b/src/w32-glib-io.c @@ -324,7 +324,7 @@ _gpgme_io_write (int fd, const void *buffer, size_t count) chan = find_channel (fd); if (!chan) { - TRACE_LOG ("fd %d: no channel registered"); + TRACE_LOG ("fd=%d: no channel registered"); errno = EINVAL; return -1; } @@ -421,12 +421,13 @@ _gpgme_io_pipe (int filedes[2], int inherit_idx) return TRACE_SYSRES (-1); } - return TRACE_SUC ("read=0x%x/%p, write=0x%x/%p, channel=%p", - filedes[0], - (HANDLE) _get_osfhandle (giochannel_table[filedes[0]].fd), - filedes[1], - (HANDLE) _get_osfhandle (giochannel_table[filedes[1]].fd), - giochannel_table[1 - inherit_idx].chan); + TRACE_SUC ("read=0x%x/%p, write=0x%x/%p, channel=%p", + filedes[0], + (HANDLE) _get_osfhandle (giochannel_table[filedes[0]].fd), + filedes[1], + (HANDLE) _get_osfhandle (giochannel_table[filedes[1]].fd), + giochannel_table[1 - inherit_idx].chan); + return 0; } @@ -1066,7 +1067,7 @@ _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen) return TRACE_SYSRES (-1); } - TRACE_LOG ("connect sockfd=0x%x", sockfd); + TRACE_LOG ("connect socket fd=%d", sockfd); res = connect (sockfd, addr, addrlen); /* FIXME: Error ignored here. */ @@ -1081,5 +1082,7 @@ _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen) return TRACE_SYSRES (-1); } - return TRACE_SUC (""); + TRACE_SUC (""); + + return 0; } diff --git a/src/w32-io.c b/src/w32-io.c index 919ca6f..c5c21f5 100644 --- a/src/w32-io.c +++ b/src/w32-io.c @@ -1389,6 +1389,7 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags, int tmp_fd; char *tmp_name; const char *spawnhelper; + static int spawn_warning_shown = 0; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", path, "path=%s", path); @@ -1456,36 +1457,40 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags, up their installation this should also be properly communicated as otherwise calls to gnupg will result in unsupported protocol errors that do not explain a lot. */ - char *msg; - gpgrt_asprintf (&msg, "gpgme-w32spawn.exe was not found in the " - "detected installation directory of GpgME" - "\n\t\"%s\"\n\n" - "Crypto operations will not work.\n\n" - "If you see this it indicates a problem " - "with your installation.\n" - "Please report the problem to your " - "distributor of GpgME.\n\n" - "Developer's Note: The install dir can be " - "manually set with: gpgme_set_global_flag", - _gpgme_get_inst_dir ()); - MessageBoxA (NULL, msg, "GpgME not installed correctly", MB_OK); - gpgrt_free (msg); + if (!spawn_warning_shown) + { + char *msg; + gpgrt_asprintf (&msg, "gpgme-w32spawn.exe was not found in the " + "detected installation directory of GpgME" + "\n\t\"%s\"\n\n" + "Crypto operations will not work.\n\n" + "If you see this it indicates a problem " + "with your installation.\n" + "Please report the problem to your " + "distributor of GpgME.\n\n" + "Developer's Note: The install dir can be " + "manually set with: gpgme_set_global_flag", + _gpgme_get_inst_dir ()); + MessageBoxA (NULL, msg, "GpgME not installed correctly", MB_OK); + gpgrt_free (msg); + spawn_warning_shown = 1; + } gpg_err_set_errno (EIO); close (tmp_fd); DeleteFileA (tmp_name); free (tmp_name); return TRACE_SYSRES (-1); } - if (!CreateProcessA (spawnhelper, - arg_string, - &sec_attr, /* process security attributes */ - &sec_attr, /* thread security attributes */ - FALSE, /* inherit handles */ - cr_flags, /* creation flags */ - NULL, /* environment */ - NULL, /* use current drive/directory */ - &si, /* startup information */ - &pi)) /* returns process information */ + if (!_gpgme_create_process_utf8 (spawnhelper, + arg_string, + &sec_attr, /* process security attributes */ + &sec_attr, /* thread security attributes */ + FALSE, /* inherit handles */ + cr_flags, /* creation flags */ + NULL, /* environment */ + NULL, /* use current drive/directory */ + &si, /* startup information */ + &pi)) /* returns process information */ { int lasterr = (int)GetLastError (); TRACE_LOG ("CreateProcess failed: ec=%d", lasterr); diff --git a/src/w32-util.c b/src/w32-util.c index 9802d9c..8207676 100644 --- a/src/w32-util.c +++ b/src/w32-util.c @@ -168,6 +168,48 @@ wchar_to_utf8 (const wchar_t *string) } +/* Return a malloced wide char string from an UTF-8 encoded input + string STRING. Caller must free this value. On failure returns + NULL; caller may use GetLastError to get the actual error number. + Calling this function with STRING set to NULL is not defined. */ +static wchar_t * +utf8_to_wchar (const char *string) +{ + int n; + wchar_t *result; + + + n = MultiByteToWideChar (CP_UTF8, 0, string, -1, NULL, 0); + if (n < 0) + return NULL; + + result = (wchar_t *) malloc ((n+1) * sizeof *result); + if (!result) + return NULL; + + n = MultiByteToWideChar (CP_UTF8, 0, string, -1, result, n); + if (n < 0) + { + free (result); + return NULL; + } + return result; +} + + +/* Same as utf8_to_wchar but calling it with NULL returns + NULL. So a return value of NULL only indicates failure + if STRING is not set to NULL. */ +static wchar_t * +utf8_to_wchar0 (const char *string) +{ + if (!string) + return NULL; + + return utf8_to_wchar (string); +} + + /* Replace all forward slashes by backslashes. */ static void replace_slashes (char *string) @@ -220,19 +262,19 @@ _gpgme_allow_set_foreground_window (pid_t pid) if (!pid || pid == (pid_t)(-1)) { - TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", 0, + TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", NULL, "no action for pid %d", (int)pid); } else if (func) { int rc = func (pid); - TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", 0, + TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", NULL, "called for pid %d; result=%d", (int)pid, rc); } else { - TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", 0, + TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", NULL, "function not available"); } #endif /* HAVE_ALLOW_SET_FOREGROUND_WINDOW */ @@ -268,13 +310,13 @@ _gpgme_w32_cancel_synchronous_io (HANDLE thread) { if (!func (thread) && GetLastError() != ERROR_NOT_FOUND) { - TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", 0, + TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", NULL, "called for thread %p: ec=%d", thread, GetLastError ()); } } else { - TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", 0, + TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", NULL, "function not available"); } } @@ -395,7 +437,7 @@ find_program_in_dir (const char *dir, const char *name) if (!result) return NULL; - if (access (result, F_OK)) + if (_gpgme_access (result, F_OK)) { free (result); return NULL; @@ -408,7 +450,7 @@ find_program_in_dir (const char *dir, const char *name) static char * find_program_at_standard_place (const char *name) { - char path[MAX_PATH]; + wchar_t path[MAX_PATH]; char *result = NULL; /* See https://wiki.tcl-lang.org/page/Getting+Windows+%22special+folders%22+with+Ffidl for details on compatibility. @@ -416,20 +458,24 @@ find_program_at_standard_place (const char *name) We First try the generic place and then fallback to the x86 (i.e. 32 bit) place. This will prefer a 64 bit of the program over a 32 bit version on 64 bit Windows if installed. */ - if (SHGetSpecialFolderPathA (NULL, path, CSIDL_PROGRAM_FILES, 0)) + if (SHGetSpecialFolderPathW (NULL, path, CSIDL_PROGRAM_FILES, 0)) { - result = _gpgme_strconcat (path, "\\", name, NULL); - if (result && access (result, F_OK)) + char *utf8_path = wchar_to_utf8 (path); + result = _gpgme_strconcat (utf8_path, "\\", name, NULL); + free (utf8_path); + if (result && _gpgme_access (result, F_OK)) { free (result); result = NULL; } } if (!result - && SHGetSpecialFolderPathA (NULL, path, CSIDL_PROGRAM_FILESX86, 0)) + && SHGetSpecialFolderPathW (NULL, path, CSIDL_PROGRAM_FILESX86, 0)) { - result = _gpgme_strconcat (path, "\\", name, NULL); - if (result && access (result, F_OK)) + char *utf8_path = wchar_to_utf8 (path); + result = _gpgme_strconcat (utf8_path, "\\", name, NULL); + free (utf8_path); + if (result && _gpgme_access (result, F_OK)) { free (result); result = NULL; @@ -532,7 +578,7 @@ _gpgme_get_gpg_path (void) /* 4. Print a debug message if not found. */ if (!gpg) - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "_gpgme_get_gpg_path: '%s' not found", name); return gpg; @@ -608,7 +654,7 @@ _gpgme_get_gpgconf_path (void) /* 5. Print a debug message if not found. */ if (!gpgconf) - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "_gpgme_get_gpgconf_path: '%s' not found",name); return gpgconf; @@ -781,6 +827,73 @@ _gpgme_mkstemp (int *fd, char **name) } +/* Like access but using windows _waccess */ +int +_gpgme_access (const char *path, int mode) +{ + wchar_t *u16 = utf8_to_wchar0 (path); + int r = _waccess (u16, mode); + + free(u16); + return r; +} + + +/* Like CreateProcessA but mapping the arguments to wchar API */ +int _gpgme_create_process_utf8 (const char *application_name_utf8, + char *command_line_utf8, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + void *lpEnvironment, + char *working_directory_utf8, + LPSTARTUPINFOA si, + LPPROCESS_INFORMATION lpProcessInformation) +{ + BOOL ret; + wchar_t *application_name = utf8_to_wchar0 (application_name_utf8); + wchar_t *command_line = utf8_to_wchar0 (command_line_utf8); + wchar_t *working_directory = utf8_to_wchar0 (working_directory_utf8); + + STARTUPINFOW siw; + memset (&siw, 0, sizeof siw); + if (si) + { + siw.cb = sizeof (siw); + siw.dwFlags = si->dwFlags; + siw.wShowWindow = si->wShowWindow; + siw.hStdInput = si->hStdInput; + siw.hStdOutput = si->hStdOutput; + siw.hStdError = si->hStdError; + siw.dwX = si->dwX; + siw.dwY = si->dwY; + siw.dwXSize = si->dwXSize; + siw.dwYSize = si->dwYSize; + siw.dwXCountChars = si->dwXCountChars; + siw.dwYCountChars = si->dwYCountChars; + siw.dwFillAttribute = si->dwFillAttribute; + siw.lpDesktop = utf8_to_wchar0 (si->lpDesktop); + siw.lpTitle = utf8_to_wchar0 (si->lpTitle); + } + + ret = CreateProcessW (application_name, + command_line, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + working_directory, + si ? &siw : NULL, + lpProcessInformation); + free (siw.lpTitle); + free (siw.lpDesktop); + free (application_name); + free (command_line); + free (working_directory); + return ret; +} /* Entry point called by the DLL loader. */ #ifdef DLL_EXPORT @@ -137,7 +137,7 @@ _gpgme_add_io_cb (void *data, int fd, int dir, gpgme_io_cb_t fnc, } TRACE (DEBUG_CTX, "_gpgme_add_io_cb", ctx, - "fd %d, dir=%d -> tag=%p", fd, dir, tag); + "fd=%d, dir=%d -> tag=%p", fd, dir, tag); *r_tag = tag; return 0; @@ -179,7 +179,8 @@ _gpgme_remove_io_cb (void *data) own event loops could compensate for that, but the external event loops cannot. FIXME: We may still want to optimize this a bit when we are called from our own event loops. So if CHECKED is 1, the - check is skipped. */ + check is skipped. FIXME: Give an example on how the status of other + fds can be influenced. */ gpgme_error_t _gpgme_run_io_cb (struct io_select_fd_s *an_fds, int checked, gpgme_error_t *op_err) @@ -203,11 +204,13 @@ _gpgme_run_io_cb (struct io_select_fd_s *an_fds, int checked, nr = _gpgme_io_select (&fds, 1, 1); assert (nr <= 1); if (nr < 0) - return errno; + return gpg_error_from_syserror (); else if (nr == 0) - /* The status changed in the meantime, there is nothing left - to do. */ - return 0; + { + /* The status changed in the meantime, there is nothing left + * to do. */ + return 0; + } } TRACE (DEBUG_CTX, "_gpgme_run_io_cb", item, "handler (%p, %d)", diff --git a/tests/gpg/Makefile.am b/tests/gpg/Makefile.am index be84290..68222fa 100644 --- a/tests/gpg/Makefile.am +++ b/tests/gpg/Makefile.am @@ -59,6 +59,7 @@ private_keys = \ EXTRA_DIST = initial.test final.test \ pubdemo.asc secdemo.asc cipher-1.asc cipher-2.asc \ + cipher-3.asc cipher-no-sig.asc \ geheim.txt pubkey-1.asc seckey-1.asc pinentry $(private_keys) BUILT_SOURCES = gpg.conf gpg-agent.conf pubring-stamp \ diff --git a/tests/gpg/Makefile.in b/tests/gpg/Makefile.in index fce0b85..5b1ec77 100644 --- a/tests/gpg/Makefile.in +++ b/tests/gpg/Makefile.in @@ -579,6 +579,7 @@ private_keys = \ EXTRA_DIST = initial.test final.test \ pubdemo.asc secdemo.asc cipher-1.asc cipher-2.asc \ + cipher-3.asc cipher-no-sig.asc \ geheim.txt pubkey-1.asc seckey-1.asc pinentry $(private_keys) BUILT_SOURCES = gpg.conf gpg-agent.conf pubring-stamp \ diff --git a/tests/gpg/cipher-3.asc b/tests/gpg/cipher-3.asc new file mode 100644 index 0000000..10125b5 --- /dev/null +++ b/tests/gpg/cipher-3.asc @@ -0,0 +1,18 @@ +-----BEGIN PGP MESSAGE----- + +hQEOA++dwnahcsiBEAQApowkbz0idxMfLIT/a1G4QbZy77cWyaYOM/qh4lH4h7ra +f19MZzbRViZYBtJeITszTFmulp31qrF++6gBy8tU7l8VfVfOtvwn1WlRvn2+bsns +SicJAXtUiwIF8fbapSrkN6qpg2l+Fnx3SYV7+lh/7Fbf93DquwVEn+GwvgkyCQkD +/j5UVU6PJ+k5E5JG7CMzLZHnpW1MtKUqKRG9sTW83SvvI5Wl9VMgjmdO6bZRPKPY +Fr5udYJ1v4CIpVTb0O/TgAaiw7/Ak4SOg6jK0H0l1vuPPRjyzx43G4Y4lZ/EoCzT +gU+0kiDD+O7xLDml5jPWTYov7LkhRMKI4Ugp37FzbF9p0sClAR1mY4WlAN23ku0W +3bw54zSi7S6sh+i/uOmjZ+ziXr/S1pHFN2TNAkpH42o6VDhJ4vYk5KIB/mn/IoP1 +G57JUhLqpSHO7LF8X45eBLT3tKC3rIaILHXcXE5PeyMiOrl1snIClrpX+Qn6dRPX +cYssX/0OQwE77bAla5QpZEy6YLOC4Ej4oTX5QU1rHNyLfvIDhwtphKMvbbssmsVI +tAMvrGQudEwLSlvNlnJsI79jqmbi5W8PYr+ssvOvrd5hWoKti7EdglWddFNVVjZl +ooJgztXUnPcufhaimQtNvX0Tj2hI8QwTxGPNO3aesPCQI0GbSUSqpvu6mlaQY2Qe +NJNkfTH23fzoG61nFxIQ0HYbuBp0hJnGOXfHtMNxyjoAs1yEJbyQ20Lso/Vrsf3J +sY7jkisFrURxTGQyBxxbnIO+nJPemOzV9Q/qo6Lj9yW0KagaffWogw/6X/xOvL4u +aM8pI7NOi48b +=mh2s +-----END PGP MESSAGE----- diff --git a/tests/gpg/cipher-no-sig.asc b/tests/gpg/cipher-no-sig.asc new file mode 100644 index 0000000..47e399c --- /dev/null +++ b/tests/gpg/cipher-no-sig.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP MESSAGE----- + +hQEOA++dwnahcsiBEAP8CPkDWzZrcMJ1fyaPoQAhlx18p44+7vTF5zYCkMVT+i4E +X/zcIreDYDpFNHzK2orNW/LGGNpMf3ZyDAYw7eDWgPwld2PJh2/ZfR/dXoGcAEd4 +vhbhA1D/ymQn+lxgSXNLMonxaoCqFK7cKStLCHfaPMbQ0S5GXU0IRwjfSn76oxwE +AKzTa/54g8XNL9Dku9c/KATzopdDLzAzN6oBJlgRbjhc3T8mzTVJF91s+ow8dEC+ +0U2GBwkL5ZOKTFelSO3dBsH7RBNbYiJA+DbZ6nZia2KAtlLgyr4Z5vGh9bmltJl/ +1qdIdRF/La3wA/6bL10YMYF+7IJ9DiuDi/NRmSsO/ZWt0nMBXnzOeyLgxdbW1RS1 +NPWzKC/4RqvnWJmm7W3p0JG8v2ad+97O5KsOcqJnaKJOY0jKXeORrhih2DG32rpe +4bhskRTIBJBaPtPnRGevlxardQi+Spbd75yqccCLjkQF0HhCLiHW9SJiLSMdnY1U +U9th3ZR5 +=uvfB +-----END PGP MESSAGE----- diff --git a/tests/run-import.c b/tests/run-import.c index f464eb1..36d9a0d 100644 --- a/tests/run-import.c +++ b/tests/run-import.c @@ -44,6 +44,8 @@ show_usage (int ex) fputs ("usage: " PGM " [options] FILENAMEs\n\n" "Options:\n" " --verbose run in verbose mode\n" + " --openpgp use the OpenPGP protocol (default)\n" + " --cms use the CMS protocol\n" " --url import from given URLs\n" " -0 URLs are delimited by a nul\n" , stderr); @@ -60,6 +62,7 @@ main (int argc, char **argv) int nul_mode = 0; gpgme_import_result_t impres; gpgme_data_t data; + gpgme_protocol_t protocol = GPGME_PROTOCOL_OpenPGP; if (argc) { argc--; argv++; } @@ -88,6 +91,16 @@ main (int argc, char **argv) nul_mode = 1; argc--; argv++; } + else if (!strcmp (*argv, "--openpgp")) + { + protocol = GPGME_PROTOCOL_OpenPGP; + argc--; argv++; + } + else if (!strcmp (*argv, "--cms")) + { + protocol = GPGME_PROTOCOL_CMS; + argc--; argv++; + } else if (!strncmp (*argv, "--", 2)) show_usage (1); @@ -96,11 +109,11 @@ main (int argc, char **argv) if (!argc) show_usage (1); - init_gpgme (GPGME_PROTOCOL_OpenPGP); + init_gpgme (protocol); err = gpgme_new (&ctx); fail_if_err (err); - gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP); + gpgme_set_protocol (ctx, protocol); for (; argc; argc--, argv++) { diff --git a/tests/run-threaded.c b/tests/run-threaded.c index 02524b2..1fe4f19 100644 --- a/tests/run-threaded.c +++ b/tests/run-threaded.c @@ -56,12 +56,14 @@ static int mem_only; static void create_thread (THREAD_RET (*func) (void *), void *arg) { - running_threads++; - if (CloseHandle (CreateThread (NULL, 0, func, arg, 0, NULL))) + HANDLE hd = CreateThread (NULL, 0, func, arg, 0, NULL); + if (hd == INVALID_HANDLE_VALUE) { fprintf (stderr, "Failed to create thread!\n"); exit (1); } + running_threads++; + CloseHandle (hd); } #else @@ -124,6 +126,7 @@ show_usage (int ex) "Options:\n" " --verbose run in verbose mode\n" " --no-list do not do keylistings\n" + " --allow-del allow to delete keys after import\n" " --data-type mem function to use one of:\n" " 1: fstream\n" " 2: posix fd\n" @@ -171,6 +174,8 @@ typedef struct keylist_args_s *keylist_args_t; static volatile int keylists; +static int allow_del; + static THREAD_RET do_keylist (void *keylist_args) { @@ -279,6 +284,9 @@ random_data_new (const char *fname) { data_t ret = calloc (1, sizeof (struct data_s)); int data_rand; + + ret->fd = -1; + if (data_type) { data_rand = data_type; @@ -352,7 +360,8 @@ random_data_close (data_t data) { gpgme_data_release (data->dh); } - if (data->fd) + + if (data->fd != -1) { close (data->fd); } @@ -376,7 +385,7 @@ verify (const char *fname, gpgme_protocol_t proto) { gpgme_ctx_t ctx; gpgme_error_t err; - gpgme_data_t out; + gpgme_data_t output; char *msg; size_t msg_len; data_t data = random_data_new (fname); @@ -384,7 +393,7 @@ verify (const char *fname, gpgme_protocol_t proto) log ("Starting verify on: %s with protocol %s", fname, proto == GPGME_PROTOCOL_CMS ? "CMS" : "OpenPGP"); - gpgme_data_new (&out); + gpgme_data_new (&output); err = gpgme_new (&ctx); fail_if_err (err); @@ -392,13 +401,13 @@ verify (const char *fname, gpgme_protocol_t proto) err = gpgme_set_protocol (ctx, proto); fail_if_err (err); - err = gpgme_op_verify (ctx, data->dh, NULL, out); + err = gpgme_op_verify (ctx, data->dh, NULL, output); out ("Data: %p, %i %p %p %p", data->dh, data->fd, data->file, data->stream, data->mem); fail_if_err (err); - msg = gpgme_data_release_and_get_mem (out, &msg_len); + msg = gpgme_data_release_and_get_mem (output, &msg_len); if (msg_len) { @@ -421,14 +430,14 @@ decrypt (const char *fname, gpgme_protocol_t proto) { gpgme_ctx_t ctx; gpgme_error_t err; - gpgme_data_t out; + gpgme_data_t output; char *msg; size_t msg_len; data_t data = random_data_new (fname); log ("Starting decrypt on: %s", fname); - gpgme_data_new (&out); + gpgme_data_new (&output); err = gpgme_new (&ctx); fail_if_err (err); @@ -436,12 +445,12 @@ decrypt (const char *fname, gpgme_protocol_t proto) err = gpgme_set_protocol (ctx, proto); fail_if_err (err); - err = gpgme_op_decrypt (ctx, data->dh, out); + err = gpgme_op_decrypt (ctx, data->dh, output); fail_if_err (err); gpgme_release (ctx); - msg = gpgme_data_release_and_get_mem (out, &msg_len); + msg = gpgme_data_release_and_get_mem (output, &msg_len); if (msg_len) { @@ -451,6 +460,98 @@ decrypt (const char *fname, gpgme_protocol_t proto) random_data_close (data); } +void +delete_key (gpgme_key_t key) +{ + gpgme_ctx_t ctx; + gpgme_error_t err; + + err = gpgme_new (&ctx); + fail_if_err (err); + + gpgme_set_protocol (ctx, key->protocol); + + err = gpgme_op_delete (ctx, key, 0); + fail_if_err (err); + + gpgme_release (ctx); +} + +/* Get the key for the fpr in protocol and call delete_key + on it. */ +void +delete_fpr (const char *fpr, gpgme_protocol_t proto) +{ + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_key_t key = NULL; + + err = gpgme_new (&ctx); + fail_if_err (err); + + gpgme_set_protocol (ctx, proto); + + err = gpgme_get_key (ctx, fpr, &key, 0); + fail_if_err (err); + + if (!key) + { + errpoint; + } + delete_key (key); + + log ("deleted key %s", fpr); + gpgme_key_unref (key); + gpgme_release (ctx); +} + +void +delete_impres (gpgme_import_result_t r, gpgme_protocol_t proto) +{ + gpgme_import_status_t st; + + if (!r) + { + errpoint; + } + + for (st=r->imports; st; st = st->next) + { + delete_fpr (st->fpr, proto); + } +} + +void +import (const char *fname, gpgme_protocol_t proto) +{ + gpgme_ctx_t ctx; + gpgme_error_t err; + data_t data = random_data_new (fname); + + log ("Starting import on: %s", fname); + + err = gpgme_new (&ctx); + fail_if_err (err); + + err = gpgme_set_protocol (ctx, proto); + fail_if_err (err); + + gpgme_set_offline (ctx, 1); + + err = gpgme_op_import (ctx, data->dh); + fail_if_err (err); + + if (allow_del) + { + delete_impres (gpgme_op_import_result (ctx), proto); + } + + gpgme_release (ctx); + + log ("Import completed."); + + random_data_close (data); +} static THREAD_RET do_data_op (void *file_name) @@ -500,10 +601,20 @@ do_data_op (void *file_name) decrypt (fname, GPGME_PROTOCOL_CMS); break; } + case GPGME_DATA_TYPE_PGP_KEY: + { + import (fname, GPGME_PROTOCOL_OpenPGP); + break; + } + case GPGME_DATA_TYPE_X509_CERT: + { + import (fname, GPGME_PROTOCOL_CMS); + break; + } default: { - fprintf (stderr, "Unhandled data type 0x%x for '%s'", type, fname); - exit(1); + out ("Unhandled data type 0x%x for '%s'\n", type, fname); + errpoint; } } @@ -569,6 +680,11 @@ main (int argc, char **argv) no_list = 1; argc--; argv++; } + else if (!strcmp (*argv, "--allow-del")) + { + allow_del = 1; + argc--; argv++; + } else if (!strcmp (*argv, "--mem-only")) { if (data_type) |