11#!/usr/bin/env python
22#
3+ # Validate a TLS certificate with DANE-EE usage.
34# Get a TLS certificate from a HTTP server and verify it with
4- # DANE/DNSSEC. Only supports TLSA usage type 3 (DANE-EE).
5+ # DANE/DNSSEC. Only supports TLSA usage= 3 (DANE-EE)
56#
67
7- import sys , socket , hashlib
8+ import os . path , sys , socket , hashlib
89from M2Crypto import SSL , X509
910import getdns
1011
1112
13+ def usage ():
14+ print """\
15+ Usage: %s [hostname] [port]\
16+ """ % os .path .basename (sys .argv [0 ])
17+ sys .exit (1 )
18+
19+
1220def compute_hash (func , string ):
1321 """compute hash of string using given hash function"""
1422 h = func ()
1523 h .update (string )
1624 return h .hexdigest ()
1725
1826
19- def get_tlsa_rdata_set (replies ):
27+ def get_addresses (hostname ):
28+
29+ extensions = {
30+ "return_both_v4_and_v6" : getdns .GETDNS_EXTENSION_TRUE
31+ }
32+ ctx = getdns .Context ()
33+ results = ctx .address (name = hostname , extensions = extensions )
34+ status = results ['status' ]
35+
36+ address_list = []
37+ if status == getdns .GETDNS_RESPSTATUS_GOOD :
38+ for addr in results ['just_address_answers' ]:
39+ address_list .append ((addr ['address_type' ], addr ['address_data' ]))
40+ else :
41+ print "getdns.address(): failed, return code: %d" % status
42+
43+ return address_list
44+
45+
46+ def get_tlsa_rdata_set (replies , requested_usage = None ):
2047 tlsa_rdata_set = []
2148 for reply in replies :
2249 for rr in reply ['answer' ]:
@@ -27,34 +54,33 @@ def get_tlsa_rdata_set(replies):
2754 matching_type = rdata ['matching_type' ]
2855 cadata = rdata ['certificate_association_data' ]
2956 cadata = str (cadata ).encode ('hex' )
30- tlsa_rdata_set .append (
31- (usage , selector , matching_type , cadata ) )
57+ if usage == requested_usage :
58+ tlsa_rdata_set .append (
59+ (usage , selector , matching_type , cadata ) )
3260 return tlsa_rdata_set
3361
3462
3563def get_tlsa (port , proto , hostname ):
3664
65+ extensions = {
66+ "dnssec_return_only_secure" : getdns .GETDNS_EXTENSION_TRUE ,
67+ }
3768 qname = "_%d._%s.%s" % (port , proto , hostname )
3869 ctx = getdns .Context ()
39- extensions = { "dnssec_return_only_secure" : getdns .GETDNS_EXTENSION_TRUE }
4070 results = ctx .general (name = qname ,
4171 request_type = getdns .GETDNS_RRTYPE_TLSA ,
4272 extensions = extensions )
4373 status = results ['status' ]
4474
4575 if status == getdns .GETDNS_RESPSTATUS_GOOD :
46- return get_tlsa_rdata_set (results ['replies_tree' ])
76+ return get_tlsa_rdata_set (results ['replies_tree' ], requested_usage = 3 )
4777 else :
48- print "getdns: failed looking up TLSA record, code: %d" % status
78+ print "getdns.general() : failed, return code: %d" % status
4979 return None
5080
5181
5282def verify_tlsa (cert , usage , selector , matchtype , hexdata1 ):
5383
54- if usage != 3 :
55- print "Only TLSA usage type 3 is currently supported"
56- return
57-
5884 if selector == 0 :
5985 certdata = cert .as_der ()
6086 elif selector == 1 :
@@ -79,35 +105,62 @@ def verify_tlsa(cert, usage, selector, matchtype, hexdata1):
79105
80106if __name__ == '__main__' :
81107
82- hostname , port = sys . argv [ 1 :]
83- port = int ( port )
84- tlsa_rdata_set = get_tlsa ( port , "tcp" , hostname )
85-
86- ctx = SSL . Context ()
108+ try :
109+ hostname , port = sys . argv [ 1 :]
110+ port = int ( port )
111+ except :
112+ usage ()
87113
88- sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
89- sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
114+ tlsa_rdata_set = get_tlsa (port , "tcp" , hostname )
90115
91- connection = SSL .Connection (ctx , sock = sock )
92- connection .connect ((hostname , port ))
116+ for (iptype , ipaddr ) in get_addresses (hostname ):
93117
94- chain = connection .get_peer_cert_chain ()
95- # Get the first certificate from the chain (which will be the EE cert)
96- cert = chain [0 ]
118+ print "Connecting to %s at address %s ..." % (hostname , ipaddr )
119+ ctx = SSL .Context ()
97120
98- # find a matching TLSA record entry for the certificate
99- tlsa_match = False
100- for (usage , selector , matchtype , hexdata ) in tlsa_rdata_set :
101- if verify_tlsa (cert , usage , selector , matchtype , hexdata ):
102- tlsa_match = True
103- print "Certificate matched TLSA record %d %d %d %s" % \
104- (usage , selector , matchtype , hexdata )
121+ if iptype == "IPv4" :
122+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
123+ elif iptype == "IPv6" :
124+ sock = socket .socket (socket .AF_INET6 , socket .SOCK_STREAM )
105125 else :
106- print "Certificate did not match TLSA record %d %d %d %s" % \
107- (usage , selector , matchtype , hexdata )
108- if tlsa_match :
109- print "Found at least one matching TLSA record"
110-
111- connection .close ()
112- ctx .close ()
126+ raise ValueError , "Unknown address type: %s" % iptype
127+
128+ sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
129+ connection = SSL .Connection (ctx , sock = sock )
130+
131+ # set TLS SNI extension if available in M2Crypto on this platform
132+ # Note: the official M2Crypto release does not yet (as of late 2014)
133+ # have support for SNI, sigh, but patches exist.
134+ try :
135+ connection .set_tlsext_host_name (hostname )
136+ except AttributeError :
137+ pass
138+
139+ # Per https://tools.ietf.org/html/draft-ietf-dane-ops, for DANE-EE
140+ # usage, certificate identity checks are based solely on the TLSA
141+ # record, so we ignore name mismatch conditions in the certificate.
142+ try :
143+ connection .connect ((ipaddr , port ))
144+ except SSL .Checker .WrongHost :
145+ pass
146+
147+ chain = connection .get_peer_cert_chain ()
148+ cert = chain [0 ]
149+
150+ # find a matching TLSA record entry for the certificate
151+ tlsa_match = False
152+ for (usage , selector , matchtype , hexdata ) in tlsa_rdata_set :
153+ if verify_tlsa (cert , usage , selector , matchtype , hexdata ):
154+ tlsa_match = True
155+ print "Matched TLSA record %d %d %d %s" % \
156+ (usage , selector , matchtype , hexdata )
157+ else :
158+ print "Didn't match TLSA record %d %d %d %s" % \
159+ (usage , selector , matchtype , hexdata )
160+
161+ if not tlsa_match :
162+ print "No Matching DANE-EE TLSA record found."
163+
164+ connection .close ()
165+ ctx .close ()
113166
0 commit comments