Last active
February 27, 2017 18:44
-
-
Save philpennock/30d030b6d1d0d86603febdabd98a1313 to your computer and use it in GitHub Desktop.
Generate DNS records for PKA for GnuPG 2.1.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
# Must be python2 for zbase32 module :( | |
# $ pip install attrs zbase32 | |
""" | |
pka: emit PKA records for key | |
Generate all PKA records for a given key, which must be on the keyring. | |
Optionally include a keyserver in the CERT/IPGP payload. | |
""" | |
from __future__ import print_function | |
__author__ = 'phil.pennock@spodhuis.org (Phil Pennock)' | |
import argparse | |
import attr | |
import base64 | |
import email.utils | |
import hashlib | |
import os | |
import subprocess | |
import sys | |
import zbase32 | |
GPG_COMMAND = 'gpg' | |
class Error(Exception): | |
"""Base class for exceptions from pka.""" | |
pass | |
@attr.s | |
class PGPKey(object): | |
keyid = attr.ib(validator=attr.validators.instance_of(str)) | |
emails = attr.ib(default=attr.Factory(list)) | |
fingerprint = attr.ib(default='') | |
keyserver = attr.ib(default='', validator=attr.validators.optional(attr.validators.instance_of(str))) | |
def get_from_keyring(self): | |
with open(os.devnull, 'w') as devnull: | |
cmd = subprocess.Popen([GPG_COMMAND, '--with-colons', '--fingerprint', self.keyid], | |
stdout=subprocess.PIPE, stderr=devnull) | |
for l in cmd.stdout: | |
fields = l.split(':') | |
if fields[0] == 'fpr' and not self.fingerprint: | |
self.fingerprint = fields[9] | |
elif fields[0] == 'uid': | |
pair = email.utils.parseaddr(fields[9]) | |
if '@' in pair[1]: | |
self.emails.append(pair[1]) | |
rc = cmd.wait() | |
if rc: | |
raise Error('gpg invocation failed, exiting {}'.format(rc)) | |
def get_data(self, email): | |
left_hand_side, domain = email.rsplit('@', 1) | |
rrname = zbase32.zbase32.b2a(hashlib.sha1(left_hand_side).digest()) | |
raw_fp = self.fingerprint.decode('hex') | |
if len(raw_fp) > 255: | |
raise Error('fingerprint too long to handle: length {}'.format(len(raw_fp))) | |
ipgpdata = chr(len(raw_fp)) + raw_fp | |
if self.keyserver: | |
ipgpdata += self.keyserver | |
return { | |
'lhs': left_hand_side, | |
'domain': domain, | |
'rrname': rrname, | |
'key': self, | |
'ipgpdata': base64.b64encode(ipgpdata), | |
} | |
def display(self, outstream): | |
for email in self.emails: | |
print("""$ORIGIN _pka.{domain}. | |
; Fingerprint: {key.fingerprint} | |
; <{lhs}@{domain}> | |
{rrname} IN CERT 6 0 0 {ipgpdata} | |
""".format(**self.get_data(email)), file=outstream) | |
def _main(args, argv0): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('-c', '--gpg-cmd', | |
default='', type=str, | |
help='Override the PGP command to invoke') | |
parser.add_argument('-k', '--keyserver', | |
default='', type=str, | |
help='Specify a keyserver to include') | |
parser.add_argument('keyids', nargs='+', metavar='keyid', | |
help='PGP keyids (in keyring) to provide DNS for') | |
options = parser.parse_args(args=args) | |
if options.gpg_cmd: | |
global GPG_COMMAND | |
GPG_COMMAND = options.gpg_cmd | |
for keyid in options.keyids: | |
key = PGPKey(keyid) | |
if options.keyserver: | |
key.keyserver = options.keyserver | |
key.get_from_keyring() | |
key.display(sys.stdout) | |
if __name__ == '__main__': | |
argv0 = sys.argv[0].rsplit('/')[-1] | |
rv = _main(sys.argv[1:], argv0=argv0) | |
sys.exit(rv) | |
# vim: set ft=python sw=2 expandtab : |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment