/
checkdanecert.py
executable file
·151 lines (118 loc) · 4.38 KB
/
checkdanecert.py
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
#!/usr/bin/env python
#
# Validate a TLS certificate with DANE-EE usage.
# Get a TLS certificate from a HTTP server and verify it with
# DANE/DNSSEC. Only supports TLSA usage=3 (DANE-EE)
#
import os.path, sys, socket, hashlib
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import PublicFormat
import getdns
import ssl
def usage():
print """\
Usage: %s [hostname] [port]\
""" % os.path.basename(sys.argv[0])
sys.exit(1)
def compute_hash(func, string):
"""compute hash of string using given hash function"""
h = func()
h.update(string)
return h.hexdigest()
def get_addresses(hostname):
extensions = {
"return_both_v4_and_v6" : getdns.EXTENSION_TRUE
}
ctx = getdns.Context()
try:
results = ctx.address(name=hostname, extensions=extensions)
except getdns.error, e:
print(str(e))
sys.exit(1)
status = results.status
address_list = []
if status == getdns.RESPSTATUS_GOOD:
for addr in results.just_address_answers:
address_list.append((addr['address_type'], addr['address_data']))
else:
print "getdns.address(): failed, return code: %d" % status
address_list = [ x for x in address_list if x[0] != 'IPv6' ]
return address_list
def get_tlsa_rdata_set(replies, requested_usage=None):
tlsa_rdata_set = []
for reply in replies:
for rr in reply['answer']:
if rr['type'] == getdns.RRTYPE_TLSA:
rdata = rr['rdata']
usage = rdata['certificate_usage']
selector = rdata['selector']
matching_type = rdata['matching_type']
cadata = rdata['certificate_association_data']
cadata = str(cadata).encode('hex')
if usage == requested_usage:
tlsa_rdata_set.append(
(usage, selector, matching_type, cadata) )
return tlsa_rdata_set
def get_tlsa(port, proto, hostname):
extensions = {
"dnssec_return_only_secure" : getdns.EXTENSION_TRUE,
}
qname = "_%d._%s.%s" % (port, proto, hostname)
ctx = getdns.Context()
try:
results = ctx.general(name=qname,
request_type=getdns.RRTYPE_TLSA,
extensions=extensions)
except getdns.error, e:
print(str(e))
sys.exit(1)
status = results.status
if status == getdns.RESPSTATUS_GOOD:
return get_tlsa_rdata_set(results.replies_tree, requested_usage=3)
else:
print "getdns.general(): failed, return code: %d" % status
return None
def verify_tlsa(cert, usage, selector, matchtype, hexdata1):
if selector == 0:
certdata = cert.as_der()
elif selector == 1:
cert = x509.load_pem_x509_certificate(str(cert), default_backend())
certdata = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
else:
raise ValueError("selector type %d not recognized" % selector)
if matchtype == 0:
hexdata2 = hexdump(certdata)
elif matchtype == 1:
hexdata2 = compute_hash(hashlib.sha256, certdata)
elif matchtype == 2:
hexdata2 = compute_hash(hashlib.sha512, certdata)
else:
raise ValueError("matchtype %d not recognized" % matchtype)
if hexdata1 == hexdata2:
return True
else:
return False
if __name__ == '__main__':
try:
hostname, port = sys.argv[1:]
port = int(port)
except:
usage()
tlsa_rdata_set = get_tlsa(port, "tcp", hostname)
addrs = get_addresses(hostname)
for (iptype, ipaddr) in addrs:
cert = ssl.get_server_certificate((ipaddr, port))
# find a matching TLSA record entry for the certificate
tlsa_match = False
for (usage, selector, matchtype, hexdata) in tlsa_rdata_set:
if verify_tlsa(cert, usage, selector, matchtype, hexdata):
tlsa_match = True
print "Matched TLSA record %d %d %d %s" % \
(usage, selector, matchtype, hexdata)
else:
print "Didn't match TLSA record %d %d %d %s"% \
(usage, selector, matchtype, hexdata)
if not tlsa_match:
print "No Matching DANE-EE TLSA record found."