/usr/bin/openpgpkey is in hash-slinger 2.6-1.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | #!/usr/bin/python
# openpgpkey: create OPENPGPKEY DNS record from a key in your keychain.
# Copyright 2012 - 2014 Paul Wouters <paul@cypherpunks.ca>
#
# License: GNU GENERAL PUBLIC LICENSE Version 2 or later
VERSION="2.6"
OPENPGPKEY=61
import sys
import os
import gnupg
import unbound
from hashlib import sha224
import tempfile
import shutil
global ctx
def asctohex(s):
empty = '' # I use this construct because I find ''.join() too dense
return empty.join(['%02x' % ord(c) for c in s]) # the %02 pads when needed
def getOPENPGPKEY(email,insecure_ok):
"""This function queries for an OPENPGPKEY DNS Resource Record and compares it with the local gnupg keyring"""
global ctx
try:
username, domainname = email.split("@")
except:
sys.exit("Invalid email syntax")
keyname = "%s._openpgpkey.%s"%(sha224(username).hexdigest() ,domainname)
status, result = ctx.resolve(keyname, OPENPGPKEY)
if status == 0 and result.havedata:
if not result.secure:
if not insecure_ok:
# The data is insecure and a secure lookup was requested
sys.exit("Error: query data is not secured by DNSSEC - use --insecure to override")
else:
print >> sys.stderr, 'Warning: query data was not secured by DNSSEC.'
# If we are here the data was either secure or insecure data is accepted
return result.data.raw
else:
sys.exit('Unsuccesful lookup or no data returned for OPENPGPKEY (rrtype 61)')
if __name__ == '__main__':
import argparse
# create the parser
parser = argparse.ArgumentParser(description='Create and verify OPENPGPKEY records.', epilog='For bugs. see paul@nohats.ca')
parser.add_argument('--verify','-v', action='store_true', help='Verify an OPENPGPKEY record, exit 0 when all records are matched, exit 1 on error.')
parser.add_argument('--fetch','-f', action='store_true', help='Fetch an OPENPGPKEY record, and show it in ascii armor on stdout')
parser.add_argument('--create','-c', action='store_true', help='Create an OPENPGKEY record')
parser.add_argument('--version', action='version', version='openpgpkey version: %s'%VERSION, help='show version and exit')
parser.add_argument('--insecure', action='store_true', default=False, help='Allow use of non-dnssec secured answers')
parser.add_argument('--resolvconf', action='store', default='', help='Use a recursive resolver listed in a resolv.conf file (default: /etc/resolv.conf)')
parser.add_argument('--rootanchor', action='store', default='/usr/share/dns/root.key', help='Location of the unbound compatible DNSSEC root key (default: /usr/share/dns/root.key)')
parser.add_argument('email', metavar="email")
parser.add_argument('--uid', action='store', default='', help='override the uid (email address) within the key')
parser.add_argument('--debug', '-d', action='store_true', help='Print details plus the result of the validation')
parser.add_argument('--quiet', '-q', action='store_true', help='Ignored for backwards compatibility')
# default for now to generic - when more tools support OPENPGPKEY, switch the default to rfc
parser.add_argument('--output', '-o', action='store', default='generic', choices=['generic','rfc','both'], help='The type of output. Generic (RFC 3597, TYPE61), RFC (OPENPGPKEY) or both (default: %(default)s).')
args = parser.parse_args()
if args.verify and args.create:
sys.exit("openpgpkey: must specify --create or --verify")
if args.verify or args.fetch:
# unbound setup, only for verify
global ctx
ctx = unbound.ub_ctx()
resolvconf = "/etc/resolv.conf"
rootanchor = "/usr/share/dns/root.key"
if args.resolvconf:
if os.path.isfile(args.resolvconf):
resolvconf = args.resolvconf
else:
print >> sys.stdout, 'openpgpkey: %s is not a file. Unable to use it as resolv.conf' % args.resolvconf
sys.exit(1)
ctx.resolvconf(resolvconf)
if args.rootanchor:
if os.path.isfile(args.rootanchor):
rootanchor = args.rootanchor
else:
print >> sys.stdout, 'openpgpkey: %s is not a file. Unable to use it as rootanchor' % args.rootanchor
sys.exit(1)
else:
if not os.path.isfile(rootanchor):
rootanchor = None
if rootanchor:
try:
ctx.add_ta_file(rootanchor)
except:
unbound.ub_ctx_trustedkeys(ctx,rootanchor)
else:
# fallback to a root key if we can find it
if os.path.isfile("/usr/share/dns/root.key"):
rootkey = "/usr/share/dns/root.key"
else:
rootkey = None
if rootkey:
unbound.ub_ctx_trustedkeys(ctx,rootkey)
# unbound setup completed
openpgpkeys = getOPENPGPKEY(args.email, args.insecure)
if len(openpgpkeys) == 0:
print >> sys.stderr, 'openpgpkey: Received nothing?'
sys.exit(1)
fdir = tempfile.mkdtemp(".gpg","openpgpkey-","/tmp/")
gpgnet = gnupg.GPG(gnupghome=fdir)
gpgnet.decode_errors = 'ignore'
for openpgpkey in openpgpkeys:
import_result = gpgnet.import_keys(openpgpkey)
if args.fetch:
if args.uid:
pubkey = gpgnet.export_keys(args.uid, minimal=True)
if not pubkey:
print >> sys.stderr, 'openpgpkey: Requested uid not present in received OpenPGP data'
for id in gpgnet.list_keys()[0]['uids']:
print >> sys.stderr, "# %s"%id
sys.exit(1)
else:
pubkey = gpgnet.export_keys(args.email, minimal=True)
if not pubkey:
print >> sys.stderr, 'openpgpkey: Received OpenPGP data does not contain a key with keyid %s'%args.email
print >> sys.stderr, '(add --uid <uid> to override with any of the below received uids)'
for id in gpgnet.list_keys()[0]['uids']:
print >> sys.stderr, "# %s"%id
sys.exit(1)
pubkey = pubkey.replace("Version:","Comment: %s key obtained from DNS\nVersion:"%args.email)
if args.insecure:
pubkey = pubkey.replace("Version:","Comment: NOT VALIDATED BY DNSSEC\nVersion:")
else:
pubkey = pubkey.replace("Version:","Comment: key transfer was protected by DNSSEC\nVersion:")
print pubkey
if args.fetch:
sys.exit(0)
received_keys = gpgnet.list_keys()
gpgdisk = gnupg.GPG()
gpgdisk.decode_errors = 'ignore'
disk_keys = gpgdisk.list_keys()
for pkey in received_keys:
if args.debug:
print >> sys.stderr, "Received from DNS: Key-ID:%s Fingerprint:%s"%(pkey["keyid"], pkey["fingerprint"])
found = False
for dkey in disk_keys:
if args.debug:
print >> sys.stderr, "Local disk: Key-ID:%s Fingerprint:%s"%(dkey["keyid"], dkey["fingerprint"])
if pkey["keyid"] == dkey["keyid"] and pkey["fingerprint"] == dkey["fingerprint"]:
found = True
if found == False:
shutil.rmtree(fdir)
sys.exit("Received key with keyid %s was not found"%pkey["keyid"])
else:
if args.debug:
print >> sys.stderr, "Received key with keyid %s was found"%pkey["keyid"]
print "All OPENPGPKEY records matched with content from the local keyring"
shutil.rmtree(fdir)
sys.exit(0)
else: # we want to create
gpgdisk = gnupg.GPG()
gpgdisk.decode_errors = 'ignore'
disk_keys = gpgdisk.list_keys()
found = False
for pgpkey in disk_keys:
for uid in pgpkey["uids"]:
if "<%s>"%args.email in uid:
found = True
if args.debug:
print >> sys.stderr, "Found matching KeyID: %s (%s) for %s"%(pgpkey["keyid"], pgpkey["fingerprint"], uid)
ekey = gpgdisk.export_keys(pgpkey["keyid"],minimal=True, secret=False, armor=False)
ekey64 = "".join(gpgdisk.export_keys(pgpkey["keyid"],minimal=True, secret=False, armor=True).split("\n")[2:-3])
user,domain = args.email.split("@")
euser = sha224(user).hexdigest()
if args.output == "rfc":
print "%s._openpgpkey.%s. IN OPENPGPKEY %s"%(euser,domain,ekey64)
if args.output == "generic":
if args.debug:
print "Length for generic record is %s"%len(ekey)
print "%s._openpgpkey.%s. IN TYPE61 \# %s %s"%(euser,domain,len(ekey),asctohex(ekey))
if not found:
sys.exit("No key found containing the email address '%s' in the keyid(s)"%args.email)
|