docs and examples: python bindings

* Woumd up the "what's new" section.
* Added an example for sending a key to the keyservers via hkp4py.
* Updated the export key code to use a more complete check for the
  $GNUPGHOME location.
* Expanded on the installation and reinstallation troubleshooting
  section.

Tested-by: Ben McGinnes <ben@adversary.org>
Signed-off-by: Ben McGinnes <ben@adversary.org>
This commit is contained in:
Ben McGinnes 2018-09-25 09:59:31 +10:00
parent e9da4d9710
commit 62e4e2cb5e
7 changed files with 486 additions and 36 deletions

View File

@ -64,6 +64,10 @@ GPGME Python bindings installation
* Installation:: * Installation::
* Known Issues:: * Known Issues::
Requirements
* Recommended Additions::
Installation Installation
* Installing GPGME:: * Installing GPGME::
@ -71,6 +75,7 @@ Installation
Known Issues Known Issues
* Breaking Builds:: * Breaking Builds::
* Reinstalling Responsibly::
* Multiple installations:: * Multiple installations::
* Won't Work With Windows:: * Won't Work With Windows::
* CFFI is the Best™ and GPGME should use it instead of SWIG:: * CFFI is the Best™ and GPGME should use it instead of SWIG::
@ -101,6 +106,7 @@ Exporting keys
* Exporting public keys:: * Exporting public keys::
* Exporting secret keys:: * Exporting secret keys::
* Sending public keys to the SKS Keyservers::
Basic Functions Basic Functions
@ -445,7 +451,8 @@ The GPGME Python bindings only have three requirements:
@enumerate @enumerate
@item @item
A suitable version of Python 2 or Python 3. With Python 2 that 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. means CPython 2.7 and with Python 3 that means CPython 3.4 or
higher.
@item @item
@uref{https://www.swig.org, SWIG}. @uref{https://www.swig.org, SWIG}.
@item @item
@ -453,6 +460,33 @@ GPGME itself. Which also means that all of GPGME's dependencies
must be installed too. must be installed too.
@end enumerate @end enumerate
@menu
* Recommended Additions::
@end menu
@node Recommended Additions
@subsection Recommended Additions
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.
@enumerate
@item
If possible, use Python 3 instead of 2.
@item
Favour a more recent version of Python since even 3.4 is due to
reach EOL soon. In production systems and services, Python 3.6
should be robust enough to be relied on.
@item
If possible add the following Python modules which are not part of
the standard library: @uref{http://docs.python-requests.org/en/latest/index.html, Requests}, @uref{http://cython.org/, Cython} and @uref{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.
@end enumerate
@node Installation @node Installation
@section Installation @section Installation
@ -495,6 +529,7 @@ they be encountered.
@menu @menu
* Breaking Builds:: * Breaking Builds::
* Reinstalling Responsibly::
* Multiple installations:: * Multiple installations::
* Won't Work With Windows:: * Won't Work With Windows::
* CFFI is the Best™ and GPGME should use it instead of SWIG:: * CFFI is the Best™ and GPGME should use it instead of SWIG::
@ -559,10 +594,34 @@ If Python is set to precede one of the other languages then it is
possible that the errors described here may interrupt the build possible that the errors described here may interrupt the build
process before generating bindings for those other languages. In process before generating bindings for those other languages. In
these cases it may be preferable to configure all preferred language these cases it may be preferable to configure all preferred language
howto-export-public-keybindings separately with alternative @samp{configure} steps for GPGME using bindings separately with alternative @samp{configure} steps for GPGME using
the @samp{--enable-languages=$LANGUAGE} option. the @samp{--enable-languages=$LANGUAGE} option.
@end enumerate @end enumerate
@node Reinstalling Responsibly
@subsection Reinstalling Responsibly
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 @samp{site-packages} directory for the version of Python in
question and removing the @samp{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.
@node Multiple installations @node Multiple installations
@subsection Multiple installations @subsection Multiple installations
@ -1427,6 +1486,7 @@ third is for exporting secret keys.
@menu @menu
* Exporting public keys:: * Exporting public keys::
* Exporting secret keys:: * Exporting secret keys::
* Sending public keys to the SKS Keyservers::
@end menu @end menu
@node Exporting public keys @node Exporting public keys
@ -1586,12 +1646,26 @@ else:
logrus = input("Enter the UID matching the secret key(s) to export: ") logrus = input("Enter the UID matching the secret key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass
@ -1656,12 +1730,26 @@ else:
logrus = input("Enter the UID matching the secret key(s) to export: ") logrus = input("Enter the UID matching the secret key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass
@ -1712,6 +1800,70 @@ else:
pass pass
@end example @end example
@node Sending public keys to the SKS Keyservers
@subsection Sending public keys to the SKS Keyservers
As with the previous section on importing keys, the @samp{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.
@example
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 example
An expanded version of this script with additional functions for
specifying an alternative homedir location is in the examples
directory as @samp{send-key-to-keyserver.py}.
The @samp{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.
@node Basic Functions @node Basic Functions
@chapter Basic Functions @chapter Basic Functions

View File

@ -293,12 +293,34 @@ section for further details.
The GPGME Python bindings only have three requirements: The GPGME Python bindings only have three requirements:
1. A suitable version of Python 2 or Python 3. With Python 2 that 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. means CPython 2.7 and with Python 3 that means CPython 3.4 or
higher.
2. [[https://www.swig.org][SWIG]]. 2. [[https://www.swig.org][SWIG]].
3. GPGME itself. Which also means that all of GPGME's dependencies 3. GPGME itself. Which also means that all of GPGME's dependencies
must be installed too. 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]], [[http://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.
** Installation ** Installation
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: installation :CUSTOM_ID: installation
@ -398,10 +420,37 @@ If Python is set to precede one of the other languages then it is
possible that the errors described here may interrupt the build possible that the errors described here may interrupt the build
process before generating bindings for those other languages. In process before generating bindings for those other languages. In
these cases it may be preferable to configure all preferred language these cases it may be preferable to configure all preferred language
howto-export-public-keybindings separately with alternative =configure= steps for GPGME using bindings separately with alternative =configure= steps for GPGME using
the =--enable-languages=$LANGUAGE= option. 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 *** Multiple installations
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: snafu-the-full-monty :CUSTOM_ID: snafu-the-full-monty
@ -1446,12 +1495,26 @@ else:
logrus = input("Enter the UID matching the secret key(s) to export: ") logrus = input("Enter the UID matching the secret key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass
@ -1516,12 +1579,26 @@ else:
logrus = input("Enter the UID matching the secret key(s) to export: ") logrus = input("Enter the UID matching the secret key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass
@ -1573,6 +1650,73 @@ else:
#+END_SRC #+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 * Basic Functions
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: howto-the-basics :CUSTOM_ID: howto-the-basics

View File

@ -51,12 +51,26 @@ else:
logrus = input("Enter the UID matching the key(s) to export: ") logrus = input("Enter the UID matching the key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass

View File

@ -51,12 +51,26 @@ else:
logrus = input("Enter the UID matching the key(s) to export: ") logrus = input("Enter the UID matching the key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass

View File

@ -54,12 +54,26 @@ else:
logrus = input("Enter the UID matching the secret key(s) to export: ") logrus = input("Enter the UID matching the secret key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass

View File

@ -63,12 +63,26 @@ else:
logrus = input("Enter the UID matching the secret key(s) to export: ") logrus = input("Enter the UID matching the secret key(s) to export: ")
homedir = input("Enter the GPG configuration directory path (optional): ") homedir = input("Enter the GPG configuration directory path (optional): ")
if homedir.startswith("~"): if len(homedir) == 0:
if os.path.exists(os.path.expanduser(homedir)) is True: homedir = None
c.home_dir = os.path.expanduser(homedir) 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: else:
pass pass
elif os.path.exists(homedir) is True:
if homedir is not None:
c.home_dir = homedir c.home_dir = homedir
else: else:
pass pass

View File

@ -0,0 +1,98 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import gpg
import hkp4py
import os.path
import sys
# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU General Public License and the GNU
# Lesser General Public along with this program; if not, see
# <http://www.gnu.org/licenses/>.
print("""
This script sends one or more public keys to the SKS keyservers and is
essentially a slight variation on the export-key.py script.
The default is to send all keys if there is no pattern or search term.
""")
c = gpg.Context(armor=True)
server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
if len(sys.argv) >= 3:
logrus = sys.argv[1]
homedir = sys.argv[2]
elif len(sys.argv) == 2:
logrus = sys.argv[1]
homedir = input("Enter the GPG configuration directory path (optional): ")
else:
logrus = input("Enter the UID matching the 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 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