From f0063afa71bc7e71f19d174acc2fde26f0c11850 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 15 May 2018 13:13:16 +1000 Subject: [PATCH] docs: python bindings HOWTO - DITA XML version * Due to the org-babel bug which breaks Python source code examples beyond the most simple snippets, ported the HOWTO to a source format which I *know* for sure won't break it. * Details of the org-mode bug is in https://dev.gnupg.org/T3977 * DITA project uses DITA-OT 2.x (2.4 or 2.5, IIRC) with support for DITA 1.3. * source files were written with oXygenXML Editor 20.0, hence the oXygenXML project file in the directory; however only the .ditamap and .dita files are required to generate any output with the DITA-OT. Signed-off-by: Ben McGinnes --- .../docs/dita/gpgme-python-howto.ditamap | 68 +++ lang/python/docs/dita/gpgme-python.ditamap | 6 + lang/python/docs/dita/gpgmePython.xpr | 453 ++++++++++++++++++ lang/python/docs/dita/howto/part-1.dita | 10 + lang/python/docs/dita/howto/part-2.dita | 10 + lang/python/docs/dita/howto/part-3.dita | 10 + lang/python/docs/dita/howto/part-4.dita | 10 + lang/python/docs/dita/howto/part-5.dita | 10 + lang/python/docs/dita/howto/part-6.dita | 10 + .../docs/dita/howto/part01/docs-source.dita | 27 ++ .../docs/dita/howto/part01/examples.dita | 11 + .../docs/dita/howto/part01/introduction.dita | 11 + .../dita/howto/part01/python2-vs-python3.dita | 22 + lang/python/docs/dita/howto/part02/c-api.dita | 17 + .../docs/dita/howto/part02/context.dita | 21 + lang/python/docs/dita/howto/part02/daesh.dita | 20 + .../howto/part02/differences-to-others.dita | 11 + .../docs/dita/howto/part02/fundamentals.dita | 12 + .../dita/howto/part02/gpgme-concepts.dita | 10 + .../docs/dita/howto/part02/installation.dita | 10 + .../docs/dita/howto/part02/installing.dita | 23 + .../docs/dita/howto/part02/no-pypi.dita | 19 + .../docs/dita/howto/part02/no-rest.dita | 20 + lang/python/docs/dita/howto/part02/pyme.dita | 25 + .../dita/howto/part02/python-bindings.dita | 16 + .../docs/dita/howto/part02/python-gnupg.dita | 22 + .../docs/dita/howto/part02/requirements.dita | 20 + .../docs/dita/howto/part03/get-key.dita | 37 ++ .../docs/dita/howto/part03/key-counting.dita | 31 ++ .../docs/dita/howto/part03/key-selection.dita | 53 ++ .../dita/howto/part04/basic-functions.dita | 12 + .../docs/dita/howto/part04/clear-signing.dita | 42 ++ .../docs/dita/howto/part04/decryption.dita | 31 ++ .../dita/howto/part04/default-signing.dita | 51 ++ .../dita/howto/part04/detached-signing.dita | 42 ++ .../dita/howto/part04/encrypt-to-many.dita | 100 ++++ .../dita/howto/part04/encrypt-to-one.dita | 83 ++++ .../docs/dita/howto/part04/encryption.dita | 12 + .../howto/part04/signing-key-selection.dita | 28 ++ .../docs/dita/howto/part04/signing.dita | 11 + .../docs/dita/howto/part04/verification.dita | 150 ++++++ .../docs/dita/howto/part05/add-uid.dita | 39 ++ .../docs/dita/howto/part05/certification.dita | 36 ++ .../docs/dita/howto/part05/key-creation.dita | 34 ++ .../docs/dita/howto/part05/primary-key.dita | 97 ++++ .../docs/dita/howto/part05/rev-uid.dita | 24 + .../docs/dita/howto/part05/subkeys.dita | 53 ++ .../docs/dita/howto/part05/user-ids.dita | 10 + .../docs/dita/howto/part06/group-lines.dita | 47 ++ 49 files changed, 1927 insertions(+) create mode 100644 lang/python/docs/dita/gpgme-python-howto.ditamap create mode 100644 lang/python/docs/dita/gpgme-python.ditamap create mode 100644 lang/python/docs/dita/gpgmePython.xpr create mode 100644 lang/python/docs/dita/howto/part-1.dita create mode 100644 lang/python/docs/dita/howto/part-2.dita create mode 100644 lang/python/docs/dita/howto/part-3.dita create mode 100644 lang/python/docs/dita/howto/part-4.dita create mode 100644 lang/python/docs/dita/howto/part-5.dita create mode 100644 lang/python/docs/dita/howto/part-6.dita create mode 100644 lang/python/docs/dita/howto/part01/docs-source.dita create mode 100644 lang/python/docs/dita/howto/part01/examples.dita create mode 100644 lang/python/docs/dita/howto/part01/introduction.dita create mode 100644 lang/python/docs/dita/howto/part01/python2-vs-python3.dita create mode 100644 lang/python/docs/dita/howto/part02/c-api.dita create mode 100644 lang/python/docs/dita/howto/part02/context.dita create mode 100644 lang/python/docs/dita/howto/part02/daesh.dita create mode 100644 lang/python/docs/dita/howto/part02/differences-to-others.dita create mode 100644 lang/python/docs/dita/howto/part02/fundamentals.dita create mode 100644 lang/python/docs/dita/howto/part02/gpgme-concepts.dita create mode 100644 lang/python/docs/dita/howto/part02/installation.dita create mode 100644 lang/python/docs/dita/howto/part02/installing.dita create mode 100644 lang/python/docs/dita/howto/part02/no-pypi.dita create mode 100644 lang/python/docs/dita/howto/part02/no-rest.dita create mode 100644 lang/python/docs/dita/howto/part02/pyme.dita create mode 100644 lang/python/docs/dita/howto/part02/python-bindings.dita create mode 100644 lang/python/docs/dita/howto/part02/python-gnupg.dita create mode 100644 lang/python/docs/dita/howto/part02/requirements.dita create mode 100644 lang/python/docs/dita/howto/part03/get-key.dita create mode 100644 lang/python/docs/dita/howto/part03/key-counting.dita create mode 100644 lang/python/docs/dita/howto/part03/key-selection.dita create mode 100644 lang/python/docs/dita/howto/part04/basic-functions.dita create mode 100644 lang/python/docs/dita/howto/part04/clear-signing.dita create mode 100644 lang/python/docs/dita/howto/part04/decryption.dita create mode 100644 lang/python/docs/dita/howto/part04/default-signing.dita create mode 100644 lang/python/docs/dita/howto/part04/detached-signing.dita create mode 100644 lang/python/docs/dita/howto/part04/encrypt-to-many.dita create mode 100644 lang/python/docs/dita/howto/part04/encrypt-to-one.dita create mode 100644 lang/python/docs/dita/howto/part04/encryption.dita create mode 100644 lang/python/docs/dita/howto/part04/signing-key-selection.dita create mode 100644 lang/python/docs/dita/howto/part04/signing.dita create mode 100644 lang/python/docs/dita/howto/part04/verification.dita create mode 100644 lang/python/docs/dita/howto/part05/add-uid.dita create mode 100644 lang/python/docs/dita/howto/part05/certification.dita create mode 100644 lang/python/docs/dita/howto/part05/key-creation.dita create mode 100644 lang/python/docs/dita/howto/part05/primary-key.dita create mode 100644 lang/python/docs/dita/howto/part05/rev-uid.dita create mode 100644 lang/python/docs/dita/howto/part05/subkeys.dita create mode 100644 lang/python/docs/dita/howto/part05/user-ids.dita create mode 100644 lang/python/docs/dita/howto/part06/group-lines.dita diff --git a/lang/python/docs/dita/gpgme-python-howto.ditamap b/lang/python/docs/dita/gpgme-python-howto.ditamap new file mode 100644 index 00000000..99dde6ce --- /dev/null +++ b/lang/python/docs/dita/gpgme-python-howto.ditamap @@ -0,0 +1,68 @@ + + + + + GPGME Python Bindings HOWTO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lang/python/docs/dita/gpgme-python.ditamap b/lang/python/docs/dita/gpgme-python.ditamap new file mode 100644 index 00000000..5232dca8 --- /dev/null +++ b/lang/python/docs/dita/gpgme-python.ditamap @@ -0,0 +1,6 @@ + + + + GPGME Python Bindings + + diff --git a/lang/python/docs/dita/gpgmePython.xpr b/lang/python/docs/dita/gpgmePython.xpr new file mode 100644 index 00000000..e8756fca --- /dev/null +++ b/lang/python/docs/dita/gpgmePython.xpr @@ -0,0 +1,453 @@ + + + + + + + + + scenario.associations + + + + gpgme-python-howto.ditamap + + + + gpgme-python-howto (WebHelp Responsive) + + + + + DITAMAP + + + + + + + scenarios + + + + false + + + false + + + ${cfd} + + + ${cfd}/out/html5 + + + ${cfd}/temp/html5 + + + html5 + + + + + false + + + true + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + -Xmx384m + + + false + + + + + + false + + + + + + + + + true + + + + + + gpgme-python-howto (HTML5) + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + DITAMAP + + + true + + + true + + + + + + + + + false + + + + + + false + + + false + + + false + + + false + + + false + + + false + + + + + + + + + DITA-OT + + + + + + + + false + + + false + + + ${cfd} + + + ${cfd}/out/webhelp-responsive-${ddt} + + + ${cfd}/temp/webhelp-responsive-${ddt} + + + webhelp-responsive + + + + + false + + + true + + + + + + + + + + + + + + true + + + + + + + + + + + + + + ${configured.ditaot.dir}/plugins/com.oxygenxml.webhelp.responsive/templates/oxygen + + + oxygen-tiles.opt + + + false + + + + + + + + + + + + + + + + + force-unique + + + + + + true + + + false + + + 4 + + + + true + false + + + + + + + + + + + + + args.xhtml.classattr + + + + + + yes + + + yes + + + 4 + + + + yes + no + + + + + + + + + + + + + + -Xmx384m + + + false + + + + + + false + + + + + + + + + true + + + + + + gpgme-python-howto (WebHelp Responsive) + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + DITAMAP + + + true + + + true + + + + + + + + + false + + + + + + false + + + false + + + false + + + false + + + false + + + false + + + + + + + + + DITA-OT + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lang/python/docs/dita/howto/part-1.dita b/lang/python/docs/dita/howto/part-1.dita new file mode 100644 index 00000000..12488f8c --- /dev/null +++ b/lang/python/docs/dita/howto/part-1.dita @@ -0,0 +1,10 @@ + + + + + Introducing the Python Bindings + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part-2.dita b/lang/python/docs/dita/howto/part-2.dita new file mode 100644 index 00000000..6ddd174a --- /dev/null +++ b/lang/python/docs/dita/howto/part-2.dita @@ -0,0 +1,10 @@ + + + + + Preparation and Setting Up + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part-3.dita b/lang/python/docs/dita/howto/part-3.dita new file mode 100644 index 00000000..37cd3ba9 --- /dev/null +++ b/lang/python/docs/dita/howto/part-3.dita @@ -0,0 +1,10 @@ + + + + + Working With Keys + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part-4.dita b/lang/python/docs/dita/howto/part-4.dita new file mode 100644 index 00000000..b79a04e8 --- /dev/null +++ b/lang/python/docs/dita/howto/part-4.dita @@ -0,0 +1,10 @@ + + + + + The Fun Stuff + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part-5.dita b/lang/python/docs/dita/howto/part-5.dita new file mode 100644 index 00000000..77849a01 --- /dev/null +++ b/lang/python/docs/dita/howto/part-5.dita @@ -0,0 +1,10 @@ + + + + + Generating Keys + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part-6.dita b/lang/python/docs/dita/howto/part-6.dita new file mode 100644 index 00000000..01b94270 --- /dev/null +++ b/lang/python/docs/dita/howto/part-6.dita @@ -0,0 +1,10 @@ + + + + + Miscellaneous Work-arounds + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part01/docs-source.dita b/lang/python/docs/dita/howto/part01/docs-source.dita new file mode 100644 index 00000000..f0a8affa --- /dev/null +++ b/lang/python/docs/dita/howto/part01/docs-source.dita @@ -0,0 +1,27 @@ + + + + + Documentation Source Files + +

Unlike all other documentation in the GnuPG Project, including the initial version of + this HOWTO, this version was not written in Emacs Org-Mode. Nor was it written in + LaTeX, Texinfo or even directly in HTML. Instead it was written using the Darwin Information + Typing Architecture (DITA) XML.

+

This was done for two main reasons:

+

+

    +
  1. A bug in either Org-Mode or Babel prevented the more complex examples included in the + HOWTO from displaying correctly.
  2. +
  3. To demonstrate some of the advantages of DITA XML over existing documentation + productionsoftware used in the project (particularly Texinfo and LaTeX).
  4. +
+

+

The XML format definitely supports displaying all the more complex Python code correctly, + as well as being designed to produce standards compliant print and HTML output. Whereas + currently the existing tools utilised by the GnuPG Project can't display the example code in + a way which would actually pass the project's own git commit ruleset.

+

+ +
+
diff --git a/lang/python/docs/dita/howto/part01/examples.dita b/lang/python/docs/dita/howto/part01/examples.dita new file mode 100644 index 00000000..afa66197 --- /dev/null +++ b/lang/python/docs/dita/howto/part01/examples.dita @@ -0,0 +1,11 @@ + + + + + Examples + +

All of the examples found in this document can be found as Python 3 scripts in the + lang/python/examples/howto directory.

+ +
+
diff --git a/lang/python/docs/dita/howto/part01/introduction.dita b/lang/python/docs/dita/howto/part01/introduction.dita new file mode 100644 index 00000000..7b18eb89 --- /dev/null +++ b/lang/python/docs/dita/howto/part01/introduction.dita @@ -0,0 +1,11 @@ + + + + + Introduction + +

This document provides basic instruction in how to use the GPGME Python bindings to + programmatically leverage the GPGME library.

+ +
+
diff --git a/lang/python/docs/dita/howto/part01/python2-vs-python3.dita b/lang/python/docs/dita/howto/part01/python2-vs-python3.dita new file mode 100644 index 00000000..97662764 --- /dev/null +++ b/lang/python/docs/dita/howto/part01/python2-vs-python3.dita @@ -0,0 +1,22 @@ + + + + + Python 2 vs. Python 3 + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/c-api.dita b/lang/python/docs/dita/howto/part02/c-api.dita new file mode 100644 index 00000000..67736ab2 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/c-api.dita @@ -0,0 +1,17 @@ + + + + + A C API + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/context.dita b/lang/python/docs/dita/howto/part02/context.dita new file mode 100644 index 00000000..4b137af8 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/context.dita @@ -0,0 +1,21 @@ + + + + + Context + +

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.

+

+ + + diff --git a/lang/python/docs/dita/howto/part02/daesh.dita b/lang/python/docs/dita/howto/part02/daesh.dita new file mode 100644 index 00000000..0d02f45e --- /dev/null +++ b/lang/python/docs/dita/howto/part02/daesh.dita @@ -0,0 +1,20 @@ + + + + + The gnupg package created and maintained by Isis Lovecruft + +

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).

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/differences-to-others.dita b/lang/python/docs/dita/howto/part02/differences-to-others.dita new file mode 100644 index 00000000..dd3521f2 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/differences-to-others.dita @@ -0,0 +1,11 @@ + + + + + Difference between the Python bindings and other GnuPG Python packages + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/fundamentals.dita b/lang/python/docs/dita/howto/part02/fundamentals.dita new file mode 100644 index 00000000..a81faf50 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/fundamentals.dita @@ -0,0 +1,12 @@ + + + + + Fundamentals + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/gpgme-concepts.dita b/lang/python/docs/dita/howto/part02/gpgme-concepts.dita new file mode 100644 index 00000000..436111bf --- /dev/null +++ b/lang/python/docs/dita/howto/part02/gpgme-concepts.dita @@ -0,0 +1,10 @@ + + + + + GPGME Concepts + +

+ + + diff --git a/lang/python/docs/dita/howto/part02/installation.dita b/lang/python/docs/dita/howto/part02/installation.dita new file mode 100644 index 00000000..4e35dc99 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/installation.dita @@ -0,0 +1,10 @@ + + + + + GPGME Python bindings installation + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/installing.dita b/lang/python/docs/dita/howto/part02/installing.dita new file mode 100644 index 00000000..91a0cf49 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/installing.dita @@ -0,0 +1,23 @@ + + + + + Installing + +

Installing the Python bindings is effectively achieved by compiling and installing GPGME + itself.

+

Once SWIG is installed with Python and all the dependencies for GPGME are installed you + only need to confirm that the version(s) of Python you want the bindings installed for are + in your $PATH.

+

By default GPGME will attempt to install the bindings for the most recent or highest + version number of Python 2 and Python 3 it detects in $PATH. It + specifically checks for the python and python3 + executables first and then checks for specific version numbers.

+

For Python 2 it checks for these executables in this order: python, + python2 and python2.7.

+

For Python 3 it checks for these executables in this order: python3, + python3.6, python3.5 and + python3.4.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/no-pypi.dita b/lang/python/docs/dita/howto/part02/no-pypi.dita new file mode 100644 index 00000000..8f2b6013 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/no-pypi.dita @@ -0,0 +1,19 @@ + + + + + No PyPI + +

Most third-party Python packages and modules are available and distributed through + the Python Package Installer, known as PyPI.

+

Due to the nature of what these bindings are and how they work, it is infeasible to install + the GPGME Python bindings in the same way.

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/no-rest.dita b/lang/python/docs/dita/howto/part02/no-rest.dita new file mode 100644 index 00000000..82cf8e80 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/no-rest.dita @@ -0,0 +1,20 @@ + + + + + No REST + +

The first part of which is or will be fairly blatantly obvious upon viewing the first + example, but it's worth reiterating anyway. That being that this API is not a + REST API. Nor indeed could it ever be one.

+

Most, if not all, Python programmers (and not just Python programmers) know how easy it is + to work with a RESTful API. In fact they've become so popular that many other APIs attempt + to emulate REST-like behaviour as much as they are able. Right down to the use of JSON + formatted output to facilitate the use of their API without having to retrain + developers.

+

This API does not do that. It would not be able to do that and also provide access to the + entire C API on which it's built. It does, however, provide a very pythonic interface on top + of the direct bindings and it's this pythonic layer with which this HOWTO deals with.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/pyme.dita b/lang/python/docs/dita/howto/part02/pyme.dita new file mode 100644 index 00000000..3ced2dc5 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/pyme.dita @@ -0,0 +1,25 @@ + + + + + The PyME package maintained by Martin Albrecht + +

This package is the origin of these bindings, though they are somewhat different now. For + details of when and how the PyME package was folded back into GPGME itself see the + Short History documentShort_History.org and/or + Short_History.html. in the Python bindings docs/ + directory.The lang/python/docs/ directory in the GPGME + source.

+

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).

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/python-bindings.dita b/lang/python/docs/dita/howto/part02/python-bindings.dita new file mode 100644 index 00000000..6590fee4 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/python-bindings.dita @@ -0,0 +1,16 @@ + + + + + Python Bindings + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/python-gnupg.dita b/lang/python/docs/dita/howto/part02/python-gnupg.dita new file mode 100644 index 00000000..f083a7a6 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/python-gnupg.dita @@ -0,0 +1,22 @@ + + + + + The python-gnupg package maintained by Vinay Sajip + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part02/requirements.dita b/lang/python/docs/dita/howto/part02/requirements.dita new file mode 100644 index 00000000..584219d5 --- /dev/null +++ b/lang/python/docs/dita/howto/part02/requirements.dita @@ -0,0 +1,20 @@ + + + + + Requirements + +

The GPGME Python bindings only have three requirements:

+

+

    +
  1. A suitable version of Python 2 or Python 3. With Python 2 that means Python 2.7 and + with Python 3 that means Python 3.4 or higher.
  2. +
  3. SWIG.
  4. +
  5. GPGME itself. Which also means that all of GPGME's dependencies must be installed + too.
  6. +
+

+

+ + + diff --git a/lang/python/docs/dita/howto/part03/get-key.dita b/lang/python/docs/dita/howto/part03/get-key.dita new file mode 100644 index 00000000..1e3309c2 --- /dev/null +++ b/lang/python/docs/dita/howto/part03/get-key.dita @@ -0,0 +1,37 @@ + + + + + Get Key + +

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:

+

+ import gpg + +fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" +key = gpg.Context().get_key(fingerprint) + +

+

Whereas this example demonstrates selecting the author's current key with the secret key + word argument set to True:

+

+ import gpg + +fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" +key = gpg.Context().get_key(fingerprint, secret=True) + +

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part03/key-counting.dita b/lang/python/docs/dita/howto/part03/key-counting.dita new file mode 100644 index 00000000..b26fbd2d --- /dev/null +++ b/lang/python/docs/dita/howto/part03/key-counting.dita @@ -0,0 +1,31 @@ + + + + + Counting Keys + +

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.

+

+ import gpg + +c = gpg.Context() +seckeys = c.keylist(pattern=None, secret=True) +pubkeys = c.keylist(pattern=None, secret=False) + +seclist = list(seckeys) +secnum = len(seclist) + +publist = list(pubkeys) +pubnum = len(publist) + +print(""" +Number of secret keys: {0} +Number of public keys: {1} +""".format(secnum, pubnum)) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part03/key-selection.dita b/lang/python/docs/dita/howto/part03/key-selection.dita new file mode 100644 index 00000000..3e51a4da --- /dev/null +++ b/lang/python/docs/dita/howto/part03/key-selection.dita @@ -0,0 +1,53 @@ + + + + + Key Selection + +

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:

+

+ import gpg + +k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") +keys = list(k) + +

+

This is passable and very likely to be common:

+

+ import gpg + +k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") +keys = list(k) + +

+

And this is a really bad idea:

+

+ import gpg + +k = gpg.Context().keylist(pattern="0xDEADBEEF") +keys = list(k) + +

+

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:

+

+ import gpg + +ncsc = gpg.Context().keylist(pattern="ncsc.mil") +nsa = list(ncsc) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/basic-functions.dita b/lang/python/docs/dita/howto/part04/basic-functions.dita new file mode 100644 index 00000000..a0c64b56 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/basic-functions.dita @@ -0,0 +1,12 @@ + + + + + Basic Functions + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/clear-signing.dita b/lang/python/docs/dita/howto/part04/clear-signing.dita new file mode 100644 index 00000000..c6104922 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/clear-signing.dita @@ -0,0 +1,42 @@ + + + + + Clear Signatures + +

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.

+

+ 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()) + +

+

In spite of the appearance of a clear-signed message, the data handled by GPGME in signing + it must still be byte literals.

+

+ 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) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/decryption.dita b/lang/python/docs/dita/howto/part04/decryption.dita new file mode 100644 index 00000000..e3918c55 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/decryption.dita @@ -0,0 +1,31 @@ + + + + + Decryption + +

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.

+

+ import gpg + +ciphertext = input("Enter path and filename of encrypted file: ") +newfile = input("Enter path and filename of file to save decrypted data to: ") + +with open(ciphertext, "rb") as cfile: + plaintext, result, verify_result = gpg.Context().decrypt(cfile) + +with open(newfile, "wb") as nfile: + nfile.write(plaintext) + +

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/default-signing.dita b/lang/python/docs/dita/howto/part04/default-signing.dita new file mode 100644 index 00000000..d3a227b5 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/default-signing.dita @@ -0,0 +1,51 @@ + + + + + Default Signatures + +

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.

+

+ 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()) + +

+

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.

+

+ 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) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/detached-signing.dita b/lang/python/docs/dita/howto/part04/detached-signing.dita new file mode 100644 index 00000000..38406eff --- /dev/null +++ b/lang/python/docs/dita/howto/part04/detached-signing.dita @@ -0,0 +1,42 @@ + + + + + Detached Signatures + +

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).

+

+ 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()) + +

+

As with normal signatures, detached signatures are best handled as byte literals, even when + the output is ASCII armoured.

+

+ 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) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/encrypt-to-many.dita b/lang/python/docs/dita/howto/part04/encrypt-to-many.dita new file mode 100644 index 00000000..df3454f8 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/encrypt-to-many.dita @@ -0,0 +1,100 @@ + + + + + Encrypting to Multiple Keys + +

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,You probably don't really want to do + this. Searching the keyservers for "gnupg.org" produces over 400 results, the majority of + which aren't actually at the gnupg.org domain, but just included a comment regarding the + project in their key somewhere. but does not encrypt to a default key or other + key which is configured to normally encrypt to.

+

+ 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 f: + f.write(ciphertext) + +

+

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:

+

+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + always_trust=True, + add_encrypt_to=True) + +

+

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:

+

+ import gpg + +with open("secret_plans.txt.asc", "rb") as f: + text = f.read() + +c = gpg.Context(armor=True) +rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) +logrus = [] + +for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + +try: + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + add_encrypt_to=True) +except gpg.errors.InvalidRecipients as e: + for i in range(len(e.recipients)): + for n in range(len(logrus)): + if logrus[n].fpr == e.recipients[i].fpr: + logrus.remove(logrus[n]) + else: + pass + try: + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + add_encrypt_to=True) + except: + pass + +with open("secret_plans.txt.asc", "wb") as f: + f.write(ciphertext) + +

+

This will attempt to encrypt to all the keys searched for, then remove invalid recipients + if it fails and try again.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/encrypt-to-one.dita b/lang/python/docs/dita/howto/part04/encrypt-to-one.dita new file mode 100644 index 00000000..2abbe06a --- /dev/null +++ b/lang/python/docs/dita/howto/part04/encrypt-to-one.dita @@ -0,0 +1,83 @@ + + + + + Encrypting to One Key + +

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).
  • +
+

+

+ 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 f: + f.write(ciphertext) + +

+

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:

+

+ import gpg + +a_key = "0x12345678DEADBEEF" + +with open("secret_plans.txt", "rb") as f: + text = f.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 f: + f.write(ciphertext) + +

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/encryption.dita b/lang/python/docs/dita/howto/part04/encryption.dita new file mode 100644 index 00000000..572cc9d2 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/encryption.dita @@ -0,0 +1,12 @@ + + + + + Encryption + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/signing-key-selection.dita b/lang/python/docs/dita/howto/part04/signing-key-selection.dita new file mode 100644 index 00000000..34d02b4a --- /dev/null +++ b/lang/python/docs/dita/howto/part04/signing-key-selection.dita @@ -0,0 +1,28 @@ + + + + + Signing Key Selection + +

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.

+

+ 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) + +

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/signing.dita b/lang/python/docs/dita/howto/part04/signing.dita new file mode 100644 index 00000000..289e3742 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/signing.dita @@ -0,0 +1,11 @@ + + + + + Signing Text and Files + +

The following sections demonstrate how to specify keys to sign with and the types of + signatures which can be made.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/verification.dita b/lang/python/docs/dita/howto/part04/verification.dita new file mode 100644 index 00000000..d50482a8 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/verification.dita @@ -0,0 +1,150 @@ + + + + + Signature Verification + +

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:

+

+ 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 + +

+

Whereas this next example, which is almost identical would work with normal ASCII armoured + files and with clear-signed files:

+

+ 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 + +

+

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:

+

+ with open(filename, "rb") as afile: + text = afile.read() + +if text == data: + print("Good signature.") +else: + pass + +

+

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.

+

+ 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 + +

+

+ import gpg +import time + +filename = "statement.txt" +asc_file = "statement.txt.asc" + +c = gpg.Context() + +try: + data, result = c.verify(open(filename), open(asc_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is not None: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, + sign.fpr, time.ctime(sign.timestamp))) +else: + pass + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part05/add-uid.dita b/lang/python/docs/dita/howto/part05/add-uid.dita new file mode 100644 index 00000000..4265d421 --- /dev/null +++ b/lang/python/docs/dita/howto/part05/add-uid.dita @@ -0,0 +1,39 @@ + + + + + Adding a User ID + +

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.

+

+ import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <danger.mouse@secret.example.net>" + +c.key_add_uid(key, uid) + +

+

Unsurprisingly the result of this is:

+

+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K +~/.gnupg-dm/pubring.kbx +---------------------- +sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA +uid [ultimate] Danger Mouse <danger.mouse@secret.example.net> +uid [ultimate] Danger Mouse <dm@secret.example.net> +ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + +bash-4.4$ + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part05/certification.dita b/lang/python/docs/dita/howto/part05/certification.dita new file mode 100644 index 00000000..072c774e --- /dev/null +++ b/lang/python/docs/dita/howto/part05/certification.dita @@ -0,0 +1,36 @@ + + + + + Key Certification + +

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:

+

+ import gpg + +c = gpg.Context() +uid = "Danger Mouse <dm@secret.example.net>" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +c.key_sign(key, uidsuid, expires_in=2764800) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part05/key-creation.dita b/lang/python/docs/dita/howto/part05/key-creation.dita new file mode 100644 index 00000000..6478bf95 --- /dev/null +++ b/lang/python/docs/dita/howto/part05/key-creation.dita @@ -0,0 +1,34 @@ + + + + + Creating Keys and Subkeys + +

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:

+

+ 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 + +

+

+ + + diff --git a/lang/python/docs/dita/howto/part05/primary-key.dita b/lang/python/docs/dita/howto/part05/primary-key.dita new file mode 100644 index 00000000..5401dc9f --- /dev/null +++ b/lang/python/docs/dita/howto/part05/primary-key.dita @@ -0,0 +1,97 @@ + + + + + Primary Key Creation + +

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.

+

+ import gpg + +c = gpg.Context() + +c.home_dir = "~/.gnupg-dm" +userid = "Danger Mouse <dm@secret.example.net>" + +dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, + sign=True, certify=True) + +

+

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:

+

+ 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)) + +

+

Alternatively the information can be confirmed using the command line program:

+

+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K +~/.gnupg-dm/pubring.kbx +---------------------- +sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA +uid [ultimate] Danger Mouse <dm@secret.example.net> + +bash-4.4$ + +

+

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:

+

+ bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit +Secret key is available. + +sec rsa3072/026D2F19E99E63AA + created: 2018-03-15 expires: 2019-03-15 usage: SC + trust: ultimate validity: ultimate +[ultimate] (1). Danger Mouse <dm@secret.example.net> + +[ultimate] (1). Danger Mouse <dm@secret.example.net> + Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES + Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1 + Compression: ZLIB, BZIP2, ZIP, Uncompressed + Features: MDC, Keyserver no-modify + +bash-4.4$ + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part05/rev-uid.dita b/lang/python/docs/dita/howto/part05/rev-uid.dita new file mode 100644 index 00000000..18d6efbd --- /dev/null +++ b/lang/python/docs/dita/howto/part05/rev-uid.dita @@ -0,0 +1,24 @@ + + + + + Revoking a User ID + +

Revoking a user ID is a fairly similar process, except that it uses the + key_revoke_uid method.

+

+ import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <danger.mouse@secret.example.net>" + +c.key_revoke_uid(key, uid) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part05/subkeys.dita b/lang/python/docs/dita/howto/part05/subkeys.dita new file mode 100644 index 00000000..81491979 --- /dev/null +++ b/lang/python/docs/dita/howto/part05/subkeys.dita @@ -0,0 +1,53 @@ + + + + + Subkey Creation + +

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.

+

+ 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) + +

+

As with the primary key, the results here can be checked with:

+

+ 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)) + +

+

As well as on the command line with:

+

+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K +~/.gnupg-dm/pubring.kbx +---------------------- +sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA +uid [ultimate] Danger Mouse <dm@secret.example.net> +ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + +bash-4.4$ + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part05/user-ids.dita b/lang/python/docs/dita/howto/part05/user-ids.dita new file mode 100644 index 00000000..9308c0a6 --- /dev/null +++ b/lang/python/docs/dita/howto/part05/user-ids.dita @@ -0,0 +1,10 @@ + + + + + User IDs + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part06/group-lines.dita b/lang/python/docs/dita/howto/part06/group-lines.dita new file mode 100644 index 00000000..5a63d150 --- /dev/null +++ b/lang/python/docs/dita/howto/part06/group-lines.dita @@ -0,0 +1,47 @@ + + + + + Group Lines + +

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.

+

+ import subprocess + +lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines() + +for i in range(len(lines)): + if lines[i].startswith("group") is True: + line = lines[i] + else: + pass + +groups = line.split(":")[-1].replace('"', '').split(',') + +group_lines = groups +for i in range(len(group_lines)): + group_lines[i] = group_lines[i].split("=") + +group_lists = group_lines +for i in range(len(group_lists)): + group_lists[i][1] = group_lists[i][1].split() + +

+

The result of that code is that group_lines is a list of lists where + group_lines[i][0] is the name of the group and + group_lines[i][1] is the key IDs of the group as a string.

+

The group_lists result is very similar in that it is a list of lists. The + first part, group_lists[i][0] matches group_lines[i][0] as + the name of the group, but group_lists[i][1] is the key IDs of the group as + a string.

+

To use this code as a module use:

+

+ from groups import group_lists +

+ +
+