wkd4pgp/generate-openpgpkey-hu-3

147 lines
5.2 KiB
Python

#!/usr/bin/python3
#
# generate-openpgpkey-hu-3
# Copyright 2017, W. Martin Borgert <debacle@debian.org>
# 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 <tocho AT tochev DOT net>
# 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)