-
Notifications
You must be signed in to change notification settings - Fork 15
/
openpgpkey
executable file
·242 lines (213 loc) · 9.46 KB
/
openpgpkey
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
242
#!/usr/bin/python3
#
# openpgpkey: create OPENPGPKEY DNS record from a key in your keychain.
#
# Copyright 2012 - 2015 Paul Wouters <[email protected]>
# Copyright 2015 Dirk Stoecker <[email protected]>
# Copyright 2015 Jan Vcelak <[email protected]>
# Copyright 2015 Carsten Strotmann <[email protected]>
# Copyright 2015 Ondrej Sury <[email protected]>
#
# 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="3.3"
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' % 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(r"%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.encode(encoding="utf-8")).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('Warning: query data was not secured by DNSSEC.', file=sys.stderr)
# 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 [email protected]')
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: /var/lib/unbound/root.anchor)')
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('openpgpkey: %s is not a file. Unable to use it as rootanchor' % args.rootanchor, file=sys.stdout)
sys.exit(1)
else:
cauldron = ( "/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('openpgpkey: %s is not a file. Unable to use it as resolv.conf' % args.resolvconf, file=sys.stdout)
sys.exit(1)
ctx.resolvconf(resolvconf)
openpgpkeys = getOPENPGPKEY(args.email, args.insecure)
if len(openpgpkeys) == 0:
print('openpgpkey: Received nothing?', file=sys.stderr)
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('openpgpkey: Requested keyid not present in received OpenPGP data', file=sys.stderr)
sys.exit(1)
if args.uid:
pubkey = gpgnet.export_keys(args.uid, minimal=True)
if not pubkey:
print('openpgpkey: Requested uid not present in received OpenPGP data', file=sys.stderr)
for id in gpgnet.list_keys()[0]['uids']:
print("# %s"%id, file=sys.stderr)
sys.exit(1)
if not args.uid and not args.keyid:
pubkey = gpgnet.export_keys(args.email, minimal=True)
if not pubkey:
print('openpgpkey: Received OpenPGP data does not contain a key with keyid %s'%args.email, file=sys.stderr)
print('(add --uid <uid> to override with any of the below received uids)', file=sys.stderr)
for id in gpgnet.list_keys()[0]['uids']:
print("# %s"%id, file=sys.stderr)
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("Received from DNS: Key-ID:%s Fingerprint:%s"%(pkey["keyid"], pkey["fingerprint"]), file=sys.stderr)
found = False
for dkey in disk_keys:
if args.debug:
print("Local disk: Key-ID:%s Fingerprint:%s"%(dkey["keyid"], dkey["fingerprint"]), file=sys.stderr)
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("Received key with keyid %s was found"%pkey["keyid"], file=sys.stderr)
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("Found matching KeyID: %s (%s) for %s"%(pgpkey["keyid"], pgpkey["fingerprint"], uid), file=sys.stderr)
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)