aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen McGinnes <[email protected]>2018-03-21 19:33:16 +0000
committerBen McGinnes <[email protected]>2018-03-21 19:33:16 +0000
commit76055dd5c7d755c6f8a242b701aeadba621fbc0f (patch)
treeb995196bcadcfa0485429d753397f61317b17292
parentcore: Do not clobber R_KEY in gpgme_get_key on error. (diff)
parentexamples: multi-key selection operations (diff)
downloadgpgme-76055dd5c7d755c6f8a242b701aeadba621fbc0f.tar.gz
gpgme-76055dd5c7d755c6f8a242b701aeadba621fbc0f.zip
Merge branch 'ben/docs/2018-03' of ssh+git://playfair.gnupg.org/git/gpgme into ben/docs/2018-03
-rw-r--r--.gitignore1
-rw-r--r--TODO157
-rw-r--r--lang/python/docs/GPGMEpythonHOWTOen.org1370
-rw-r--r--lang/python/docs/TODO.org77
-rw-r--r--lang/python/examples/howto/README.org58
-rwxr-xr-xlang/python/examples/howto/clear-sign-file.py56
-rwxr-xr-xlang/python/examples/howto/decrypt-file.py44
-rwxr-xr-xlang/python/examples/howto/detach-sign-file.py64
-rwxr-xr-xlang/python/examples/howto/encrypt-file.py71
-rwxr-xr-xlang/python/examples/howto/encrypt-sign-file.py70
-rw-r--r--lang/python/examples/howto/groups.py50
-rwxr-xr-xlang/python/examples/howto/keycount.py42
-rwxr-xr-xlang/python/examples/howto/sign-file.py64
-rwxr-xr-xlang/python/examples/howto/verify-signatures.py64
-rwxr-xr-xlang/python/examples/howto/verify-signed-file.py61
-rwxr-xr-xlang/python/examples/low_level-encrypt_to_all.py (renamed from lang/python/examples/encrypt-to-all.py)0
-rw-r--r--src/gpgme-tool.c2
17 files changed, 2209 insertions, 42 deletions
diff --git a/.gitignore b/.gitignore
index de173b8f..e5bf69dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,4 +52,3 @@ nosetests.xml
default.profraw
.DS_Store
._.DS_Store
-default.profraw \ No newline at end of file
diff --git a/TODO b/TODO
index 5f03fb61..a915ed7c 100644
--- a/TODO
+++ b/TODO
@@ -14,23 +14,46 @@ Hey Emacs, this is -*- org -*- mode!
tracked through the [[https://dev.gnupg.org/][dev.gnupg.org]] site.
-* TODO Document all the new stuff.
+* Documentation
:PROPERTIES:
- :CUSTOM_ID: more-docs-is-better
+ :CUSTOM_ID: documentation
:END:
-** STARTED Fix this TODO list.
+** Document all the new stuff.
:PROPERTIES:
- :CUSTOM_ID: fix-todo
+ :CUSTOM_ID: more-docs-is-better
:END:
- - State "STARTED" from "TODO" [2018-03-09 Fri 08:31]
- Clean up the current TODO list. Include properties as relevant (so
- if someone does make a PDF or HTML version the TOC will work).
- Also check ans see if some of these ancient things can be removed
- (e.g. do we really need to fix things that were broken in GPG
- 1.3.x? I'm thinking not so much).
+*** TODO Fix this TODO list.
+ :PROPERTIES:
+ :CUSTOM_ID: fix-todo
+ :END:
+
+ Clean up the current TODO list. Include properties as relevant (so
+ if someone does make a PDF or HTML version the TOC will work).
+
+ Also check ans see if some of these ancient things can be removed
+ (e.g. do we really need to fix things that were broken in GPG
+ 1.3.x? I'm thinking not so much).
+
+**** DONE fix TODO items
+ CLOSED: [2018-03-04 Sun 08:55]
+ :PROPERTIES:
+ :CUSTOM_ID: fix-todo-items
+ :END:
+
+ Adjust todo items so each can now be referenced by custom-id and
+ checked off as necessary.
+** TODO Document validity and trust issues.
+ :PROPERTIES:
+ :CUSTOM_ID: valid-trust-issues
+ :END:
+
+** In gpgme.texi: Register callbacks under the right letter in the index.
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-texi
+ :END:
* Fix the remaining UI Server problems:
@@ -63,10 +86,12 @@ Hey Emacs, this is -*- org -*- mode!
:END:
Right now we block reading the next line with assuan.
+
* Before release:
:PROPERTIES:
:CUSTOM_ID: pre-release
:END:
+
** CANCELLED Some gpg tests fail with gpg 1.3.4-cvs (gpg/t-keylist-sig)
CLOSED: [2018-03-09 Fri 08:16]
:PROPERTIES:
@@ -75,100 +100,123 @@ Hey Emacs, this is -*- org -*- mode!
- State "CANCELLED" from "TODO" [2018-03-09 Fri 08:16] \\
WON'T FIX — too old or no longer applies.
The test is currently disabled there and in gpg/t-import.
+
** When gpg supports it, write binary subpackets directly,
:PROPERTIES:
:CUSTOM_ID: binary-subpackets
:END:
and parse SUBPACKET status lines.
+
* ABI's to break:
:PROPERTIES:
:CUSTOM_ID: abi-breakage-apparently-on-purpose
:END:
+
** Old opassuan interface.
:PROPERTIES:
:CUSTOM_ID: old-opassuan
:END:
+
** Implementation: Remove support for old style error codes in
:PROPERTIES:
:CUSTOM_ID: remove-old-error-codes
:END:
conversion.c::_gpgme_map_gnupg_error.
+
** gpgme_edit_cb_t: Add "processed" return argument
:PROPERTIES:
:CUSTOM_ID: add-processed-return
:END:
(see edit.c::command_handler).
+
** I/O and User Data could be made extensible. But this can be done
:PROPERTIES:
:CUSTOM_ID: add-io-user-data
:END:
without breaking the ABI hopefully.
+
** All enums should be replaced by ints and simple macros for
:PROPERTIES:
:CUSTOM_ID: enums-should-be-ints
:END:
maximum compatibility.
+
** Compatibility interfaces that can be removed in future versions:
:PROPERTIES:
:CUSTOM_ID: compat-interfaces-to-go
:END:
+
*** gpgme_data_new_from_filepart
:PROPERTIES:
:CUSTOM_ID: gpgme-data-new-from-filepart
:END:
+
*** gpgme_data_new_from_file
:PROPERTIES:
:CUSTOM_ID: gpgme-data-new-from-file
:END:
+
*** gpgme_data_new_with_read_cb
:PROPERTIES:
:CUSTOM_ID: gpgme-data-new-with-read-cb
:END:
+
*** gpgme_data_rewind
:PROPERTIES:
:CUSTOM_ID: gpgme-data-rewind
:END:
+
*** gpgme_op_import_ext
:PROPERTIES:
:CUSTOM_ID: gpgme-op-import-ext
:END:
+
*** gpgme_get_sig_key
:PROPERTIES:
:CUSTOM_ID: gpgme-get-sig-key
:END:
+
*** gpgme_get_sig_ulong_attr
:PROPERTIES:
:CUSTOM_ID: gpgme-get-sig-ulong-attr
:END:
+
*** gpgme_get_sig_string_attr
:PROPERTIES:
:CUSTOM_ID: gpgme-get-sig-string-attr
:END:
+
*** GPGME_SIG_STAT_*
:PROPERTIES:
:CUSTOM_ID: gpgme-sig-stat
:END:
+
*** gpgme_get_sig_status
:PROPERTIES:
:CUSTOM_ID: gpgme-get-sig-status
:END:
+
*** gpgme_trust_item_release
:PROPERTIES:
:CUSTOM_ID: gpgme-trust-item-release
:END:
+
*** gpgme_trust_item_get_string_attr
:PROPERTIES:
:CUSTOM_ID: gpgme-trust-item-get-string-attr
:END:
+
*** gpgme_trust_item_get_ulong_attr
:PROPERTIES:
:CUSTOM_ID: gpgme-trust-item-get-ulong-attr
:END:
+
*** gpgme_attr_t
:PROPERTIES:
:CUSTOM_ID: gpgme-attr-t
:END:
+
*** All Gpgme* typedefs.
:PROPERTIES:
:CUSTOM_ID: all-gpgme-typedefs
@@ -179,20 +227,24 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: threads
:END:
+
** When GNU Pth supports sendmsg/recvmsg, wrap them properly.
:PROPERTIES:
:CUSTOM_ID: wrap-oth
:END:
+
** Without timegm (3) support our ISO time parser is not thread safe.
:PROPERTIES:
:CUSTOM_ID: time-threads
:END:
There is a configure time warning, though.
+
* New features:
:PROPERTIES:
:CUSTOM_ID: new-features
:END:
+
** Flow control for data objects.
:PROPERTIES:
:CUSTOM_ID: flow-control-is-not-a-euphemism-for-an-s-bend
@@ -205,11 +257,13 @@ Hey Emacs, this is -*- org -*- mode!
respective event loop. or (B) a way for gpgme data objects to be
associated with a waitable object, that can be registered with the
user event loop. Neither is particularly simple.
+
** Extended notation support. When gpg supports arbitrary binary
:PROPERTIES:
:CUSTOM_ID: extended-notation
:END:
notation data, provide a user interface for that.
+
** notification system
:PROPERTIES:
:CUSTOM_ID: notification-system
@@ -236,25 +290,30 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: stat-data
:END:
+
** Implement support for photo ids.
:PROPERTIES:
:CUSTOM_ID: photo-id
:END:
+
** Allow selection of subkeys
:PROPERTIES:
:CUSTOM_ID: subkey-selection
:END:
+
** Allow to return time stamps in ISO format
:PROPERTIES:
:CUSTOM_ID: iso-format-datetime
:END:
- This allows us to handle years later than 2037 properly. With the
- time_t interface they are all mapped to 2037-12-31
+ This allows us to handle years later than 2037 properly. With the
+ time_t interface they are all mapped to 2037-12-31
+
** New features requested by our dear users, but rejected or left for
:PROPERTIES:
:CUSTOM_ID: feature-requests
:END:
later consideration:
+
*** Allow to export secret keys.
:PROPERTIES:
:CUSTOM_ID: export-secret-keys
@@ -262,6 +321,7 @@ Hey Emacs, this is -*- org -*- mode!
Rejected because this is conceptually flawed. Secret keys on a
smart card can not be exported, for example.
May eventually e supproted with a keywrapping system.
+
*** Selecting the key ring, setting the version or comment in output.
:PROPERTIES:
:CUSTOM_ID: select-keyring-version
@@ -269,46 +329,23 @@ Hey Emacs, this is -*- org -*- mode!
Rejected because the naive implementation is engine specific, the
configuration is part of the engine's configuration or readily
worked around in a different way
+
*** Selecting the symmetric cipher.
:PROPERTIES:
:CUSTOM_ID: symmetric-cipher-selection
:END:
+
*** Exchanging keys with key servers.
:PROPERTIES:
:CUSTOM_ID: key-server-exchange
:END:
-* Documentation
- :PROPERTIES:
- :CUSTOM_ID: documentation
- :END:
-** TODO Document validity and trust issues.
- :PROPERTIES:
- :CUSTOM_ID: valid-trust-issues
- :END:
-** In gpgme.texi: Register callbacks under the right letter in the index.
- :PROPERTIES:
- :CUSTOM_ID: gpgme-texi
- :END:
-** TODO Update TODO file
- :PROPERTIES:
- :CUSTOM_ID: todo-update
- :END:
-
-*** DONE fix TODO items
- CLOSED: [2018-03-04 Sun 08:55]
- :PROPERTIES:
- :CUSTOM_ID: fix-todo-items
- :END:
-
- Adjust todo items so each can now be referenced by custom-id and
- checked off as necessary.
-
* Engines
:PROPERTIES:
:CUSTOM_ID: engines
:END:
+
** Do not create/destroy engines, but create engine and then reset it.
:PROPERTIES:
:CUSTOM_ID: reset-engine-is-not-quite-just-ignition
@@ -321,26 +358,31 @@ Hey Emacs, this is -*- org -*- mode!
Note that we need support in gpgsm to set include-certs to default
as RESET does not reset it, also for no_encrypt_to and probably
other options.
+
** Optimize the case where a data object has an underlying fd we can pass
:PROPERTIES:
:CUSTOM_ID: optimus-data-cousin-of-optimus-prime
:END:
directly to the engine. This will be automatic with socket I/O and
descriptor passing.
+
** Move code common to all engines up from gpg to engine.
:PROPERTIES:
:CUSTOM_ID: move-code-common-to-engines-out-of-gpg
:END:
+
** engine operations can return General Error on unknown protocol
:PROPERTIES:
:CUSTOM_ID: general-error-looking-to-be-court-martialled
:END:
(it's an internal error, as select_protocol checks already).
+
** When server mode is implemented properly, more care has to be taken to
:PROPERTIES:
:CUSTOM_ID: server-mode
:END:
release all resources on error (for example to free assuan_cmd).
+
** op_import_keys and op_export_keys have a limit in the number of keys.
:PROPERTIES:
:CUSTOM_ID: import-export-problems
@@ -354,6 +396,7 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: gpg-breakage
:END:
+
** CANCELLED gpg 1.4.2 lacks error reporting if sign/encrypt with revoked key.
CLOSED: [2018-03-09 Fri 08:19]
:PROPERTIES:
@@ -361,6 +404,7 @@ Hey Emacs, this is -*- org -*- mode!
:END:
- State "CANCELLED" from "TODO" [2018-03-09 Fri 08:19] \\
WON'T FIX.
+
** CANCELLED gpg 1.4.2 does crappy error reporting (namely none at all) when
CLOSED: [2018-03-09 Fri 08:20]
:PROPERTIES:
@@ -374,6 +418,7 @@ Hey Emacs, this is -*- org -*- mode!
gpg: signing failed: general error
[GNUPG:] BEGIN_ENCRYPTION 2 10
gpg: test: sign+encrypt failed: general error
+
** DONE Without agent and with wrong passphrase, gpg 1.4.2 enters into an
CLOSED: [2018-03-09 Fri 08:20]
:PROPERTIES:
@@ -382,6 +427,7 @@ Hey Emacs, this is -*- org -*- mode!
- State "DONE" from "TODO" [2018-03-09 Fri 08:20] \\
Must have been fixed in a subsequent release.
infinite loop.
+
** CANCELLED Use correct argv[0]
CLOSED: [2018-03-09 Fri 08:24]
:PROPERTIES:
@@ -402,71 +448,86 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: operations-are-not-surgical
:END:
+
** Include cert values -2, -1, 0 and 1 should be defined as macros.
:PROPERTIES:
:CUSTOM_ID: certified-macros
:END:
+
** If an operation failed, make sure that the result functions don't return
:PROPERTIES:
:CUSTOM_ID: operation-failure
:END:
corrupt partial information. !!!
NOTE: The EOF status handler is not called in this case !!!
+
** Verify must not fail on NODATA premature if auto-key-retrieval failed.
:PROPERTIES:
:CUSTOM_ID: autobot-key-retrieval
:END:
It should not fail silently if it knows there is an error. !!!
+
** All operations: Better error reporting. !!
:PROPERTIES:
:CUSTOM_ID: better-reporting-not-like-fox-news
:END:
+
** Export status handler need much more work. !!!
:PROPERTIES:
:CUSTOM_ID: export-status-handler
:END:
+
** Import should return a useful error when one happened.
:PROPERTIES:
:CUSTOM_ID: import-useful-stuff-even-wrong-stuff
:END:
+
*** Import does not take notice of NODATA status report.
:PROPERTIES:
:CUSTOM_ID: import-no-data
:END:
+
*** When GPGSM does issue IMPORT_OK status reports, make sure to check for
:PROPERTIES:
:CUSTOM_ID: gpgsm-import-ok
:END:
them in tests/gpgs m/t-import.c.
+
** Verify can include info about version/algo/class, but currently
:PROPERTIES:
:CUSTOM_ID: verify-class
:END:
this is only available for gpg, not gpgsm.
+
** Return ENC_TO output in verify result. Again, this is not available
:PROPERTIES:
:CUSTOM_ID: return-to-enc
:END:
for gpgsm.
+
** Genkey should return something more useful than General_Error.
:PROPERTIES:
:CUSTOM_ID: general-key-assumed-command-from-general-error
:END:
+
** If possible, use --file-setsize to set the file size for proper progress
:PROPERTIES:
:CUSTOM_ID: file-setsize
:END:
callback handling. Write data interface for file size.
+
** Optimize the file descriptor list, so the number of open fds is
:PROPERTIES:
:CUSTOM_ID: optimus-descriptus-younger-brother-of-optimus-prime
:END:
always known easily.
+
** Encryption: It should be verified that the behaviour for partially untrusted
:PROPERTIES:
:CUSTOM_ID: only-mostly-dead-means-partially-alive
:END:
recipients is correct.
+
** When GPG issues INV_something for invalid signers, catch them.
:PROPERTIES:
:CUSTOM_ID: invalid-sig
@@ -477,15 +538,18 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: error-value
:END:
+
** Map ASSUAN/GpgSM ERR error values in a better way than is done now. !!
:PROPERTIES:
:CUSTOM_ID: map-ass-error
:END:
+
** Some error values should identify the source more correctly (mostly error
:PROPERTIES:
:CUSTOM_ID: source-errors
:END:
values derived from status messages).
+
** In rungpg.c we need to check the version of the engine
:PROPERTIES:
:CUSTOM_ID: rungpg-c-engine-ver
@@ -498,6 +562,7 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: tests
:END:
+
** TODO Write a fake gpg-agent so that we can supply known passphrases to
:PROPERTIES:
:CUSTOM_ID: test-fake-gpg-agent
@@ -505,23 +570,28 @@ Hey Emacs, this is -*- org -*- mode!
gpgsm and setup the configuration files to use the agent. Without
this we are testing a currently running gpg-agent which is not a
clever idea. !
+
** t-data
:PROPERTIES:
:CUSTOM_ID: test-data
:END:
+
*** Test gpgme_data_release_and_get_mem.
:PROPERTIES:
:CUSTOM_ID: test-gpgme-data-release-mem
:END:
+
*** Test gpgme_data_seek for invalid types.
:PROPERTIES:
:CUSTOM_ID: test-gpgme-data-seek
:END:
+
** t-keylist
:PROPERTIES:
:CUSTOM_ID: test-keylist
:END:
Write a test for ext_keylist.
+
** Test reading key signatures.
:PROPERTIES:
:CUSTOM_ID: test-key-sig
@@ -532,6 +602,7 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: debug
:END:
+
** Tracepoints should be added at: Every public interface enter/leave,
:PROPERTIES:
:CUSTOM_ID: tracepoint-pub-int
@@ -547,6 +618,7 @@ Hey Emacs, this is -*- org -*- mode!
decrypt-verify.c delete.c edit.c encrypt.c encrypt-sign.c export.c
genkey.c import.c key.c keylist.c passphrase.c progress.c signers.c
sig-notation.c trust-item.c trustlist.c verify.c
+
** TODO Handle malloc and vasprintf errors. But decide first if they should be
:PROPERTIES:
:CUSTOM_ID: malloc-vasprintf
@@ -559,10 +631,12 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: build-suite
:END:
+
** TODO Make sure everything is cleaned correctly (esp. test area).
:PROPERTIES:
:CUSTOM_ID: clean-tests
:END:
+
** TODO Enable AC_CONFIG_MACRO_DIR and bump up autoconf version requirement.
:PROPERTIES:
:CUSTOM_ID: autoconf-macros
@@ -575,6 +649,7 @@ Hey Emacs, this is -*- org -*- mode!
:PROPERTIES:
:CUSTOM_ID: error-checking
:END:
+
** TODO engine-gpgsm, with-validation
:PROPERTIES:
:CUSTOM_ID: gpgsm-validation
@@ -615,7 +690,11 @@ Hey Emacs, this is -*- org -*- mode!
Write a guide/best practices for maintainers of GPGME packages with
third party package management systems.
-Copyright 2004, 2005, 2018 g10 Code GmbH
+
+* Copyright 2004, 2005, 2018 g10 Code GmbH
+ :PROPERTIES:
+ :CUSTOM_ID: copyright-and-license
+ :END:
This file is free software; as a special exception the author gives
unlimited permission to copy and/or distribute it, with or without
diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org
new file mode 100644
index 00000000..655209ac
--- /dev/null
+++ b/lang/python/docs/GPGMEpythonHOWTOen.org
@@ -0,0 +1,1370 @@
+#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman}
+#+LATEX_HEADER: \author{Ben McGinnes <[email protected]>}
+
+
+* Introduction
+ :PROPERTIES:
+ :CUSTOM_ID: intro
+ :END:
+
+ | Version: | 0.1.0-draft |
+ | Author: | Ben McGinnes <[email protected]> |
+ | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
+ | Language: | Australian English, British English |
+ | xml:lang: | en-AU, en-GB, en |
+
+ This document provides basic instruction in how to use the GPGME
+ Python bindings to programmatically leverage the GPGME library.
+
+
+** Python 2 versus Python 3
+ :PROPERTIES:
+ :CUSTOM_ID: py2-vs-py3
+ :END:
+
+ Though the GPGME Python bindings themselves provide support for
+ both Python 2 and 3, the focus is unequivocally on Python 3 and
+ specifically from Python 3.4 and above. As a consequence all the
+ examples and instructions in this guide use Python 3 code.
+
+ 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.
+
+
+** Examples
+ :PROPERTIES:
+ :CUSTOM_ID: howto-python3-examples
+ :END:
+
+ All of the examples found in this document can be found as Python 3
+ scripts in the =lang/python/examples/howto= directory.
+
+
+* GPGME Concepts
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-concepts
+ :END:
+
+
+** A C API
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-c-api
+ :END:
+
+ Unlike many modern APIs with which programmers will be more
+ familiar with these days, the GPGME API is a C API. The API is
+ intended for use by C coders who would be able to access its
+ features by including the =gpgme.h= header file with their own C
+ source code and then access its functions just as they would any
+ other C headers.
+
+ This is a very effective method of gaining complete access to the
+ API and in the most efficient manner possible. It does, however,
+ have the drawback that it cannot be directly used by other
+ languages without some means of providing an interface to those
+ languages. This is where the need for bindings in various
+ languages stems.
+
+
+** Python bindings
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-bindings
+ :END:
+
+ The Python bindings for GPGME provide a higher level means of
+ accessing the complete feature set of GPGME itself. It also
+ provides a more pythonic means of calling these API functions.
+
+ The bindings are generated dynamically with SWIG and the copy of
+ =gpgme.h= generated when GPGME is compiled.
+
+ This means that a version of the Python bindings is fundamentally
+ tied to the exact same version of GPGME used to generate that copy
+ of =gpgme.h=.
+
+
+** Difference between the Python bindings and other GnuPG Python packages
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-bindings-diffs
+ :END:
+
+ There have been numerous attempts to add GnuPG support to Python
+ over the years. Some of the most well known are listed here, along
+ with what differentiates them.
+
+
+*** The python-gnupg package maintained by Vinay Sajip
+ :PROPERTIES:
+ :CUSTOM_ID: diffs-python-gnupg
+ :END:
+
+ This is arguably the most popular means of integrating GPG with
+ Python. The package utilises the =subprocess= module to implement
+ wrappers for the =gpg= and =gpg2= executables normally invoked on
+ the command line (=gpg.exe= and =gpg2.exe= on Windows).
+
+ The popularity of this package stemmed from its ease of use and
+ capability in providing the most commonly required features.
+
+ Unfortunately it has been beset by a number of security issues in
+ the past; most of which stemmed from using unsafe methods of
+ accessing the command line via the =subprocess= calls. While some
+ effort has been made over the last two to three years (as of 2018)
+ to mitigate this, particularly by no longer providing shell access
+ through those subprocess calls, the wrapper is still somewhat
+ limited in the scope of its GnuPG features coverage.
+
+ The python-gnupg package is available under the MIT license.
+
+
+*** The gnupg package created and maintained by Isis Lovecruft
+ :PROPERTIES:
+ :CUSTOM_ID: diffs-isis-gnupg
+ :END:
+
+ In 2015 Isis Lovecruft from the Tor Project forked and then
+ re-implemented the python-gnupg package as just gnupg. This new
+ package also relied on subprocess to call the =gpg= or =gpg2=
+ binaries, but did so somewhat more securely.
+
+ The naming and version numbering selected for this package,
+ however, resulted in conflicts with the original python-gnupg and
+ since its functions were called in a different manner to
+ python-gnupg, the release of this package also resulted in a great
+ deal of consternation when people installed what they thought was
+ an upgrade that subsequently broke the code relying on it.
+
+ The gnupg package is available under the GNU General Public
+ License version 3.0 (or any later version).
+
+
+*** The PyME package maintained by Martin Albrecht
+ :PROPERTIES:
+ :CUSTOM_ID: diffs-pyme
+ :END:
+
+ This package is the origin of these bindings, though they are
+ somewhat different now. For details of when and how the PyME
+ package was folded back into GPGME itself see the /Short History/
+ document[fn:1] in this Python bindings =docs= directory.[fn:2]
+
+ The PyME package was first released in 2002 and was also the first
+ attempt to implement a low level binding to GPGME. In doing so it
+ provided access to considerably more functionality than either the
+ =python-gnupg= or =gnupg= packages.
+
+ The PyME package 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).
+
+
+* GPGME Python bindings installation
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-install
+ :END:
+
+
+** No PyPI
+ :PROPERTIES:
+ :CUSTOM_ID: do-not-use-pypi
+ :END:
+
+ Most third-party Python packages and modules are available and
+ distributed through the Python Package Installer, known as PyPI.
+
+ Due to the nature of what these bindings are and how they work, it
+ is infeasible to install the GPGME Python bindings in the same way.
+
+ This is because the bindings use SWIG to dynamically generate C
+ bindings against =gpgme.h= and =gpgme.h= is generated from
+ =gpgme.h.in= at compile time when GPGME is built from source. Thus
+ to include a package in PyPI which actually built correctly would
+ require either statically built libraries for every architecture
+ bundled with it or a full implementation of C for each
+ architecture.
+
+
+** Requirements
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-requirements
+ :END:
+
+ The GPGME Python bindings only have three requirements:
+
+ 1. A suitable version of Python 2 or Python 3. With Python 2 that
+ means Python 2.7 and with Python 3 that means Python 3.4 or
+ higher.
+ 2. SWIG.
+ 3. GPGME itself. Which also means that all of GPGME's dependencies
+ must be installed too.
+
+
+** Installation
+ :PROPERTIES:
+ :CUSTOM_ID: installation
+ :END:
+
+ Installing the Python bindings is effectively achieved by compiling
+ and installing GPGME itself.
+
+ Once SWIG is installed with Python and all the dependencies for
+ GPGME are installed you only need to confirm that the version(s) of
+ Python you want the bindings installed for are in your =$PATH=.
+
+ By default GPGME will attempt to install the bindings for the most
+ recent or highest version number of Python 2 and Python 3 it
+ detects in =$PATH=. It specifically checks for the =python= and
+ =python3= executables first and then checks for specific version
+ numbers.
+
+ For Python 2 it checks for these executables in this order:
+ =python=, =python2= and =python2.7=.
+
+ For Python 3 it checks for these executables in this order:
+ =python3=, =python3.6=, =python3.5= and =python3.4=.
+
+
+*** Installing GPGME
+ :PROPERTIES:
+ :CUSTOM_ID: install-gpgme
+ :END:
+
+ See the GPGME =README= file for details of how to install GPGME from
+ source.
+
+
+* Fundamentals
+ :PROPERTIES:
+ :CUSTOM_ID: howto-fund-a-mental
+ :END:
+
+ Before we can get to the fun stuff, there are a few matters
+ regarding GPGME's design which hold true whether you're dealing with
+ the C code directly or these Python bindings.
+
+
+** No REST
+ :PROPERTIES:
+ :CUSTOM_ID: no-rest-for-the-wicked
+ :END:
+
+ The first part of which is or will be fairly blatantly obvious upon
+ viewing the first example, but it's worth reiterating anyway. That
+ being that this API is /*not*/ a REST API. Nor indeed could it
+ ever be one.
+
+ Most, if not all, Python programmers (and not just Python
+ programmers) know how easy it is to work with a RESTful API. In
+ fact they've become so popular that many other APIs attempt to
+ emulate REST-like behaviour as much as they are able. Right down
+ to the use of JSON formatted output to facilitate the use of their
+ API without having to retrain developers.
+
+ This API does not do that. It would not be able to do that and
+ also provide access to the entire C API on which it's built. It
+ does, however, provide a very pythonic interface on top of the
+ direct bindings and it's this pythonic layer with which this HOWTO
+ deals with.
+
+
+** Context
+ :PROPERTIES:
+ :CUSTOM_ID: howto-get-context
+ :END:
+
+ One of the reasons which prevents this API from being RESTful is
+ that most operations require more than one instruction to the API
+ to perform the task. Sure, there are certain functions which can
+ be performed simultaneously, particularly if the result known or
+ strongly anticipated (e.g. selecting and encrypting to a key known
+ to be in the public keybox).
+
+ 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.
+
+
+* Working with keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-keys
+ :END:
+
+
+** Key selection
+ :PROPERTIES:
+ :CUSTOM_ID: howto-keys-selection
+ :END:
+
+ Selecting keys to encrypt to or to sign with will be a common
+ occurrence when working with GPGMe and the means available for
+ doing so are quite simple.
+
+ 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_src python
+ import gpg
+
+ k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
+ keys = list(k)
+ #+end_src
+
+ This is passable and very likely to be common:
+
+ #+begin_src python
+ import gpg
+
+ k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
+ keys = list(k)
+ #+end_src
+
+ And this is a really bad idea:
+
+ #+begin_src python
+ import gpg
+
+ k = gpg.Context().keylist(pattern="0xDEADBEEF")
+ keys = list(k)
+ #+end_src
+
+ Alternatively it may be that the intention is to create a list of
+ keys which all match a particular search string. For instance all
+ the addresses at a particular domain, like this:
+
+ #+begin_src python
+ import gpg
+
+ ncsc = gpg.Context().keylist(pattern="ncsc.mil")
+ nsa = list(ncsc)
+ #+end_src
+
+
+*** Counting keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-keys-counting
+ :END:
+
+ Counting the number of keys in your public keybox (=pubring.kbx=),
+ the format which has superseded the old keyring format
+ (=pubring.gpg= and =secring.gpg=), or the number of secret keys is
+ a very simple task.
+
+ #+begin_src python
+ 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_src
+
+
+** Get key
+ :PROPERTIES:
+ :CUSTOM_ID: howto-get-key
+ :END:
+
+ An alternative method of getting a single key via its fingerprint
+ is available directly within a Context with =Context().get_key=.
+ This is the preferred method of selecting a key in order to modify
+ it, sign or certify it and for obtaining relevant data about a
+ single key as a part of other functions; when verifying a signature
+ made by that key, for instance.
+
+ 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_src python
+ import gpg
+
+ fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
+ key = gpg.Context().get_key(fingerprint)
+ #+end_src
+
+ Whereas this example demonstrates selecting the author's current
+ key with the =secret= key word argument set to =True=:
+
+ #+begin_src python
+ import gpg
+
+ fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
+ key = gpg.Context().get_key(fingerprint, secret=True)
+ #+end_src
+
+ It is, of course, quite possible to select expired, disabled and
+ revoked keys with this function, but only to effectively display
+ information about those keys.
+
+ It is also possible to use both unicode or string literals and byte
+ literals with the fingerprint when getting a key in this way.
+
+
+* Basic Functions
+ :PROPERTIES:
+ :CUSTOM_ID: howto-the-basics
+ :END:
+
+ The most frequently called features of any cryptographic library
+ will be the most fundamental tasks for encryption software. In this
+ section we will look at how to programmatically encrypt data,
+ decrypt it, sign it and verify signatures.
+
+
+** Encryption
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-encryption
+ :END:
+
+ Encrypting is very straight forward. In the first example below
+ the message, =text=, is encrypted to a single recipient's key. In
+ the second example the message will be encrypted to multiple
+ recipients.
+
+
+*** Encrypting to one key
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-encryption-single
+ :END:
+
+ Once the the Context is set the main issues with encrypting data
+ is essentially reduced to key selection and the keyword arguments
+ specified in the =gpg.Context().encrypt()= method.
+
+ Those keyword arguments are: =recipients=, a list of keys
+ encrypted to (covered in greater detail in the following section);
+ =sign=, whether or not to sign the plaintext data, see subsequent
+ sections on signing and verifying signatures below (defaults to
+ =True=); =sink=, to write results or partial results to a secure
+ sink instead of returning it (defaults to =None=); =passphrase=,
+ only used when utilising symmetric encryption (defaults to
+ =None=); =always_trust=, used to override the trust model settings
+ for recipient keys (defaults to =False=); =add_encrypt_to=,
+ utilises any preconfigured =encrypt-to= or =default-key= settings
+ in the user's =gpg.conf= file (defaults to =False=); =prepare=,
+ prepare for encryption (defaults to =False=); =expect_sign=,
+ prepare for signing (defaults to =False=); =compress=, compresses
+ the plaintext prior to encryption (defaults to =True=).
+
+ #+begin_src python
+ import gpg
+
+ a_key = "0x12345678DEADBEEF"
+ text = b"""Some text to test with.
+
+ Since the text in this case must be bytes, it is most likely that
+ the input form will be a separate file which is opened with "rb"
+ as this is the simplest method of obtaining the correct data
+ format.
+ """
+
+ c = gpg.Context(armor=True)
+ rkey = list(c.keylist(pattern=a_key, secret=False))
+ ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+ #+end_src
+
+ Though this is even more likely to be used like this; with the
+ plaintext input read from a file, the recipient keys used for
+ encryption regardless of key trust status and the encrypted output
+ also encrypted to any preconfigured keys set in the =gpg.conf=
+ file:
+
+ #+begin_src python
+ import gpg
+
+ a_key = "0x12345678DEADBEEF"
+
+ with open("secret_plans.txt", "rb") as afile:
+ text = afile.read()
+
+ c = gpg.Context(armor=True)
+ rkey = list(c.keylist(pattern=a_key, secret=False))
+ ciphertext, result, sign_result = c.encrypt(text, recipients=rkey,
+ sign=True, always_trust=True,
+ add_encrypt_to=True)
+
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+ #+end_src
+
+ If the =recipients= paramater is empty then the plaintext is
+ encrypted symmetrically. If no =passphrase= is supplied as a
+ parameter or via a callback registered with the =Context()= then
+ an out-of-band prompt for the passphrase via pinentry will be
+ invoked.
+
+
+*** Encrypting to multiple keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-encryption-multiple
+ :END:
+
+ Encrypting to multiple keys essentially just expands upon the key
+ selection process and the recipients from the previous examples.
+
+ The following example encrypts a message (=text=) to everyone with
+ an email address on the =gnupg.org= domain,[fn:3] but does /not/ encrypt
+ to a default key or other key which is configured to normally
+ encrypt to.
+
+ #+begin_src python
+ import gpg
+
+ text = b"""Oh look, another test message.
+
+ The same rules apply as with the previous example and more likely
+ than not, the message will actually be drawn from reading the
+ contents of a file or, maybe, from entering data at an input()
+ prompt.
+
+ Since the text in this case must be bytes, it is most likely that
+ the input form will be a separate file which is opened with "rb"
+ as this is the simplest method of obtaining the correct data
+ format.
+ """
+
+ c = gpg.Context(armor=True)
+ rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+ logrus = []
+
+ for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, sign=False,
+ always_trust=True)
+
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+ #+end_src
+
+ All it would take to change the above example to sign the message
+ and also encrypt the message to any configured default keys would
+ be to change the =c.encrypt= line to this:
+
+ #+begin_src python
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ always_trust=True,
+ add_encrypt_to=True)
+ #+end_src
+
+ The only keyword arguments requiring modification are those for
+ which the default values are changing. The default value of
+ =sign= is =True=, the default of =always_trust= is =False=, the
+ default of =add_encrypt_to= is =False=.
+
+ If =always_trust= is not set to =True= and any of the recipient
+ keys are not trusted (e.g. not signed or locally signed) then the
+ encryption will raise an error. It is possible to mitigate this
+ somewhat with something more like this:
+
+ #+begin_src python
+ import gpg
+
+ with open("secret_plans.txt.asc", "rb") as afile:
+ text = afile.read()
+
+ c = gpg.Context(armor=True)
+ rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+ logrus = []
+
+ for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+ try:
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, add_encrypt_to=True)
+ except gpg.errors.InvalidRecipients as e:
+ for i in range(len(e.recipients)):
+ for n in range(len(logrus)):
+ if logrus[n].fpr == e.recipients[i].fpr:
+ logrus.remove(logrus[n])
+ else:
+ pass
+ try:
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, add_encrypt_to=True)
+ except:
+ pass
+
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+ #+end_src
+
+ This will attempt to encrypt to all the keys searched for, then
+ remove invalid recipients if it fails and try again.
+
+
+** Decryption
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-decryption
+ :END:
+
+ Decrypting something encrypted to a key in one's secret keyring is
+ fairly straight forward.
+
+ In this example code, however, preconfiguring either
+ =gpg.Context()= or =gpg.core.Context()= as =c= is unnecessary
+ because there is no need to modify the Context prior to conducting
+ the decryption and since the Context is only used once, setting it
+ to =c= simply adds lines for no gain.
+
+ #+begin_src python
+ 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:
+ plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+ with open(newfile, "wb") as nfile:
+ nfile.write(plaintext)
+ #+end_src
+
+ The data available in plaintext in this example is the decrypted
+ content as a byte object in =plaintext[0]=, the recipient key IDs
+ and algorithms in =plaintext[1]= and the results of verifying any
+ signatures of the data in =plaintext[0]=.
+
+
+** Signing text and files
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing
+ :END:
+
+ The following sections demonstrate how to specify keys to sign with.
+
+
+*** Signing key selection
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-signers
+ :END:
+
+ By default GPGME and the Python bindings will use the default key
+ configured for the user invoking the GPGME API. If there is no
+ default key specified and there is more than one secret key
+ available it may be necessary to specify the key or keys with
+ which to sign messages and files.
+
+ #+begin_src python
+ import gpg
+
+ logrus = input("Enter the email address or string to match signing keys to: ")
+ hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+ sig_src = list(hancock)
+ #+end_src
+
+ The signing examples in the following sections include the
+ explicitly designated =signers= parameter in two of the five
+ examples; once where the resulting signature would be ASCII
+ armoured and once where it would not be armoured.
+
+ While it would be possible to enter a key ID or fingerprint here
+ to match a specific key, it is not possible to enter two
+ fingerprints and match two keys since the patten expects a string,
+ bytes or None and not a list. A string with two fingerprints
+ won't match any single key.
+
+
+*** Normal or default signing messages or files
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-normal
+ :END:
+
+ The normal or default signing process is essentially the same as
+ is most often invoked when also encrypting a message or file. So
+ when the encryption component is not utilised, the result is to
+ produce an encoded and signed output which may or may not be ASCII
+ armoured and which may or may not also be compressed.
+
+ By default compression will be used unless GnuPG detects that the
+ plaintext is already compressed. ASCII armouring will be
+ determined according to the value of =gpg.Context().armor=.
+
+ The compression algorithm is selected in much the same way as the
+ symmetric encryption algorithm or the hash digest algorithm is
+ when multiple keys are involved; from the preferences saved into
+ the key itself or by comparison with the preferences with all
+ other keys involved.
+
+ #+begin_src python
+ 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_src
+
+ Though everything in this example is accurate, it is more likely
+ that reading the input data from another file and writing the
+ result to a new file will be performed more like the way it is done
+ in the next example. Even if the output format is ASCII armoured.
+
+ #+begin_src python
+ 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_src
+
+
+*** Detached signing messages and files
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-detached
+ :END:
+
+ Detached signatures will often be needed in programmatic uses of
+ GPGME, either for signing files (e.g. tarballs of code releases)
+ or as a component of message signing (e.g. PGP/MIME encoded
+ email).
+
+ #+begin_src python
+ 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_src
+
+ As with normal signatures, detached signatures are best handled as
+ byte literals, even when the output is ASCII armoured.
+
+ #+begin_src python
+ 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_src
+
+
+*** Clearsigning messages or text
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-clear
+ :END:
+
+ Though PGP/in-line messages are no longer encouraged in favour of
+ PGP/MIME, there is still sometimes value in utilising in-line
+ signatures. This is where clear-signed messages or text is of
+ value.
+
+ #+begin_src python
+ 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_src
+
+ In spite of the appearance of a clear-signed message, the data
+ handled by GPGME in signing it must still be byte literals.
+
+ #+begin_src python
+ import gpg
+
+ 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_src
+
+
+** Signature verification
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-verification
+ :END:
+
+ Essentially there are two principal methods of verification of a
+ signature. The first of these is for use with the normal or
+ default signing method and for clear-signed messages. The second is
+ for use with files and data with detached signatures.
+
+ The following example is intended for use with the default signing
+ method where the file was not ASCII armoured:
+
+ #+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ gpg_file = "statement.txt.gpg"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(gpg_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+ else:
+ pass
+ #+end_src
+
+ Whereas this next example, which is almost identical would work
+ with normal ASCII armoured files and with clear-signed files:
+
+ #+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ asc_file = "statement.txt.asc"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(asc_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+ else:
+ pass
+ #+end_src
+
+ In both of the previous examples it is also possible to compare the
+ original data that was signed against the signed data in =data= to
+ see if it matches with something like this:
+
+ #+begin_src python
+ with open(filename, "rb") as afile:
+ text = afile.read()
+
+ if text == data:
+ print("Good signature.")
+ else:
+ pass
+ #+end_src
+
+ The following two examples, however, deal with detached signatures.
+ With his method of verification the data that was signed does not
+ get returned since it is already being explicitly referenced in the
+ first argument of =c.verify=. So =data= is =None= and only the
+ information in =result= is available.
+
+ #+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ sig_file = "statement.txt.sig"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(filename), open(sig_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+ else:
+ pass
+ #+end_src
+
+ #+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ asc_file = "statement.txt.asc"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(filename), open(asc_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is not None:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+ else:
+ pass
+ #+end_src
+
+
+* Creating keys and subkeys
+ :PROPERTIES:
+ :CUSTOM_ID: key-generation
+ :END:
+
+ The one thing, aside from GnuPG itself, that GPGME depends on, of
+ course, is the keys themselves. So it is necessary to be able to
+ generate them and modify them by adding subkeys, revoking or
+ disabling them, sometimes deleting them and doing the same for user
+ IDs.
+
+ In the following examples a key will be created for the world's
+ greatest secret agent, Danger Mouse. Since Danger Mouse is a secret
+ agent he needs to be able to protect information to =SECRET= level
+ clearance, so his keys will be 3072-bit keys.
+
+ The pre-configured =gpg.conf= file which sets cipher, digest and
+ other preferences contains the following configuration parameters:
+
+ #+begin_src conf
+ expert
+ allow-freeform-uid
+ allow-secret-key-import
+ trust-model tofu+pgp
+ tofu-default-policy unknown
+ enable-large-rsa
+ enable-dsa2
+ # cert-digest-algo SHA256
+ cert-digest-algo SHA512
+ default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
+ personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
+ personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
+ personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+ #+end_src
+
+
+** Primary key
+ :PROPERTIES:
+ :CUSTOM_ID: keygen-primary
+ :END:
+
+ Generating a primary key uses the =create_key= method in a Context.
+ It contains multiple arguments and keyword arguments, including:
+ =userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=,
+ =certify=, =authenticate=, =passphrase= and =force=. The defaults
+ for all of those except =userid=, =algorithm=, =expires_in=,
+ =expires= and =passphrase= is =False=. The defaults for
+ =algorithm= and =passphrase= is =None=. The default for
+ =expires_in= is =0=. The default for =expires= is =True=. There
+ is no default for =userid=.
+
+ If =passphrase= is left as =None= then the key will not be
+ generated with a passphrase, if =passphrase= is set to a string
+ then that will be the passphrase and if =passphrase= is set to
+ =True= then gpg-agent will launch pinentry to prompt for a
+ passphrase. For the sake of convenience, these examples will keep
+ =passphrase= set to =None=.
+
+ #+begin_src python
+ import gpg
+
+ c = gpg.Context()
+
+ c.home_dir = "~/.gnupg-dm"
+ userid = "Danger Mouse <[email protected]>"
+
+ dmkey = c.create_key(userid, algorithm = "rsa3072", expires_in = 31536000,
+ sign = True, certify = True)
+ #+end_src
+
+ One thing to note here is the use of setting the =c.home_dir=
+ parameter. This enables generating the key or keys in a different
+ location. In this case to keep the new key data created for this
+ example in a separate location rather than adding it to existing
+ and active key store data. As with the default directory,
+ =~/.gnupg=, any temporary or separate directory needs the
+ permissions set to only permit access by the directory owner. On
+ posix systems this means setting the directory permissions to 700.
+
+ The successful generation of the key can be confirmed via the
+ returned =GenkeyResult= object, which includes the following data:
+
+ #+begin_src python
+ print("""
+ Fingerprint: {0}
+ Primary Key: {1}
+ Public Key: {2}
+ Secret Key: {3}
+ Sub Key: {4}
+ User IDs: {5}
+ """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
+ dmkey.uid))
+ #+end_src
+
+ Alternatively the information can be confirmed using the command
+ line program:
+
+ #+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse <[email protected]>
+
+ bash-4.4$
+ #+end_src
+
+ As with generating keys manually, to preconfigure expanded
+ preferences for the cipher, digest and compression algorithms, the
+ =gpg.conf= file must contain those details in the home directory in
+ which the new key is being generated. I used a cut down version of
+ my own =gpg.conf= file in order to be able to generate this:
+
+ #+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
+ Secret key is available.
+
+ sec rsa3072/026D2F19E99E63AA
+ created: 2018-03-15 expires: 2019-03-15 usage: SC
+ trust: ultimate validity: ultimate
+ [ultimate] (1). Danger Mouse <[email protected]>
+
+ [ultimate] (1). Danger Mouse <[email protected]>
+ Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
+ Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
+ Compression: ZLIB, BZIP2, ZIP, Uncompressed
+ Features: MDC, Keyserver no-modify
+
+ bash-4.4$
+ #+end_src
+
+
+** Subkeys
+ :PROPERTIES:
+ :CUSTOM_ID: keygen-subkeys
+ :END:
+
+ Adding subkeys to a primary key is fairly similar to creating the
+ primary key with the =create_subkey= method. Most of the arguments
+ are the same, but not quite all. Instead of the =userid= argument
+ there is now a =key= argument for selecting which primary key to
+ add the subkey to.
+
+ In the following example an encryption subkey will be added to the
+ primary key. Since Danger Mouse is a security conscious secret
+ agent, this subkey will only be valid for about six months, half
+ the length of the primary key.
+
+ #+begin_src python
+ import gpg
+
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
+
+ key = c.get_key(dmkey.fpr, secret = True)
+ dmsub = c.create_subkey(key, algorithm = "rsa3072", expires_in = 15768000,
+ encrypt = True)
+ #+end_src
+
+ As with the primary key, the results here can be checked with:
+
+ #+begin_src python
+ print("""
+ Fingerprint: {0}
+ Primary Key: {1}
+ Public Key: {2}
+ Secret Key: {3}
+ Sub Key: {4}
+ User IDs: {5}
+ """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
+ dmsub.uid))
+ #+end_src
+
+ As well as on the command line with:
+
+ #+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse <[email protected]>
+ ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+ bash-4.4$
+ #+end_src
+
+
+** User IDs
+ :PROPERTIES:
+ :CUSTOM_ID: keygen-uids
+ :END:
+
+ By comparison to creating primary keys and subkeys, adding a new
+ user ID to an existing key is much simpler. The method used to do
+ this is =key_add_uid= and the only arguments it takes are for the
+ =key= and the new =uid=.
+
+ #+begin_src python
+ import gpg
+
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
+
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret = True)
+ uid = "Danger Mouse <[email protected]>"
+
+ c.key_add_uid(key, uid)
+ #+end_src
+
+ Unsurprisingly the result of this is:
+
+ #+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse <[email protected]>
+ uid [ultimate] Danger Mouse <[email protected]>
+ ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+ bash-4.4$
+ #+end_src
+
+
+** Key certification
+ :PROPERTIES:
+ :CUSTOM_ID: key-sign
+ :END:
+
+ Since key certification is more frequently referred to as key
+ signing, the method used to perform this function is =key_sign=.
+
+ The =key_sign= method takes four arguments: =key=, =uids=,
+ =expires_in= and =local=. The default value of =uids= is =None=
+ and which results in all user IDs being selected. The default
+ values of =expires_in= snd =local= is =False=; which result in the
+ signature never expiring and being able to be exported.
+
+ The =key= is the key being signed rather than the key doing the
+ signing. To change the key doing the signing refer to the signing
+ key selection above for signing messages and files.
+
+ If the =uids= value is not =None= then it must either be a string
+ to match a single user ID or a list of strings to match multiple
+ user IDs. In this case the matching of those strings must be
+ precise and it is case sensitive.
+
+ To sign Danger Mouse's key for just the initial user ID with a
+ signature which will last a little over a month, do this:
+
+ #+begin_src python
+ import gpg
+
+ c = gpg.Context()
+ uid = "Danger Mouse <[email protected]>"
+
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret = True)
+ c.key_sign(key, uids = uid, expires_in = 2764800)
+ #+end_src
+
+
+* Miscellaneous work-arounds
+ :PROPERTIES:
+ :CUSTOM_ID: cheats-and-hacks
+ :END:
+
+
+** Group lines
+ :PROPERTIES:
+ :CUSTOM_ID: group-lines
+ :END:
+
+ There is not yet an easy way to access groups configured in the
+ gpg.conf file from within GPGME. As a consequence these central
+ groupings of keys cannot be shared amongst multiple programs, such
+ as MUAs readily.
+
+ The following code, however, provides a work-around for obtaining
+ this information in Python.
+
+ #+begin_src python
+ import subprocess
+
+ lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines()
+
+ for i in range(len(lines)):
+ if lines[i].startswith("group") is True:
+ line = lines[i]
+ else:
+ pass
+
+ groups = line.split(":")[-1].replace('"', '').split(',')
+
+ group_lines = groups
+ for i in range(len(group_lines)):
+ group_lines[i] = group_lines[i].split("=")
+
+ group_lists = group_lines
+ for i in range(len(group_lists)):
+ group_lists[i][1] = group_lists[i][1].split()
+ #+end_src
+
+ The result of that code is that =group_lines= is a list of lists
+ where =group_lines[i][0]= is the name of the group and
+ =group_lines[i][1]= is the key IDs of the group as a string.
+
+ The =group_lists= result is very similar in that it is a list of
+ lists. The first part, =group_lists[i][0]= matches
+ =group_lines[i][0]= as the name of the group, but
+ =group_lists[i][1]= is the key IDs of the group as a string.
+
+
+* Copyright and Licensing
+ :PROPERTIES:
+ :CUSTOM_ID: copyright-and-license
+ :END:
+
+
+** Copyright (C) The GnuPG Project, 2018
+ :PROPERTIES:
+ :CUSTOM_ID: copyright
+ :END:
+
+ Copyright © The GnuPG Project, 2018.
+
+
+** License GPL compatible
+ :PROPERTIES:
+ :CUSTOM_ID: license
+ :END:
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+
+* Footnotes
+
+[fn:1] =Short_History.org= and/or =Short_History.html=.
+
+[fn:2] The =lang/python/docs/= directory in the GPGME source.
+
+[fn:3] 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.
diff --git a/lang/python/docs/TODO.org b/lang/python/docs/TODO.org
index 9f039d81..add8f4ff 100644
--- a/lang/python/docs/TODO.org
+++ b/lang/python/docs/TODO.org
@@ -28,13 +28,74 @@
to produce reST versions via Pandoc and DITA XML can be reached
through converting to either Markdown or XHTML first.
-** TODO Documentation HOWTO
+
+** STARTED Documentation HOWTO
:PROPERTIES:
:CUSTOM_ID: todo-docs-howto
:END:
+ - State "STARTED" from "TODO" [2018-03-08 Thu 13:59] \\
+ Started yesterday.
Write a HOWTO style guide for the current Python bindings.
+*** DONE Start python bindings HOWTO
+ CLOSED: [2018-03-07 Wed 18:14]
+ :PROPERTIES:
+ :CUSTOM_ID: howto-start
+ :END:
+
+
+*** STARTED Include certain specific instructions in the HOWTO
+ :PROPERTIES:
+ :CUSTOM_ID: howto-requests
+ :END:
+
+ Note: moved the S/MIME bits out to their own section of the TODO
+ list and may be served better by separate HOWTO documentation
+ anyway.
+
+ - State "STARTED" from "TODO" [2018-03-09 Fri 15:27]
+ Some functions can be worked out from the handful of examples
+ available, but many more can't and I've already begun receiving
+ requests for certain functions to be explained.
+
+
+**** DONE Standard scenarios
+ CLOSED: [2018-03-19 Mon 12:34]
+ :PROPERTIES:
+ :CUSTOM_ID: howto-the-basics
+ :END:
+
+ - State "DONE" from "STARTED" [2018-03-19 Mon 12:34] \\
+ All four of those are done.
+ - State "STARTED" from "TODO" [2018-03-09 Fri 15:26] \\
+ Began with the example code, now to add the text.
+ What everyone expects: encryption, decryption, signing and verifying.
+
+
+**** STARTED Key control
+ :PROPERTIES:
+ :CUSTOM_ID: howto-key-control
+ :END:
+
+ - State "STARTED" from "TODO" [2018-03-19 Mon 12:35] \\
+ Generating keys and subkeys are done, but revocation is still to be done.
+ Generating keys, adding subkeys, revoking subkeys (and keeping
+ the cert key), adding and revoking UIDs, signing/certifying keys.
+
+
+**** DONE More key control
+ CLOSED: [2018-03-19 Mon 12:36]
+ :PROPERTIES:
+ :CUSTOM_ID: howto-key-selection
+ :END:
+
+ - State "DONE" from "TODO" [2018-03-19 Mon 12:36] \\
+ Key selection, searching, matching and counting is done.
+ Selecting keys to encrypt to or manipulate in other ways (e.g. as
+ with key control or the basics).
+
+
** TODO Documentation SWIG
:PROPERTIES:
:CUSTOM_ID: todo-docs-swig
@@ -47,6 +108,7 @@
something to be used in conjunction with the existing GPGME
documentation which makes it easier for Python developers to use.
+
** TODO GUI examples
:PROPERTIES:
:CUSTOM_ID: todo-gui-examples
@@ -56,6 +118,7 @@
to either match or be similar to the old GTK2 examples available
with PyME.
+
** TODO Replace SWIG
:PROPERTIES:
:CUSTOM_ID: todo-replace-swig
@@ -71,6 +134,7 @@
bindings using a more suitable means of interfacing with the GPGME
C source code.
+
*** TODO Replacement for SWIG
:PROPERTIES:
:CUSTOM_ID: todo-replace-swig-replacement
@@ -80,6 +144,7 @@
the most viable candidate, but some additional testing and checks
are yet to be completed.
+
** TODO API for an API
:PROPERTIES:
:CUSTOM_ID: todo-api-squared
@@ -94,6 +159,16 @@
available or for which it is too difficult to create proper
bindings.
+
+** TODO S/MIME
+ :PROPERTIES:
+ :CUSTOM_ID: s-mime
+ :END:
+
+ Eventually add some of this, but the OpenPGP details are far more
+ important at the moment.
+
+
* Project Task Details
:PROPERTIES:
:CUSTOM_ID: detailed-tasks
diff --git a/lang/python/examples/howto/README.org b/lang/python/examples/howto/README.org
new file mode 100644
index 00000000..b74ae7e2
--- /dev/null
+++ b/lang/python/examples/howto/README.org
@@ -0,0 +1,58 @@
+#+TITLE: GPGME Python Bindings HOWTO Examples
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman}
+#+LATEX_HEADER: \author{Ben McGinnes <[email protected]>}
+
+
+* Examples
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python3-examples
+ :END:
+
+ The contents of this directory are the examples included in the /GNU
+ Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO/ file. Each
+ script is explicitly for Python 3 and specifically for Python 3.4 or
+ later.
+
+ Some of these scripts may work with Python 2.7, but there are no
+ guarantees. They will include the relevant imports from the
+ =__future__= module to facilitate that if possible.
+
+
+* Copyright and Licensing
+ :PROPERTIES:
+ :CUSTOM_ID: copyright-and-license
+ :END:
+
+ Unless otherwise stated, all the examples in this directory are
+ released under the same terms as GPGME itself; that is they are dual
+ licensed under the terms of both 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).
+
+
+** Copyright (C) The GnuPG Project, 2018
+ :PROPERTIES:
+ :CUSTOM_ID: copyright
+ :END:
+
+ Copyright © The GnuPG Project, 2018.
+
+
+** License GPL compatible
+ :PROPERTIES:
+ :CUSTOM_ID: license
+ :END:
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is 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.
diff --git a/lang/python/examples/howto/clear-sign-file.py b/lang/python/examples/howto/clear-sign-file.py
new file mode 100755
index 00000000..597bbc59
--- /dev/null
+++ b/lang/python/examples/howto/clear-sign-file.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+
+"""
+Clear-signs a file with a specified key. If entering both the key and the
+filename on the command line, the key must be entered first.
+"""
+
+if len(sys.argv) > 3:
+ logrus = sys.argv[1]
+ filename = " ".join(sys.argv[2:])
+elif len(sys.argv) == 3:
+ logrus = sys.argv[1]
+ filename = sys.argv[2]
+elif len(sys.argv) == 2:
+ logrus = sys.argv[1]
+ filename = input("Enter the path and filename to sign: ")
+else:
+ logrus = input("Enter the fingerprint or key ID to sign with: ")
+ filename = input("Enter the path and filename to sign: ")
+
+with open(filename, "rb") as f:
+ text = f.read()
+
+key = list(gpg.Context().keylist(pattern=logrus))
+
+with gpg.Context(armor=True, signers=key) as c:
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+ with open("{0}.asc".format(filename), "wb") as f:
+ f.write(signed_data)
diff --git a/lang/python/examples/howto/decrypt-file.py b/lang/python/examples/howto/decrypt-file.py
new file mode 100755
index 00000000..60a050bd
--- /dev/null
+++ b/lang/python/examples/howto/decrypt-file.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+
+if len(sys.argv) == 3:
+ ciphertext = sys.argv[1]
+ newfile = sys.argv[2]
+elif len(sys.argv) == 2:
+ ciphertext = sys.argv[1]
+ newfile = input("Enter path and filename of file to save decrypted data to: ")
+else:
+ 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:
+ plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+
+with open(newfile, "wb") as nfile:
+ nfile.write(plaintext)
diff --git a/lang/python/examples/howto/detach-sign-file.py b/lang/python/examples/howto/detach-sign-file.py
new file mode 100755
index 00000000..99fbe65e
--- /dev/null
+++ b/lang/python/examples/howto/detach-sign-file.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+
+"""
+Signs a file with a specified key. If entering both the key and the filename
+on the command line, the key must be entered first.
+
+Will produce both an ASCII armoured and GPG binary format copy of the detached
+signature file.
+"""
+
+if len(sys.argv) > 3:
+ logrus = sys.argv[1]
+ filename = " ".join(sys.argv[2:])
+elif len(sys.argv) == 3:
+ logrus = sys.argv[1]
+ filename = sys.argv[2]
+elif len(sys.argv) == 2:
+ logrus = sys.argv[1]
+ filename = input("Enter the path and filename to sign: ")
+else:
+ logrus = input("Enter the fingerprint or key ID to sign with: ")
+ filename = input("Enter the path and filename to sign: ")
+
+with open(filename, "rb") as f:
+ text = f.read()
+
+key = list(gpg.Context().keylist(pattern=logrus))
+
+with gpg.Context(armor=True, signers=key) as ca:
+ signed_data, result = ca.sign(text, mode=gpg.constants.sig.mode.DETACH)
+ with open("{0}.asc".format(filename), "wb") as fa:
+ fa.write(signed_data)
+
+with gpg.Context(signers=key) as cb:
+ signed_data, result = cb.sign(text, mode=gpg.constants.sig.mode.DETACH)
+ with open("{0}.sig".format(filename), "wb") as fb:
+ fb.write(signed_data)
diff --git a/lang/python/examples/howto/encrypt-file.py b/lang/python/examples/howto/encrypt-file.py
new file mode 100755
index 00000000..ad4e1cef
--- /dev/null
+++ b/lang/python/examples/howto/encrypt-file.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+
+"""
+Encrypts a file to a specified key. If entering both the key and the filename
+on the command line, the key must be entered first.
+
+Will produce both an ASCII armoured and GPG binary format copy of the encrypted
+file.
+"""
+
+if len(sys.argv) > 3:
+ a_key = sys.argv[1]
+ filename = " ".join(sys.argv[2:])
+elif len(sys.argv) == 3:
+ a_key = sys.argv[1]
+ filename = sys.argv[2]
+elif len(sys.argv) == 2:
+ a_key = sys.argv[1]
+ filename = input("Enter the path and filename to encrypt: ")
+else:
+ a_key = input("Enter the fingerprint or key ID to encrypt to: ")
+ filename = input("Enter the path and filename to encrypt: ")
+
+rkey = list(gpg.Context().keylist(pattern=a_key, secret=False))
+with open(filename, "rb") as f:
+ text = f.read()
+
+with gpg.Context(armor=True) as ca:
+ try:
+ ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey,
+ sign=False)
+ with open("{0}.asc".format(filename), "wb") as fa:
+ fa.write(ciphertext)
+ except gpg.errors.InvalidRecipients as e:
+ print(e)
+
+with gpg.Context() as cg:
+ try:
+ ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey,
+ sign=False)
+ with open("{0}.gpg".format(filename), "wb") as fg:
+ fg.write(ciphertext)
+ except gpg.errors.InvalidRecipients as e:
+ print(e)
diff --git a/lang/python/examples/howto/encrypt-sign-file.py b/lang/python/examples/howto/encrypt-sign-file.py
new file mode 100755
index 00000000..41aaac86
--- /dev/null
+++ b/lang/python/examples/howto/encrypt-sign-file.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+
+"""
+Signs and encrypts a file to a specified key. If entering both the key and the
+filename on the command line, the key must be entered first.
+
+Signs with and also encrypts to the default key of the user invoking the
+script. Will treat all recipients as trusted to permit encryption.
+
+Will produce both an ASCII armoured and GPG binary format copy of the signed
+and encrypted file.
+"""
+
+if len(sys.argv) > 3:
+ a_key = sys.argv[1]
+ filename = " ".join(sys.argv[2:])
+elif len(sys.argv) == 3:
+ a_key = sys.argv[1]
+ filename = sys.argv[2]
+elif len(sys.argv) == 2:
+ a_key = sys.argv[1]
+ filename = input("Enter the path and filename to encrypt: ")
+else:
+ a_key = input("Enter the fingerprint or key ID to encrypt to: ")
+ filename = input("Enter the path and filename to encrypt: ")
+
+rkey = list(gpg.Context().keylist(pattern=a_key, secret=False))
+with open(filename, "rb") as f:
+ text = f.read()
+
+with gpg.Context(armor=True) as ca:
+ ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey,
+ always_trust=True,
+ add_encrypt_to=True)
+ with open("{0}.asc".format(filename), "wb") as fa:
+ fa.write(ciphertext)
+
+with gpg.Context() as cg:
+ ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey,
+ always_trust=True,
+ add_encrypt_to=True)
+ with open("{0}.gpg".format(filename), "wb") as fg:
+ fg.write(ciphertext)
diff --git a/lang/python/examples/howto/groups.py b/lang/python/examples/howto/groups.py
new file mode 100644
index 00000000..5e7fdf60
--- /dev/null
+++ b/lang/python/examples/howto/groups.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import subprocess
+
+"""
+Intended for use with other scripts.
+
+Usage: from groups import group_lists
+"""
+
+lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines()
+
+for i in range(len(lines)):
+ if lines[i].startswith("group") is True:
+ line = lines[i]
+ else:
+ pass
+
+groups = line.split(":")[-1].replace('"', '').split(',')
+
+group_lines = groups
+for i in range(len(group_lines)):
+ group_lines[i] = group_lines[i].split("=")
+
+group_lists = group_lines
+for i in range(len(group_lists)):
+ group_lists[i][1] = group_lists[i][1].split()
diff --git a/lang/python/examples/howto/keycount.py b/lang/python/examples/howto/keycount.py
new file mode 100755
index 00000000..8e25454c
--- /dev/null
+++ b/lang/python/examples/howto/keycount.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import 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))
diff --git a/lang/python/examples/howto/sign-file.py b/lang/python/examples/howto/sign-file.py
new file mode 100755
index 00000000..01006df0
--- /dev/null
+++ b/lang/python/examples/howto/sign-file.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+
+"""
+Signs a file with a specified key. If entering both the key and the filename
+on the command line, the key must be entered first.
+
+Will produce both an ASCII armoured and GPG binary format copy of the signed
+file.
+"""
+
+if len(sys.argv) > 3:
+ logrus = sys.argv[1]
+ filename = " ".join(sys.argv[2:])
+elif len(sys.argv) == 3:
+ logrus = sys.argv[1]
+ filename = sys.argv[2]
+elif len(sys.argv) == 2:
+ logrus = sys.argv[1]
+ filename = input("Enter the path and filename to sign: ")
+else:
+ logrus = input("Enter the fingerprint or key ID to sign with: ")
+ filename = input("Enter the path and filename to sign: ")
+
+with open(filename, "rb") as f:
+ text = f.read()
+
+key = list(gpg.Context().keylist(pattern=logrus))
+
+with gpg.Context(armor=True, signers=key) as ca:
+ signed_data, result = ca.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+ with open("{0}.asc".format(filename), "wb") as fa:
+ fa.write(signed_data)
+
+with gpg.Context(signers=key) as cg:
+ signed_data, result = cg.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+ with open("{0}.gpg".format(filename), "wb") as fg:
+ fg.write(signed_data)
diff --git a/lang/python/examples/howto/verify-signatures.py b/lang/python/examples/howto/verify-signatures.py
new file mode 100755
index 00000000..8aafc3ba
--- /dev/null
+++ b/lang/python/examples/howto/verify-signatures.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+import time
+
+"""
+Verifies a signed file which has been signed with a detached signature.
+"""
+
+if len(sys.argv) > 2:
+ filename = sys.argv[1]
+ sig_file = sys.argv[2]
+elif len(sys.argv) == 2:
+ filename = sys.argv[1]
+ sig_file = input("Enter the path and filename of the detached signature: ")
+else:
+ filename = input("Enter the path and filename to verify: ")
+ sig_file = input("Enter the path and filename of the detached signature: ")
+
+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
diff --git a/lang/python/examples/howto/verify-signed-file.py b/lang/python/examples/howto/verify-signed-file.py
new file mode 100755
index 00000000..9f8702f5
--- /dev/null
+++ b/lang/python/examples/howto/verify-signed-file.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
+# Lesser General Public Licensefor more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import gpg
+import sys
+import time
+
+"""
+Verifies a signed file which has been signed with either NORMAL or CLEAR modes.
+"""
+
+if len(sys.argv) > 2:
+ filename = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+ filename = sys.argv[1]
+else:
+ filename = input("Enter the path and filename to sign: ")
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(filename))
+ 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
diff --git a/lang/python/examples/encrypt-to-all.py b/lang/python/examples/low_level-encrypt_to_all.py
index bad4220c..bad4220c 100755
--- a/lang/python/examples/encrypt-to-all.py
+++ b/lang/python/examples/low_level-encrypt_to_all.py
diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c
index 3e2dc785..e7a7a6f3 100644
--- a/src/gpgme-tool.c
+++ b/src/gpgme-tool.c
@@ -3101,7 +3101,7 @@ cmd_hash_algo_name (assuan_context_t ctx, char *line)
static const char hlp_identify[] =
- "IDENTIY\n"
+ "IDENTIFY\n"
"\n"
"Identify the type of data set with the INPUT command.";
static gpg_error_t