API documentation

GnuPG API

Native Python / GPG API

This API was written to replace the GPGME bindings because the GPGME API has a few problems:

  1. it is arcane and difficult to grasp
  2. it is very closely bound to the internal GPG data and commandline structures, which are quite confusing
  3. GPGME doesn’t actually talk to a GPG library, but interacts with GPG through the commandline
  4. GPGME developers are not willing to extend GPGME to cover private key material management and consider this is outside the scope of the project.

The latter two points are especially problematic for this project, and I have therefore started working on a replacement.

Operations are performed mostly through the Keyring or KeyringTmp class (if you do not want to access your regular keyring but an empty temporary one).

This is how you can access keys, which are represented by the OpenPGPkey datastructure, but which will not look in your keyring or on the keyservers itself without the Keyring class.

It seems that I have missed a similar project that’s been around for quite a while (2008-2012):

https://code.google.com/p/python-gnupg/

The above project has a lot of similarities with this implementation, but is better because:

  1. it actually parses most status outputs from GPG, in a clean way
  2. uses threads so it doesn’t block
  3. supports streams
  4. supports verification, key generation and deletion
  5. has a cleaner and more complete test suite

However, the implementation here has:

  1. key signing support
  2. a cleaner API

Error handling is somewhat inconsistent here. Some functions rely on exceptions, other on boolean return values. We prefer exceptions as it allows us to propagate error messages to the UI, but make sure to generate a RuntimeError, and not a ProtocolError, which are unreadable to the user.

class monkeysign.gpg.Context[source]

Python wrapper for GnuPG

This wrapper allows for a simpler interface than GPGME or PyME to GPG, and bypasses completely GPGME to interoperate directly with GPG as a process.

It uses the gpg-agent to prompt for passphrases and communicates with GPG over the stdin for commnads (–command-fd) and stdout for status (–status-fd).

build_command(command)[source]

internal helper to build a proper gpg commandline

this will add relevant arguments around the gpg binary.

like the options arguments, the command is expected to be a regular gpg command with the – stripped. the – are added before being called. this is to make the code more readable, and eventually support other backends that actually make more sense.

this uses build_command to create a commandline out of the ‘options’ dictionary, and appends the provided command at the end. this is because order of certain options matter in gpg, where some options (like –recv-keys) are expected to be at the end.

it is here that the options dictionary is converted into a list. the command argument is expected to be a list of arguments that can be converted to strings. if it is not a list, it is cast into a list.

call_command(command, stdin=None)[source]

internal wrapper to call a GPG commandline

this will call the command generated by build_command() and setup a regular pipe to the subcommand.

this assumes that we have the status-fd on stdout and command-fd on stdin, but could really be used in any other way.

we pass the stdin argument in the standard input of gpg and we keep the output in the stdout and stderr array. the exit code is in the returncode variable.

we can optionnally watch for a confirmation pattern on the statusfd.

expect(fd, pattern)[source]

look for a specific GNUPG status on the next line of output

this is a stub for expect()

expect_pattern(fd, pattern)[source]

make sure the next line matches the provided pattern

in contrast with seek_pattern(), this will not skip non-matching lines and instead raise an exception if such a line is found.

this therefore looks only at the next line, but may also hang like seek_pattern()

if the beginning of the line matches a pattern which is being ignored, it will skip it and look at the next line

gpg_binary = 'gpg'
options = {'batch': None, 'command-fd': 0, 'fixed-list-mode': None, 'list-options': 'show-sig-subpackets,show-uid-validity,show-unusable-uids,show-unusable-subkeys,show-keyring,show-sig-expire', 'no-tty': None, 'no-verbose': None, 'quiet': None, 'status-fd': 2, 'use-agent': None, 'with-colons': None, 'with-fingerprint': None}
seek(fd, pattern)[source]

look for a specific GNUPG status line in the output

this is a stub for seek_pattern()

seek_pattern(fd, pattern)[source]

iterate over file descriptor until certain pattern is found

fd is a file descriptor pattern a string describing a regular expression to match

this will skip lines not matching pattern until the pattern is found. it will raise an IOError if the pattern is not found and EOF is reached.

this may hang for streams that do not send EOF or are waiting for input.

set_option(option, value=None)[source]

set an option to pass to gpg

this adds the given ‘option’ commandline argument with the value ‘value’. to pass a flag without an argument, use ‘None’ for value

unset_option(option)[source]

remove an option from the gpg commandline

version()[source]

return the version of the GPG binary

write(fd, message)[source]

write the specified message to gnupg, usually on stdout

but really, the pipes are often setup outside of here so the fd is hardcoded here

exception monkeysign.gpg.GpgProtocolError[source]

simple exception raised when we have trouble talking with GPG

we try to pass the subprocess.popen.returncode as an errorno and a significant description string

this error shouldn’t be propagated to the user, because it will contain mostly “expect” jargon from the DETAILS.txt file. the gpg module should instead raise a GpgRutimeError with a user-readable error message (e.g. “key not found”).

expected()[source]
found()[source]
match()[source]
exception monkeysign.gpg.GpgRuntimeError[source]
class monkeysign.gpg.Keyring(homedir=None)[source]

Keyring functionalities.

This allows various operations (e.g. listing, signing, exporting data) on a keyring.

Concretely, we talk about a “keyring”, but we really mean a set of public and private keyrings and their trust databases. In practice, this is the equivalent of the GNUPGHOME or –homedir in GPG, and in fact this is implemented by setting a specific homedir to tell GPG to operate on a specific keyring.

We actually use the –homedir parameter to gpg to set the keyring we operate upon.

context = None
decrypt_data(data)[source]

decrypt data using asymetric encryption

returns the plaintext data or raise a GpgRuntimeError if it failed.

del_uid(fingerprint, pattern)[source]
encrypt_data(data, recipient)[source]

encrypt data using asymetric encryption

returns the encrypted data or raise a GpgRuntimeError if it fails

export_data(fpr=None, secret=False)[source]

Export OpenPGP data blocks from the keyring.

This exports actual OpenPGP data, by default in binary format, but can also be exported asci-armored by setting the ‘armor’ option.

fetch_keys(fpr, keyserver=None)[source]

Download keys from a keyserver into the local keyring

This expects a fingerprint (or a at least a key id).

Returns true if the command succeeded.

get_agent_socket()[source]

get the location of the gpg-agent socket for this keyring

get_keys(pattern=None, secret=False, public=True, keys=None)[source]

load keys matching a specific patterns

this uses the (rather poor) list-keys API to load keys information

import_data(data)[source]

Import OpenPGP data blocks into the keyring.

This takes actual OpenPGP data, ascii-armored or not, gpg will gladly take it. This can be signatures, public, private keys, etc.

You may need to set import-flags to import non-exportable signatures, however.

sign_key(pattern, signall=False, local=False)[source]

sign a OpenPGP public key

By default it looks up and signs a specific uid, but it can also sign all uids in one shot thanks to GPG’s optimization on that.

The pattern here should be a full user id if we sign a specific key (default) or any pattern (fingerprint, keyid, partial user id) that GPG will accept if we sign all uids.

@todo that this currently block if the pattern specifies an incomplete UID and we do not sign all keys.

verify_file(sigfile, filename)[source]
class monkeysign.gpg.OpenPGPkey(data=None)[source]

An OpenPGP key.

Some of this datastructure is taken verbatim from GPGME.

algo = -1
creation = 0
disabled = False
expired
expiry

Returns a datetime from the _expiry field or None if the key does not expire

format_fpr()[source]

display a clean version of the fingerprint

this is the display we usually see

fpr = None
get_trust()[source]
invalid = False
keyid(l=8)[source]
length = None
parse_gpg_list(text)[source]
purpose = {}
qualified = False
revoked

Returns whether GnuPG thinks the key has been revoked

This is the second field of the result of the –list-key –with-colons call. Note that this information is only present on public keys, i.e. not on secret keys.

Returns None if it cannot be determined whether this key has been revoked.

secret = False
subkeys = {}
trust = None
trust_map = {'': 'empty', '-': 'unknown', 'd': 'disabled', 'e': 'expired', 'f': 'full', 'i': 'invalid', 'm': 'marginal', 'n': 'none', 'o': 'new', 'q': 'undefined', 'r': 'revoked', 'u': 'ultimate'}
uids = {}
class monkeysign.gpg.OpenPGPuid(uid, trust, creation=0, expire=None, uidhash='')[source]
get_trust()[source]
revoked

Whether this UID has been revoked

Note that, due to GnuPG not exporting that information for secret keys, UIDs of secret keys do not carry that information.

Return None if it cannot be determined whether this UID has been revoked. Try again with the public key.

class monkeysign.gpg.TempKeyring[source]

CLI Interface

GTK Interface