aboutsummaryrefslogtreecommitdiffstats
path: root/lang/python/doc/src/gpgme-python-howto.org
diff options
context:
space:
mode:
authorBen McGinnes <[email protected]>2018-10-30 14:24:47 +0000
committerBen McGinnes <[email protected]>2018-10-30 16:06:24 +0000
commitd5c22966d5bf397cccfa171095885cd207ef6ae3 (patch)
tree26034c7802a554e8ba2d556b30fcf2e4f018703e /lang/python/doc/src/gpgme-python-howto.org
parentw32: Fix previous commit. (diff)
downloadgpgme-d5c22966d5bf397cccfa171095885cd207ef6ae3.tar.gz
gpgme-d5c22966d5bf397cccfa171095885cd207ef6ae3.zip
docs: python
* dropped the ".org" extension and added a mode line for each file as this will make scripting format conversions a lot easier.
Diffstat (limited to 'lang/python/doc/src/gpgme-python-howto.org')
-rw-r--r--lang/python/doc/src/gpgme-python-howto.org3043
1 files changed, 0 insertions, 3043 deletions
diff --git a/lang/python/doc/src/gpgme-python-howto.org b/lang/python/doc/src/gpgme-python-howto.org
deleted file mode 100644
index 3c389d12..00000000
--- a/lang/python/doc/src/gpgme-python-howto.org
+++ /dev/null
@@ -1,3043 +0,0 @@
-#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
-#+AUTHOR: Ben McGinnes
-#+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.4 |
-| GPGME Version: | 1.12.0 |
-| Author: | [[https://gnupg.org/people/index.html#sec-1-5][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.
-
-
-** Unofficial Drafts
- :PROPERTIES:
- :CUSTOM_ID: unofficial-drafts
- :END:
-
-In addition to shipping with each release of GPGME, there is a section
-on locations to read or download [[#draft-editions][draft editions]] of this document from
-at the end of it. These are unofficial versions produced in between
-major releases.
-
-
-** What's New
- :PROPERTIES:
- :CUSTOM_ID: new-stuff
- :END:
-
-The most obviously new point for those reading this guide is this
-section on other new things, but that's hardly important. Not given
-all the other things which spurred the need for adding this section
-and its subsections.
-
-*** New in GPGME 1·12·0
- :PROPERTIES:
- :CUSTOM_ID: new-stuff-1-12-0
- :END:
-
-There have been quite a number of additions to GPGME and the Python
-bindings to it since the last release of GPGME with versions 1.11.0
-and 1.11.1 in April, 2018.
-
-The bullet points of new additiions are:
-
-- an expanded section on [[#installation][installing]] and [[#snafu][troubleshooting]] the Python
- bindings.
-- The release of Python 3.7.0; which appears to be working just fine
- with our bindings, in spite of intermittent reports of problems for
- many other Python projects with that new release.
-- Python 3.7 has been moved to the head of the specified python
- versions list in the build process.
-- In order to fix some other issues, there are certain underlying
- functions which are more exposed through the [[#howto-get-context][gpg.Context()]], but
- ongoing documentation ought to clarify that or otherwise provide the
- best means of using the bindings. Some additions to =gpg.core= and
- the =Context()=, however, were intended (see below).
-- Continuing work in identifying and confirming the cause of
- oft-reported [[#snafu-runtime-not-funtime][problems installing the Python bindings on Windows]].
-- GSOC: Google's Surreptitiously Ordered Conscription ... erm ... oh,
- right; Google's Summer of Code. Though there were two hopeful
- candidates this year; only one ended up involved with the GnuPG
- Project directly, the other concentrated on an unrelated third party
- project with closer ties to one of the GNU/Linux distributions than
- to the GnuPG Project. Thus the Python bindings benefited from GSOC
- participant Jacob Adams, who added the key_import function; building
- on prior work by Tobias Mueller.
-- Several new methods functions were added to the gpg.Context(),
- including: [[#howto-import-key][key_import]], [[#howto-export-key][key_export]], [[#howto-export-public-key][key_export_minimal]] and
- [[#howto-export-secret-key][key_export_secret]].
-- Importing and exporting examples include versions integrated with
- Marcel Fest's recently released [[https://github.com/Selfnet/hkp4py][HKP for Python]] module. Some
- [[#hkp4py][additional notes on this module]] are included at the end of the HOWTO.
-- Instructions for dealing with semi-walled garden implementations
- like ProtonMail are also included. This is intended to make things
- a little easier when communicating with users of ProtonMail's
- services and should not be construed as an endorsement of said
- service. The GnuPG Project neither favours, nor disfavours
- ProtonMail and the majority of this deals with interacting with the
- ProtonMail keyserver.
-- Semi-formalised the location where [[#draft-editions][draft versions]] of this HOWTO may
- periodically be accessible. This is both for the reference of
- others and testing the publishing of the document itself. Renamed
- this file at around the same time.
-- The Texinfo documentation build configuration has been replicated
- from the parent project in order to make to maintain consistency
- with that project (and actually ship with each release).
-- a reStructuredText (=.rst=) version is also generated for Python
- developers more used to and comfortable with that format as it is
- the standard Python documentation format and Python developers may
- wish to use it with Sphinx. Please note that there has been no
- testing of the reStructuredText version with Sphinx at all. The
- reST file was generated by the simple expedient of using [[https://pandoc.org/][Pandoc]].
-- Added a new section for [[#advanced-use][advanced or experimental use]].
-- Began the advanced use cases with [[#cython][a section]] on using the module with
- [[https://cython.org/][Cython]].
-- Added a number of new scripts to the =example/howto/= directory;
- some of which may be in advance of their planned sections of the
- HOWTO (and some are just there because it seemed like a good idea at
- the time).
-- Cleaned up a lot of things under the hood.
-
-
-* 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 [[file:short-history.org][Short History]] document.[fn:1]
-
-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.
-
-See the additional notes regarding [[#snafu-cffi][CFFI and SWIG]] at the end of this
-section for further details.
-
-
-** 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 CPython 2.7 and with Python 3 that means CPython 3.4 or
- higher.
-2. [[https://www.swig.org][SWIG]].
-3. GPGME itself. Which also means that all of GPGME's dependencies
- must be installed too.
-
-
-*** Recommended Additions
- :PROPERTIES:
- :CUSTOM_ID: gpgme-python-recommendations
- :END:
-
-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.
-
-1. If possible, use Python 3 instead of 2.
-2. 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.
-3. If possible add the following Python modules which are not part of
- the standard library: [[http://docs.python-requests.org/en/latest/index.html][Requests]], [[https://cython.org/][Cython]] and [[https://github.com/Selfnet/hkp4py][hkp4py]]. 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 the planned additions to the
-[[#advanced-use][Advanced]] section, 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.
-
-
-** 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.7=, =python3.6=, =python3.5= and =python3.4=.[fn:2]
-
-On systems where =python= is actually =python3= and not =python2= it
-may be possible that =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.
-
-
-*** Installing GPGME
- :PROPERTIES:
- :CUSTOM_ID: install-gpgme
- :END:
-
-See the GPGME =README= file for details of how to install GPGME from
-source.
-
-
-** Known Issues
- :PROPERTIES:
- :CUSTOM_ID: snafu
- :END:
-
-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.
-
-
-*** Breaking Builds
- :PROPERTIES:
- :CUSTOM_ID: snafu-a-swig-of-this-builds-character
- :END:
-
-Occasionally when installing GPGME with the Python bindings included
-it may be observed that the =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 =make check= and
-=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 =lang/python/python2-gpg/= and
-=lang/python/python3-gpg/= directories. These should contain the
-build output generated during compilation, including the complete
-bindings and module installed into =site-packages=.
-
-Occasionally the errors in the early part or some other conflict
-(e.g. not installing as */root/* or */su/*) may result in nothing
-being installed to the relevant =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
-*root* user or prepended with =sudo -H=[fn:3] in the =lang/python/=
-directory:
-
-#+BEGIN_SRC shell
- /path/to/pythonX.Y setup.py build
- /path/to/pythonX.Y setup.py build
- /path/to/pythonX.Y setup.py install
-#+END_SRC
-
-Yes, the build command does need to be run twice. Yes, you still need
-to run the potentially failing or incomplete steps during the
-=configure=, =make= and =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
-=setup.py= and =gpgme.h= files).
-
-
-**** IMPORTANT Note
- :PROPERTIES:
- :CUSTOM_ID: snafu-swig-build-note
- :END:
-
-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 and so that just happens by default currently.
-
-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 =configure= steps for GPGME using
-the =--enable-languages=$LANGUAGE= option.
-
-
-*** Reinstalling Responsibly
- :PROPERTIES:
- :CUSTOM_ID: snafu-lessons-for-the-lazy
- :END:
-
-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 =site-packages= directory for the version of Python in
-question and removing the =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.
-
-
-*** Multiple installations
- :PROPERTIES:
- :CUSTOM_ID: snafu-the-full-monty
- :END:
-
-For a veriety 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 *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.
-
-
-*** Won't Work With Windows
- :PROPERTIES:
- :CUSTOM_ID: snafu-runtime-not-funtime
- :END:
-
-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
-[[https://python.org][Python website]] (i.e. mostly MSI installers, sometimes self-extracting
-=.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 /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:
-
- 1. Compile and install the GnuPG stack, including GPGME and the
- Python bibdings 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.
-
- 2. Compile and install Python using the same tools used by choice,
- such as MinGW or Msys2.
-
-Do *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.
-
-
-*** CFFI is the Best™ and GPGME should use it instead of SWIG
- :PROPERTIES:
- :CUSTOM_ID: snafu-cffi
- :END:
-
-There are many reasons for favouring [[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 =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 =gpgme-tool.c= file in the GPGME =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.
-
-
-*** Virtualised Environments
- :PROPERTIES:
- :CUSTOM_ID: snafu-venv
- :END:
-
-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 =/usr/local= or
-=/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
-=sites.pth= file in the =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. [[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. [[https://www.virtualbox.org/][VirtualBox]]), a
-hardware emulation layer (e.g. [[https://www.qemu.org/][QEMU]]) or an application container
-(e.g. [[https://www.docker.com/why-docker][Docker]]).
-
-Finally it should be noted that the limited tests conducted thus far
-have been using the =virtualenv= command in a new directory to create
-the virtual python environment. As opposed to the standard =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 =python3 -m virtualenv /path/to/install/virtual/thingy=
-instead.
-
-
-* 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 that 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 -i
-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 -i
-import gpg
-
-k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
-keys = list(k)
-#+END_SRC
-
-And this is a really bad idea:
-
-#+BEGIN_SRC python -i
-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 -i
-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 -i
-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
-
-NOTE: The [[#cython][Cython]] introduction in the [[#advanced-use][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.
-
-
-** 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 -i
-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 -i
-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.
-
-
-** Importing keys
- :PROPERTIES:
- :CUSTOM_ID: howto-import-key
- :END:
-
-Importing keys is possible with the =key_import()= method and takes
-one argument which is a bytes literal object containing either the
-binary or ASCII armoured key data for one or more keys.
-
-The following example retrieves one or more keys from the SKS
-keyservers via the web using the requests module. Since requests
-returns the content as a bytes literal object, we can then use that
-directly to import the resulting data into our keybox.
-
-#+BEGIN_SRC python -i
-import gpg
-import os.path
-import requests
-
-c = gpg.Context()
-url = "https://sks-keyservers.net/pks/lookup"
-pattern = input("Enter the pattern to search for key or user IDs: ")
-payload = {"op": "get", "search": pattern}
-
-r = requests.get(url, verify=True, params=payload)
-result = c.key_import(r.content)
-
-if result is not None and hasattr(result, "considered") is False:
- print(result)
-elif result is not None and hasattr(result, "considered") is True:
- num_keys = len(result.imports)
- new_revs = result.new_revocations
- new_sigs = result.new_signatures
- new_subs = result.new_sub_keys
- new_uids = result.new_user_ids
- new_scrt = result.secret_imported
- nochange = result.unchanged
- print("""
- The total number of keys considered for import was: {0}
-
- Number of keys revoked: {1}
- Number of new signatures: {2}
- Number of new subkeys: {3}
- Number of new user IDs: {4}
- Number of new secret keys: {5}
- Number of unchanged keys: {6}
-
- The key IDs for all considered keys were:
-""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
- nochange))
- for i in range(num_keys):
- print("{0}\n".format(result.imports[i].fpr))
-else:
- pass
-#+END_SRC
-
-NOTE: When searching for a key ID of any length or a fingerprint
-(without spaces), the SKS servers require the the leading =0x=
-indicative of hexadecimal be included. Also note that the old short
-key IDs (e.g. =0xDEADBEEF=) should no longer be used due to the
-relative ease by which such key IDs can be reproduced, as demonstrated
-by the Evil32 Project in 2014 (which was subsequently exploited in
-2016).
-
-
-*** Working with ProtonMail
- :PROPERTIES:
- :CUSTOM_ID: import-protonmail
- :END:
-
-Here is a variation on the example above which checks the constrained
-ProtonMail keyserver for ProtonMail public keys.
-
-#+BEGIN_SRC python -i
-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_SRC
-
-Both the above example, [[../examples/howto/pmkey-import.py][pmkey-import.py]], and a version which prompts
-for an alternative GnuPG home directory, [[../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.
-
-
-*** Importing with HKP for Python
- :PROPERTIES:
- :CUSTOM_ID: import-hkp4py
- :END:
-
-Performing the same tasks with the [[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_SRC python -i
-import gpg
-import hkp4py
-import sys
-
-c = gpg.Context()
-server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
-results = []
-
-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: ")
-
-try:
- keys = server.search(pattern)
- print("Found {0} key(s).".format(len(keys)))
-except Exception as e:
- keys = []
- for logrus in pattern.split():
- if logrus.startswith("0x") is True:
- key = server.search(logrus)
- else:
- key = server.search("0x{0}".format(logrus))
- keys.append(key[0])
- print("Found {0} key(s).".format(len(keys)))
-
-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_SRC
-
-Since the hkp4py module handles multiple keys just as effectively as
-one (=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.
-
-
-*** Importing from ProtonMail with HKP for Python
- :PROPERTIES:
- :CUSTOM_ID: import-protonmail-hkp4py
- :END:
-
-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.
-
-
-**** Import from ProtonMail via HKP for Python Example no. 1
- :PROPERTIES:
- :CUSTOM_ID: import-hkp4py-pm1
- :END:
-
-The following script is avalable with the rest of the examples under
-the somewhat less than original name, =pmkey-import-hkp.py=.
-
-#+BEGIN_SRC python -i
-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_SRC
-
-
-**** Import from ProtonMail via HKP for Python Example no. 2
- :PROPERTIES:
- :CUSTOM_ID: import-hkp4py-pm2
- :END:
-
-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 =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_SRC python -i
-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_SRC
-
-
-** Exporting keys
- :PROPERTIES:
- :CUSTOM_ID: howto-export-key
- :END:
-
-Exporting keys remains a reasonably simple task, but has been
-separated into three different functions for the OpenPGP cryptographic
-engine. Two of those functions are for exporting public keys and the
-third is for exporting secret keys.
-
-
-*** Exporting public keys
- :PROPERTIES:
- :CUSTOM_ID: howto-export-public-key
- :END:
-
-There are two methods of exporting public keys, both of which are very
-similar to the other. The default method, =key_export()=, will export
-a public key or keys matching a specified pattern as normal. The
-alternative, the =key_export_minimal()= method, will do the same thing
-except producing a minimised output with extra signatures and third
-party signatures or certifications removed.
-
-#+BEGIN_SRC python -i
-import gpg
-import os.path
-import sys
-
-print("""
-This script exports one or more public keys.
-""")
-
-c = gpg.Context(armor=True)
-
-if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
-elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
-elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-else:
- keyfile = input("Enter the path and filename to save the secret key to: ")
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
-if homedir.startswith("~"):
- if os.path.exists(os.path.expanduser(homedir)) is True:
- c.home_dir = os.path.expanduser(homedir)
- else:
- pass
-elif os.path.exists(homedir) is True:
- c.home_dir = homedir
-else:
- pass
-
-try:
- result = c.key_export(pattern=logrus)
-except:
- result = c.key_export(pattern=None)
-
-if result is not None:
- with open(keyfile, "wb") as f:
- f.write(result)
-else:
- pass
-#+END_SRC
-
-It should be noted that the result will only return =None= when a
-search pattern has been entered, but has not matched any keys. When
-the search pattern itself is set to =None= this triggers the exporting
-of the entire public keybox.
-
-#+BEGIN_SRC python -i
-import gpg
-import os.path
-import sys
-
-print("""
-This script exports one or more public keys in minimised form.
-""")
-
-c = gpg.Context(armor=True)
-
-if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
-elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
-elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-else:
- keyfile = input("Enter the path and filename to save the secret key to: ")
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
-if homedir.startswith("~"):
- if os.path.exists(os.path.expanduser(homedir)) is True:
- c.home_dir = os.path.expanduser(homedir)
- else:
- pass
-elif os.path.exists(homedir) is True:
- c.home_dir = homedir
-else:
- pass
-
-try:
- result = c.key_export_minimal(pattern=logrus)
-except:
- result = c.key_export_minimal(pattern=None)
-
-if result is not None:
- with open(keyfile, "wb") as f:
- f.write(result)
-else:
- pass
-#+END_SRC
-
-
-*** Exporting secret keys
- :PROPERTIES:
- :CUSTOM_ID: howto-export-secret-key
- :END:
-
-Exporting secret keys is, functionally, very similar to exporting
-public keys; save for the invocation of =pinentry= via =gpg-agent= in
-order to securely enter the key's passphrase and authorise the export.
-
-The following example exports the secret key to a file which is then
-set with the same permissions as the output files created by the
-command line secret key export options.
-
-#+BEGIN_SRC python -i
-import gpg
-import os
-import os.path
-import sys
-
-print("""
-This script exports one or more secret keys.
-
-The gpg-agent and pinentry are invoked to authorise the export.
-""")
-
-c = gpg.Context(armor=True)
-
-if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
-elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
-elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-else:
- keyfile = input("Enter the path and filename to save the secret key to: ")
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
-if 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_SRC
-
-Alternatively the approach of the following script can be used. This
-longer example saves the exported secret key(s) in files in the GnuPG
-home directory, in addition to setting the file permissions as only
-readable and writable by the user. It also exports the secret key(s)
-twice in order to output both GPG binary (=.gpg=) and ASCII armoured
-(=.asc=) files.
-
-#+BEGIN_SRC python -i
-import gpg
-import os
-import os.path
-import subprocess
-import sys
-
-print("""
-This script exports one or more secret keys as both ASCII armored and binary
-file formats, saved in files within the user's GPG home directory.
-
-The gpg-agent and pinentry are invoked to authorise the export.
-""")
-
-if sys.platform == "win32":
- gpgconfcmd = "gpgconf.exe --list-dirs homedir"
-else:
- gpgconfcmd = "gpgconf --list-dirs homedir"
-
-a = gpg.Context(armor=True)
-b = gpg.Context()
-c = gpg.Context()
-
-if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
-elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
-elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-else:
- keyfile = input("Enter the filename to save the secret key to: ")
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
-if 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_SRC
-
-
-*** Sending public keys to the SKS Keyservers
- :PROPERTIES:
- :CUSTOM_ID: howto-send-public-key
- :END:
-
-As with the previous section on importing keys, the =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_SRC python -i
-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_SRC
-
-An expanded version of this script with additional functions for
-specifying an alternative homedir location is in the examples
-directory as =send-key-to-keyserver.py=.
-
-The =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.
-
-
-* 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 -i
-import gpg
-
-a_key = "0x12345678DEADBEEF"
-text = b"""Some text to test with.
-
-Since the text in this case must be bytes, it is most likely that
-the input form will be a separate file which is opened with "rb"
-as this is the simplest method of obtaining the correct data format.
-"""
-
-c = gpg.Context(armor=True)
-rkey = list(c.keylist(pattern=a_key, secret=False))
-ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
-
-with open("secret_plans.txt.asc", "wb") as afile:
- afile.write(ciphertext)
-#+END_SRC
-
-Though this is even more likely to be used like this; with the
-plaintext input read from a file, the recipient keys used for
-encryption regardless of key trust status and the encrypted output
-also encrypted to any preconfigured keys set in the =gpg.conf= file:
-
-#+BEGIN_SRC python -i
-import gpg
-
-a_key = "0x12345678DEADBEEF"
-
-with open("secret_plans.txt", "rb") as afile:
- text = afile.read()
-
-c = gpg.Context(armor=True)
-rkey = list(c.keylist(pattern=a_key, secret=False))
-ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True,
- always_trust=True,
- add_encrypt_to=True)
-
-with open("secret_plans.txt.asc", "wb") as afile:
- afile.write(ciphertext)
-#+END_SRC
-
-If the =recipients= paramater is empty then the plaintext is encrypted
-symmetrically. If no =passphrase= is supplied as a parameter or via a
-callback registered with the =Context()= then an out-of-band prompt
-for the passphrase via pinentry will be invoked.
-
-
-*** Encrypting to multiple keys
- :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:4] but does /not/ encrypt
-to a default key or other key which is configured to normally encrypt
-to.
-
-#+BEGIN_SRC python -i
-import gpg
-
-text = b"""Oh look, another test message.
-
-The same rules apply as with the previous example and more likely
-than not, the message will actually be drawn from reading the
-contents of a file or, maybe, from entering data at an input()
-prompt.
-
-Since the text in this case must be bytes, it is most likely that
-the input form will be a separate file which is opened with "rb"
-as this is the simplest method of obtaining the correct data
-format.
-"""
-
-c = gpg.Context(armor=True)
-rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
-logrus = []
-
-for i in range(len(rpattern)):
- if rpattern[i].can_encrypt == 1:
- logrus.append(rpattern[i])
-
-ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
- sign=False, always_trust=True)
-
-with open("secret_plans.txt.asc", "wb") as afile:
- afile.write(ciphertext)
-#+END_SRC
-
-All it would take to change the above example to sign the message
-and also encrypt the message to any configured default keys would
-be to change the =c.encrypt= line to this:
-
-#+BEGIN_SRC python -i
-ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
- always_trust=True,
- add_encrypt_to=True)
-#+END_SRC
-
-The only keyword arguments requiring modification are those for which
-the default values are changing. The default value of =sign= is
-=True=, the default of =always_trust= is =False=, the default of
-=add_encrypt_to= is =False=.
-
-If =always_trust= is not set to =True= and any of the recipient keys
-are not trusted (e.g. not signed or locally signed) then the
-encryption will raise an error. It is possible to mitigate this
-somewhat with something more like this:
-
-#+BEGIN_SRC python -i
-import gpg
-
-with open("secret_plans.txt.asc", "rb") as afile:
- text = afile.read()
-
-c = gpg.Context(armor=True)
-rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
-logrus = []
-
-for i in range(len(rpattern)):
- if rpattern[i].can_encrypt == 1:
- logrus.append(rpattern[i])
-
- try:
- ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
- add_encrypt_to=True)
- except gpg.errors.InvalidRecipients as e:
- for i in range(len(e.recipients)):
- for n in range(len(logrus)):
- if logrus[n].fpr == e.recipients[i].fpr:
- logrus.remove(logrus[n])
- else:
- pass
- try:
- ciphertext, result, sign_result = c.encrypt(text,
- recipients=logrus,
- add_encrypt_to=True)
- with open("secret_plans.txt.asc", "wb") as afile:
- afile.write(ciphertext)
- except:
- pass
-#+END_SRC
-
-This will attempt to encrypt to all the keys searched for, then remove
-invalid recipients if it fails and try again.
-
-
-** Decryption
- :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 -i
-import gpg
-
-ciphertext = input("Enter path and filename of encrypted file: ")
-newfile = input("Enter path and filename of file to save decrypted data to: ")
-
-with open(ciphertext, "rb") as cfile:
- try:
- plaintext, result, verify_result = gpg.Context().decrypt(cfile)
- except gpg.errors.GPGMEError as e:
- plaintext = None
- print(e)
-
-if plaintext is not None:
- with open(newfile, "wb") as nfile:
- nfile.write(plaintext)
- else:
- pass
-#+END_SRC
-
-The data available in =plaintext= in this example is the decrypted
-content as a byte object, the recipient key IDs and algorithms in
-=result= and the results of verifying any signatures of the data in
-=verify_result=.
-
-
-** Signing text and files
- :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 -i
-import gpg
-
-logrus = input("Enter the email address or string to match signing keys to: ")
-hancock = gpg.Context().keylist(pattern=logrus, secret=True)
-sig_src = list(hancock)
-#+END_SRC
-
-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 -i
-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 -i
-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 -i
-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 -i
-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 -i
-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 -i
-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 -i
-import gpg
-import time
-
-filename = "statement.txt"
-gpg_file = "statement.txt.gpg"
-
-c = gpg.Context()
-
-try:
- data, result = c.verify(open(gpg_file))
- verified = True
-except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
-if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
-{0}
-with key {1}
-made at {2}
-""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
-else:
- pass
-#+END_SRC
-
-Whereas this next example, which is almost identical would work with
-normal ASCII armoured files and with clear-signed files:
-
-#+BEGIN_SRC python -i
-import gpg
-import time
-
-filename = "statement.txt"
-asc_file = "statement.txt.asc"
-
-c = gpg.Context()
-
-try:
- data, result = c.verify(open(asc_file))
- verified = True
-except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
-if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
-{0}
-with key {1}
-made at {2}
-""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
-else:
- pass
-#+END_SRC
-
-In both of the previous examples it is also possible to compare the
-original data that was signed against the signed data in =data= to see
-if it matches with something like this:
-
-#+BEGIN_SRC python -i
-with open(filename, "rb") as afile:
- text = afile.read()
-
-if text == data:
- print("Good signature.")
-else:
- pass
-#+END_SRC
-
-The following two examples, however, deal with detached signatures.
-With his method of verification the data that was signed does not get
-returned since it is already being explicitly referenced in the first
-argument of =c.verify=. So =data= is =None= and only the information
-in =result= is available.
-
-#+BEGIN_SRC python -i
-import gpg
-import time
-
-filename = "statement.txt"
-sig_file = "statement.txt.sig"
-
-c = gpg.Context()
-
-try:
- data, result = c.verify(open(filename), open(sig_file))
- verified = True
-except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
-if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
-{0}
-with key {1}
-made at {2}
-""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
-else:
- pass
-#+END_SRC
-
-#+BEGIN_SRC python -i
-import gpg
-import time
-
-filename = "statement.txt"
-asc_file = "statement.txt.asc"
-
-c = gpg.Context()
-
-try:
- data, result = c.verify(open(filename), open(asc_file))
- verified = True
-except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
-if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
-{0}
-with key {1}
-made at {2}
-""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
-else:
- pass
-#+END_SRC
-
-
-* Creating keys and subkeys
- :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 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 -i
-import gpg
-
-c = gpg.Context()
-
-c.home_dir = "~/.gnupg-dm"
-userid = "Danger Mouse <[email protected]>"
-
-dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000,
- sign=True, certify=True)
-#+END_SRC
-
-One thing to note here is the use of setting the =c.home_dir=
-parameter. This enables generating the key or keys in a different
-location. In this case to keep the new key data created for this
-example in a separate location rather than adding it to existing and
-active key store data. As with the default directory, =~/.gnupg=, any
-temporary or separate directory needs the permissions set to only
-permit access by the directory owner. On posix systems this means
-setting the directory permissions to 700.
-
-The =temp-homedir-config.py= script in the HOWTO examples directory
-will create an alternative homedir with these configuration options
-already set and the correct directory and file permissions.
-
-The successful generation of the key can be confirmed via the returned
-=GenkeyResult= object, which includes the following data:
-
-#+BEGIN_SRC python -i
-print("""
- Fingerprint: {0}
- Primary Key: {1}
- Public Key: {2}
- Secret Key: {3}
- Sub Key: {4}
-User IDs: {5}
-""".format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
- dmkey.uid))
-#+END_SRC
-
-Alternatively the information can be confirmed using the command line
-program:
-
-#+BEGIN_SRC shell
- bash-4.4$ gpg --homedir ~/.gnupg-dm -K
- ~/.gnupg-dm/pubring.kbx
- ----------------------
- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
- 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
- uid [ultimate] Danger Mouse <[email protected]>
-
- bash-4.4$
-#+END_SRC
-
-As with generating keys manually, to preconfigure expanded preferences
-for the cipher, digest and compression algorithms, the =gpg.conf= file
-must contain those details in the home directory in which the new key
-is being generated. I used a cut down version of my own =gpg.conf=
-file in order to be able to generate this:
-
-#+BEGIN_SRC shell
- bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
- Secret key is available.
-
- sec rsa3072/026D2F19E99E63AA
- created: 2018-03-15 expires: 2019-03-15 usage: SC
- trust: ultimate validity: ultimate
- [ultimate] (1). Danger Mouse <[email protected]>
-
- [ultimate] (1). Danger Mouse <[email protected]>
- Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
- Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
- Compression: ZLIB, BZIP2, ZIP, Uncompressed
- Features: MDC, Keyserver no-modify
-
- bash-4.4$
-#+END_SRC
-
-
-** Subkeys
- :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 -i
-import gpg
-
-c = gpg.Context()
-c.home_dir = "~/.gnupg-dm"
-
-key = c.get_key(dmkey.fpr, secret=True)
-dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000,
- encrypt=True)
-#+END_SRC
-
-As with the primary key, the results here can be checked with:
-
-#+BEGIN_SRC python -i
-print("""
- Fingerprint: {0}
- Primary Key: {1}
- Public Key: {2}
- Secret Key: {3}
- Sub Key: {4}
-User IDs: {5}
-""".format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
- dmsub.uid))
-#+END_SRC
-
-As well as on the command line with:
-
-#+BEGIN_SRC shell
- bash-4.4$ gpg --homedir ~/.gnupg-dm -K
- ~/.gnupg-dm/pubring.kbx
- ----------------------
- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
- 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
- uid [ultimate] Danger Mouse <[email protected]>
- ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
-
- bash-4.4$
-#+END_SRC
-
-
-** User IDs
- :PROPERTIES:
- :CUSTOM_ID: keygen-uids
- :END:
-
-
-*** Adding User IDs
- :PROPERTIES:
- :CUSTOM_ID: keygen-uids-add
- :END:
-
-By comparison to creating primary keys and subkeys, adding a new user
-ID to an existing key is much simpler. The method used to do this is
-=key_add_uid= and the only arguments it takes are for the =key= and
-the new =uid=.
-
-#+BEGIN_SRC python -i
-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
-
-
-*** Revokinging User IDs
- :PROPERTIES:
- :CUSTOM_ID: keygen-uids-revoke
- :END:
-
-Revoking a user ID is a fairly similar process, except that it uses
-the =key_revoke_uid= method.
-
-#+BEGIN_SRC python -i
-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_revoke_uid(key, uid)
-#+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 value of
-both =expires_in= and =local= is =False=; which results in the
-signature never expiring and being able to be exported.
-
-The =key= is the key being signed rather than the key doing the
-signing. To change the key doing the signing refer to the signing key
-selection above for signing messages and files.
-
-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 -i
-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
-
-
-* Advanced or Experimental Use Cases
- :PROPERTIES:
- :CUSTOM_ID: advanced-use
- :END:
-
-
-** C plus Python plus SWIG plus Cython
- :PROPERTIES:
- :CUSTOM_ID: cython
- :END:
-
-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 [[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 [[#howto-keys-counting][key counting]] code. Running that
-example as an executable Python script, =keycount.py= (available in
-the =examples/howto/= directory), will take a noticable 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
-=time= utility, with the following results:
-
-#+BEGIN_SRC shell
- 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_SRC
-
-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 [[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 =gpg= module to the end of the script:
-
-#+BEGIN_SRC python -i
-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
-
-Save that into a file called =keycount.pyx= and then create a
-=setup.py= file which contains this:
-
-#+BEGIN_SRC python -i
-from distutils.core import setup
-from Cython.Build import cythonize
-
-setup(
- ext_modules = cythonize("keycount.pyx")
-)
-#+END_SRC
-
-Compile it:
-
-#+BEGIN_SRC shell
- bash-4.4$ python setup.py build_ext --inplace
- bash-4.4$
-#+END_SRC
-
-Then run it in a similar manner to =keycount.py=:
-
-#+BEGIN_SRC shell
- 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_SRC
-
-Cython turned =keycount.pyx= into an 81KB =keycount.o= file in the
-=build/= directory, a 24KB =keycount.cpython-37m-darwin.so= file to be
-imported into Python 3.7 and a 113KB =keycount.c= generated C source
-code file of nearly three thousand lines. Quite a bit bigger than the
-314 bytes of the =keycount.pyx= file or the full 1,452 bytes of the
-full executable =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 =keycount.pyx= and =setup.py= files used to generate this example
-have been added to the =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
-[[http://docs.cython.org/en/latest/src/tutorial/pure.html#magic-attributes-within-the-pxd][Magic Attributes]] section of the Cython documentation.
-
-
-* Miscellaneous extras and work-arounds
- :PROPERTIES:
- :CUSTOM_ID: cheats-and-hacks
- :END:
-
-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 [[#group-lines][group lines]].
-
-
-** 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 -i
-import subprocess
-import sys
-
-if sys.platform == "win32":
- gpgconfcmd = "gpgconf.exe --list-options gpg"
-else:
- gpgconfcmd = "gpgconf --list-options gpg"
-
-try:
- lines = subprocess.getoutput(gpgconfcmd).splitlines()
-except:
- 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 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 = []
-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_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 list.
-
-A demonstration of using the =groups.py= module is also available in
-the form of the executable =mutt-groups.py= script. This second
-script reads all the group entries in a user's =gpg.conf= file and
-converts them into crypt-hooks suitable for use with the Mutt and
-Neomutt mail clients.
-
-
-** Keyserver access for Python
- :PROPERTIES:
- :CUSTOM_ID: hkp4py
- :END:
-
-The [[https://github.com/Selfnet/hkp4py][hkp4py]] module by Marcel Fest was originally a port of the old
-[[https://github.com/dgladkov/python-hkp][python-hkp]] module from Python 2 to Python 3 and updated to use the
-[[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 =hkp= protocol prefix as =http= and =hkps= as
-=https=, the module is able to be used even with servers which do not
-support the full scope of keyserver functions.[fn:5] It also works quite
-readily when incorporated into a [[#cython][Cython]] generated and compiled version
-of any code.
-
-
-*** Key import format
- :PROPERTIES:
- :CUSTOM_ID: hkp4py-strings
- :END:
-
-The hkp4py module returns key data via requests as string literals
-(=r.text=) instead of byte literals (=r.content=). This means that
-the retrurned key data must be encoded to UTF-8 when importing that
-key material using a =gpg.Context().key_import()= method.
-
-For this reason an alternative method has been added to the =search=
-function of =hkp4py.KeyServer()= which returns the key in the correct
-format as expected by =key_import=. When importing using this module,
-it is now possible to import with this:
-
-#+BEGIN_SRC python -i
-for key in keys:
- if key.revoked is False:
- gpg.Context().key_import(key.key_blob)
- else:
- pass
-#+END_SRC
-
-Without that recent addition it would have been necessary to encode
-the contents of each =hkp4py.KeyServer().search()[i].key= in
-=hkp4py.KeyServer().search()= before trying to import it.
-
-An example of this is included in the [[#howto-import-key][Importing Keys]] section of this
-HOWTO and the corresponding executable version of that example is
-available in the =lang/python/examples/howto= directory as normal; the
-executable version is the =import-keys-hkp.py= file.
-
-
-* Copyright and Licensing
- :PROPERTIES:
- :CUSTOM_ID: copyright-and-license
- :END:
-
-
-** Copyright
- :PROPERTIES:
- :CUSTOM_ID: copyright
- :END:
-
-Copyright © The GnuPG Project, 2018.
-
-Copyright (C) The GnuPG Project, 2018.
-
-
-** Draft Editions of this HOWTO
- :PROPERTIES:
- :CUSTOM_ID: draft-editions
- :END:
-
-Draft editions of this HOWTO may be periodically available directly
-from the author at any of the following URLs:
-
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no SSL)]]
-
-All of these draft versions except for one have been generated from
-this document via Emacs [[https://orgmode.org/][Org mode]] and [[https://www.gnu.org/software/texinfo/][GNU Texinfo]]. Though it is likely
-that the specific [[https://files.au.adversary.org/crypto/gpgme-python-howto.org][file]] [[http://files.au.adversary.org/crypto/gpgme-python-howto.org][version]] used will be on the same server with
-the generated output formats.
-
-The one exception is the reStructuredText version, which was converted
-using the latest version of Pandoc from the Org mode source file using
-the following command:
-
-#+BEGIN_SRC shell
- pandoc -f org -t rst -o gpgme-python-howto.rst gpgme-python-howto.org
-#+END_SRC
-
-In addition to these there is a significantly less frequently updated
-version as a HTML [[https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html][WebHelp site]] (AWS S3 SSL); generated from DITA XML
-source files, which can be found in [[https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/][an alternative branch]] of the GPGME
-git repository.
-
-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.
-
-
-** 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] 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.
-
-[fn:3] 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
-/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).
-
-[fn:4] 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.
-
-[fn:5] 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 =hkp4py= module will rewrite
-these appropriately when the connection is made to the server.