/usr/bin/openpgpkey is in hash-slinger 2.7-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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | #!/usr/bin/python
#
# openpgpkey: create OPENPGPKEY DNS record from a key in your keychain.
#
# Copyright 2012 - 2015 Paul Wouters <paul@cypherpunks.ca>
# Copyright 2015 Dirk Stoecker <github@dstoecker.de>
# Copyright 2015 Jan Vcelak <jv@fcelda.cz>
# Copyright 2015 Carsten Strotmann <cs@sys4.de>
# Copyright 2015 Ondrej Sury <ondrej@sury.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
VERSION="2.7"
OPENPGPKEY=61
import sys
import os
import gnupg
import unbound
import hashlib
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 createOPENPGPKEY(email, gpgdisk, keyid, output, debug):
ekey64 = "".join(gpgdisk.export_keys(keyid,minimal=True, secret=False, armor=True).split("\n")[2:-3])
user,domain = email.lower().split("@")
euser = sha256trunc(user)
if output == "rfc" or output == "both":
print "; keyid: %s" % keyid
print "%s._openpgpkey.%s. IN OPENPGPKEY %s" % (euser, domain, ekey64)
if output == "generic" or output == "both":
if debug:
print "Length for generic record is %s" % len(ekey)
print "; keyid: %s" % keyid
print "%s._openpgpkey.%s. IN TYPE61 \# %s %s" % (euser, domain, len(ekey), asctohex(ekey))
def sha256trunc(data):
"""Compute SHA2-256 hash truncated to 28 octets."""
return hashlib.sha256(data).hexdigest()[:56]
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.lower().split("@")
except:
sys.exit("Invalid email syntax")
keyname = "%s._openpgpkey.%s"%(sha256trunc(username), 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='', help='Location of the unbound compatible DNSSEC root.anchor (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('--keyid', action='store', default='', help='specify key by keyid')
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='rfc', 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 = None
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:
cauldron = ( "/usr/share/dns/root.key", "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", "/etc/unbound/root.key" )
for root in cauldron:
if os.path.isfile(root):
rootanchor=root
break
if rootanchor:
try:
ctx.add_ta_file(rootanchor)
except:
unbound.ub_ctx_trustedkeys(ctx,rootanchor)
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)
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.keyid:
pubkey = gpgnet.export_keys(args.keyid, minimal=True)
if not pubkey:
print >> sys.stderr, 'openpgpkey: Requested keyid not present in received OpenPGP data'
sys.exit(1)
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)
if not args.uid and not args.keyid:
pubkey = gpgnet.export_keys(args.email, minimal=True)
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'
found = False
# if we have the keyid, use that
if args.keyid:
ekey = gpgdisk.export_keys(args.keyid,minimal=True, secret=False, armor=False)
if ekey:
found = True
createOPENPGPKEY(args.email, gpgdisk, args.keyid, args.output, args.debug)
# else find key
if not found:
disk_keys = gpgdisk.list_keys()
for pgpkey in disk_keys:
for uid in pgpkey["uids"]:
if "<%s>"%args.email in uid:
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)
createOPENPGPKEY(args.email, gpgdisk, pgpkey["keyid"], args.output, args.debug)
found = True
# give up
if not found:
errt = "No key found for "
if args.email:
errt += "email address %s "%args.email
if args.uid:
errt += "or uid %s "%args.uid
if args.keyid:
errt += "or keyid %s "%args.keyid
sys.exit(errt)
|