#!/usr/bin/python3 # # generate-openpgpkey-hu-3 # Copyright 2017, W. Martin Borgert # License: GPL-3+ # # This script has the same purpose as generate-openpgpkey-hu by Thomas # Arendsen Hein and Andre Heinecke of Intevation GmbH, but: # - is Python 3 instead of Python 2 # - uses python-gnupg instead of python-pyme (removed from Debian) # - is licensed GPL-3+ (like GnuPG) or later instead of GPL-2+ # - uses encode_zbase32 function by Tocho Tochev # - is PEP8 clean :~) import argparse import email.utils import functools import hashlib import itertools import logging import os import sys try: import gnupg except ImportError: gnupg = None def getargs(): ap = argparse.ArgumentParser( description='generate contents of https://.../.well-known/openpgpkey/' + 'hu/ for OpenPGP Web Key Directory (WKD) from GnuPG keyring', formatter_class=argparse.ArgumentDefaultsHelpFormatter) ap.add_argument('-a', '--address', action='append', help='specific email address, more than one possible') ap.add_argument('-d', '--debug', action='store_true', help='print debug output') ap.add_argument('-e', '--exist-ok', action='store_true', help='accept, if target directory already exists') ap.add_argument('-k', '--keyring', help='keyring to parse (or use default GnuPG keyring)') ap.add_argument('-m', '--mail-domain', help='mail domain to filter keys') ap.add_argument('-o', '--output-dir', default="hu", help='directory to write keys') ap.add_argument('-x', '--include-expired', action='store_true', help='include expired keys') return ap.parse_args() # Source: https://gist.githubusercontent.com/tochev/99f19d9ce062f1c7e203 # /raw/0077ec38adc350e0fd1207e6a525de482b40df7e/zbase32.py # Copyright: Tocho Tochev # Licence: MIT # See http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt def encode_zbase32(bs): """ Encode bytes bs using zbase32 encoding. Returns: bytearray >>> encode_zbase32(b'\\xd4z\\x04') == b'4t7ye' True """ ALPTHABET = b"ybndrfg8ejkmcpqxot1uwisza345h769" result = bytearray() for word in itertools.zip_longest(*([iter(bs)] * 5)): padding_count = word.count(None) n = functools.reduce(lambda x, y: (x << 8) + (y or 0), word, 0) for i in range(0, (40 - 8 * padding_count), 5): result.append(ALPTHABET[(n >> (35 - i)) & 0x1F]) return result def localpart2zbase32(s): """transforms local part to lower case, SHA1s it, and encodes zbase32 See https://tools.ietf.org/id/draft-koch-openpgp-webkey-service-01.html >>> localpart2zbase32('Joe.Doe') 'iy9q119eutrkn8s1mk4r39qejnbu3n5q' """ return encode_zbase32( hashlib.sha1(s.lower().encode("utf-8")).digest()).decode("utf-8") class HU: def __init__(self, debug, keyring, output_dir): try: os.makedirs(output_dir, exist_ok=args.exist_ok) except FileExistsError: print("Output directory " + output_dir + " already exists, exiting!", file=sys.stderr) sys.exit(1) if debug: gnupg.logger.setLevel(logging.DEBUG) gnupg.logger.addHandler(logging.StreamHandler()) self.debug = debug self.gpg = gnupg.GPG(gpgbinary="/usr/bin/gpg2",keyring=keyring) self.output_dir = output_dir def get_fps(self, mail_domain, include_expired, addresses): """return dict of localpart: fingerprint""" fps = {} for key in self.gpg.list_keys(): for uid in key.get('uids', []): addr = email.utils.parseaddr(uid)[1] if '@' not in addr: continue local, domain = addr.split("@", 1) # trust: 'd' = disabled, 'e' = expired, 'r' = revoked if mail_domain and domain.lower() != mail_domain.lower() \ or key['trust'] in ['d', 'r'] \ or not include_expired and key['trust'] == 'e' \ or addresses and addr not in addresses: continue if local in fps and fps[local] != key['fingerprint']: print("Multiple options for %s! None used." % local, file=sys.stderr) del fps[local] else: fps[local] = key['fingerprint'] return fps def write_keys(self, fps): for local, fingerprint in fps.items(): with open(os.path.join(self.output_dir, localpart2zbase32(local)), "wb") as f: f.write(self.gpg.export_keys(fingerprint, armor=False)) print("Wrote %d keys to directory '%s'" % (len(fps), self.output_dir)) if __name__ == "__main__": args = getargs() if gnupg is None: print("Please 'apt install python3-gnupg'!", file=sys.stderr) sys.exit(1) hu = HU(args.debug, args.keyring, args.output_dir) fps = hu.get_fps(args.mail_domain, args.include_expired, args.address) hu.write_keys(fps)