Skip to content

Instantly share code, notes, and snippets.

@philpennock
Last active February 27, 2017 18:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philpennock/30d030b6d1d0d86603febdabd98a1313 to your computer and use it in GitHub Desktop.
Save philpennock/30d030b6d1d0d86603febdabd98a1313 to your computer and use it in GitHub Desktop.
Generate DNS records for PKA for GnuPG 2.1.x
#!/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