bcf4d40c6905e892891968c0a0081a9d5ce1bf16
[public/dnssec-swede-utility.git] / swede
1 #!/usr/bin/python2
2
3 # swede - A tool to create DANE/TLSA records.
4 # This tool is really simple and not foolproof, it doesn't check the CN in the
5 # Subject field of the certificate. It also doesn't check if the supplied
6 # certificate is a CA certificate if usage 1 is specified (or any other
7 # checking for that matter).
8 #
9 # Usage is explained when running this program with --help
10 #
11 # This tool is loosly based on the dane tool in the sshfp package by Paul
12 # Wouters and Christopher Olah from xelerance.com.
13 #
14 # Copyright Pieter Lexis (pieter.lexis@os3.nl)
15 #
16 # License: GNU GENERAL PUBLIC LICENSE Version 2 or later
17
18 import sys
19 import os
20 import socket
21 import unbound
22 import re
23 from M2Crypto import X509, SSL
24 from binascii import a2b_hex, b2a_hex
25 from hashlib import sha256, sha512
26 from ipaddr import IPv4Address, IPv6Address
27
28 check_ipv4=True
29 check_ipv6=True
30
31
32 def genTLSA(hostname, protocol, port, certificate, output='generic', usage=1, selector=0, mtype=1):
33         """This function generates a TLSARecord object using the data passed in the parameters,
34         it then validates the record and returns the RR as a string.
35         """
36         # check if valid vars were passed
37         if hostname[-1] != '.':
38                 hostname += '.'
39
40         certificate = loadCert(certificate)
41         if not certificate:
42                 raise Exception('Cannot load certificate from disk')
43
44         # Create the record without a certificate
45         if port == '*':
46                 record = TLSARecord(name='%s._%s.%s'%(port,protocol,hostname), usage=usage, selector=selector, mtype=mtype, cert ='')
47         else:
48                 record = TLSARecord(name='_%s._%s.%s'%(port,protocol,hostname), usage=usage, selector=selector, mtype=mtype, cert ='')
49         # Check if the record is valid
50         if record.isValid:
51                 if record.selector == 0:
52                         # Hash the Full certificate
53                         record.cert = getHash(certificate, record.mtype)
54                 else:
55                         # Hash only the SubjectPublicKeyInfo
56                         record.cert = getHash(certificate.get_pubkey(), record.mtype)
57
58         record.isValid(raiseException=True)
59
60         if output == 'generic':
61                 return record.getRecord(generic=True)
62         return record.getRecord()
63
64 def getA(hostname, secure=True):
65         if not check_ipv4: return []
66         """Gets a list of A records for hostname, returns a list of ARecords"""
67         try:
68                 records = getRecords(hostname, rrtype='A', secure=secure)
69         except InsecureLookupException, e:
70                 print str(e)
71                 sys.exit(1)
72         except DNSLookupError, e:
73                 print 'Unable to resolve %s: %s' % (hostname, str(e))
74                 sys.exit(1)
75         ret = []
76         for record in records:
77                 ret.append(ARecord(hostname, str(IPv4Address(int(b2a_hex(record),16)))))
78         return ret
79
80 def getAAAA(hostname, secure=True):
81         if not check_ipv6: return []
82         """Gets a list of A records for hostname, returns a list of AAAARecords"""
83         try:
84                 records = getRecords(hostname, rrtype='AAAA', secure=secure)
85         except InsecureLookupException, e:
86                 print str(e)
87                 sys.exit(1)
88         except DNSLookupError, e:
89                 print 'Unable to resolve %s: %s' % (hostname, str(e))
90                 sys.exit(1)
91         ret = []
92         for record in records:
93                 ret.append(AAAARecord(hostname, str(IPv6Address(int(b2a_hex(record),16)))))
94         return ret
95
96 def getVerificationErrorReason(num):
97         """This function returns the name of the X509 Error based on int(num)
98         """
99         # These were taken from the M2Crypto.m2 code
100         return {
101 50: "X509_V_ERR_APPLICATION_VERIFICATION",
102 22: "X509_V_ERR_CERT_CHAIN_TOO_LONG",
103 10: "X509_V_ERR_CERT_HAS_EXPIRED",
104 9:  "X509_V_ERR_CERT_NOT_YET_VALID",
105 28: "X509_V_ERR_CERT_REJECTED",
106 23: "X509_V_ERR_CERT_REVOKED",
107 7:  "X509_V_ERR_CERT_SIGNATURE_FAILURE",
108 27: "X509_V_ERR_CERT_UNTRUSTED",
109 12: "X509_V_ERR_CRL_HAS_EXPIRED",
110 11: "X509_V_ERR_CRL_NOT_YET_VALID",
111 8:  "X509_V_ERR_CRL_SIGNATURE_FAILURE",
112 18: "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT",
113 14: "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD",
114 13: "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD",
115 15: "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD",
116 16: "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD",
117 24: "X509_V_ERR_INVALID_CA",
118 26: "X509_V_ERR_INVALID_PURPOSE",
119 17: "X509_V_ERR_OUT_OF_MEM",
120 25: "X509_V_ERR_PATH_LENGTH_EXCEEDED",
121 19: "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN",
122 6:  "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
123 4:  "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE",
124 5:  "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE",
125 3:  "X509_V_ERR_UNABLE_TO_GET_CRL",
126 2:  "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT",
127 20: "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
128 21: "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE",
129 0:  "X509_V_OK"}[int(num)]
130
131 def getRecords(hostname, rrtype='A', secure=True):
132         """Do a lookup of a name and a rrtype, returns a list of binary coded strings. Only queries for rr_class IN."""
133         global resolvconf
134         ctx = unbound.ub_ctx()
135         ctx.add_ta_file('root.key')
136         ctx.set_option("dlv-anchor-file:", "dlv.isc.org.key")
137         # Use the local cache
138         if resolvconf and os.path.isfile(resolvconf):
139                 ctx.resolvconf(resolvconf)
140
141         if type(rrtype) == str:
142                 if 'RR_TYPE_' + rrtype in dir(unbound):
143                         rrtype = getattr(unbound, 'RR_TYPE_' + rrtype)
144                 else:
145                         raise Exception('Error: unknown RR TYPE: %s.' % rrtype)
146         elif type(rrtype) != int:
147                 raise Exception('Error: rrtype in wrong format, neither int nor str.')
148
149         status, result = ctx.resolve(hostname, rrtype=rrtype)
150         if status == 0 and result.havedata:
151                 if not result.secure:
152                         if secure:
153                                 # The data is insecure and a secure lookup was requested
154                                 raise InsecureLookupException('Error: query data not secure and secure data requested, unable to continue')
155                         else:
156                                 print >> sys.stderr, 'Warning: query data is not secure.'
157                 # If we are here the data was either secure or insecure data is accepted
158                 return result.data.raw
159         else:
160                 raise DNSLookupError('Unsuccesful lookup or no data returned for rrtype %s.' % rrtype)
161
162 def getHash(certificate, mtype):
163         """Hashes the certificate based on the mtype.
164         The certificate should be an M2Crypto.X509.X509 object (or the result of the get_pubkey() function on said object)
165         """
166         certificate = certificate.as_der()
167         if mtype == 0:
168                 return b2a_hex(certificate)
169         elif mtype == 1:
170                 return sha256(certificate).hexdigest()
171         elif mtype == 2:
172                 return sha512(certificate).hexdigest()
173         else:
174                 raise Exception('mtype should be 0,1,2')
175
176 def getTLSA(hostname, port=443, protocol='tcp', secure=True):
177         """
178         This function tries to do a secure lookup of the TLSA record.
179         At the moment it requests the TYPE52 record and parses it into a 'valid' TLSA record
180         It returns a list of TLSARecord objects
181         """
182         if hostname[-1] != '.':
183                 hostname += '.'
184
185         if not protocol.lower() in ['tcp', 'udp', 'sctp']:
186                 raise Exception('Error: unknown protocol: %s. Should be one of tcp, udp or sctp' % protocol)
187         try:
188                 if port == '*':
189                         records = getRecords('*._%s.%s' % (protocol.lower(), hostname), rrtype=52, secure=secure)
190                 else:
191                         records = getRecords('_%s._%s.%s' % (port, protocol.lower(), hostname), rrtype=52, secure=secure)
192         except InsecureLookupException, e:
193                 print str(e)
194                 sys.exit(1)
195         except DNSLookupError, e:
196                 print 'Unable to resolve %s: %s' % (hostname, str(e))
197                 sys.exit(1)
198         ret = []
199         for record in records:
200                 hexdata = b2a_hex(record)
201                 if port == '*':
202                         ret.append(TLSARecord('*._%s.%s' % (protocol.lower(), hostname), int(hexdata[0:2],16), int(hexdata[2:4],16), int(hexdata[4:6],16), hexdata[6:]))
203                 else:
204                         ret.append(TLSARecord('_%s._%s.%s' % (port, protocol.lower(), hostname), int(hexdata[0:2],16), int(hexdata[2:4],16), int(hexdata[4:6],16), hexdata[6:]))
205         return ret
206
207 def loadCert(certificate):
208         """Returns an M2Crypto.X509.X509 object"""
209         if isinstance(certificate, X509.X509):
210                 # nothing to be done :-)
211                 return certificate
212         try:
213                 # Maybe we were passed a path
214                 return X509.load_cert(certificate)
215         except:
216                 # Can't load the cert
217                 raise Exception('Unable to load certificate %s.' % certificate)
218
219 def verifyCertMatch(record, cert):
220         """
221         Verify the certificate with the record.
222         record should be a TLSARecord and cert should be a M2Crypto.X509.X509
223         """
224         if not isinstance(cert, X509.X509):
225                 return
226         if not isinstance(record, TLSARecord):
227                 return
228
229         if record.selector == 1:
230                 certhash = getHash(cert.get_pubkey(), record.mtype)
231         else:
232                 certhash = getHash(cert, record.mtype)
233
234         if not certhash:
235                 return
236
237         if certhash == record.cert:
238                 return True
239         else:
240                 return False
241
242 def verifyCertNameWithHostName(cert, hostname, with_msg=False):
243         """Verify the name on the certificate with a hostname, we need this because we get the cert based on IP address and thusly cannot rely on M2Crypto to verify this"""
244         if not isinstance(cert, X509.X509):
245                 return
246         if not isinstance(hostname, str):
247                 return
248
249         if hostname[-1] == '.':
250                 hostname = hostname[0:-1]
251
252         # Ugly string comparison to see if the name on the ee-cert matches with the name provided on the commandline
253         try:
254                 altnames_on_cert = cert.get_ext('subjectAltName').get_value()
255         except:
256                 altnames_on_cert = ''
257         if hostname in (str(cert.get_subject()) + altnames_on_cert):
258                 return True
259         else:
260                 if with_msg:
261                         print 'WARNING: Name on the certificate (Subject: %s, SubjectAltName: %s) doesn\'t match requested hostname (%s).' % (str(cert.get_subject()), altnames_on_cert, hostname)
262                 return False
263
264 class TLSARecord:
265         """When instanciated, this class contains all the fields of a TLSA record.
266         """
267         def __init__(self, name, usage, selector, mtype, cert):
268                 """name is the name of the RR in the format: /^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([a-z0-9]*\.){2,}$/
269                 usage, selector and mtype should be an integer
270                 cert should be a hexidecimal string representing the certificate to be matched field
271                 """
272                 try:
273                         self.rrtype = 52    # TLSA per https://www.iana.org/assignments/dns-parameters
274                         self.rrclass = 1    # IN
275                         self.name = str(name)
276                         self.usage = int(usage)
277                         self.selector = int(selector)
278                         self.mtype = int(mtype)
279                         self.cert = str(cert)
280                 except:
281                         raise Exception('Invalid value passed, unable to create a TLSARecord')
282
283         def getRecord(self, generic=False):
284                 """Returns the RR string of this TLSARecord, either in rfc (default) or generic format"""
285                 if generic:
286                         return '%s IN TYPE52 \# %s %s%s%s%s' % (self.name, (len(self.cert)/2)+3 , self._toHex(self.usage), self._toHex(self.selector), self._toHex(self.mtype), self.cert)
287                 return '%s IN TLSA %s %s %s %s' % (self.name, self.usage, self.selector, self.mtype, self.cert)
288
289         def _toHex(self, val):
290                 """Helper function to create hex strings from integers"""
291                 return "%0.2x" % val
292
293         def isValid(self, raiseException=False):
294                 """Check whether all fields in the TLSA record are conforming to the spec and check if the port, protocol and name are good"""
295                 err =[]
296                 try:
297                         if not 1 <= int(self.getPort()) <= 65535:
298                                 err.append('Port %s not within correct range (1 <= port <= 65535)' % self.getPort())
299                 except:
300                         if self.getPort() != '*':
301                                 err.append('Port %s not a number' % self.getPort())
302                 if not self.usage in [0,1,2,3]:
303                         err.append('Usage: invalid (%s is not one of 0, 1, 2 or 3)' % self.usage)
304                 if not self.selector in [0,1]:
305                         err.append('Selector: invalid (%s is not one of 0 or 1)' % self.selector)
306                 if not self.mtype in [0,1,2]:
307                         err.append('Matching Type: invalid (%s is not one of 0, 1 or 2)' % self.mtype)
308                 if not self.isNameValid():
309                         err.append('Name (%s) is not in the correct format: _portnumber._transportprotocol.hostname.dom.' % self.name)
310                 # A certificate length of 0 is accepted
311                 if self.mtype in [1,2] and len(self.cert) != 0:
312                         if not len(self.cert) == {1:64,2:128}[self.mtype]:
313                                 err.append('Certificate for Association: invalid (Hash length does not match hash-type in Matching Type(%s))' % {1:'SHA-256',2:'SHA-512'}[self.mtype])
314                 if len(err) != 0:
315                         if not raiseException:
316                                 return False
317                         else:
318                                 msg = 'The TLSA record is invalid.'
319                                 for error in err:
320                                         msg += '\n\t%s' % error
321                                 raise RecordValidityException(msg)
322                 else:
323                         return True
324
325         def isNameValid(self):
326                 """Check if the name if in the correct format"""
327                 if not re.match('^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([-a-z0-9]*\.){2,}$', self.name):
328                         return False
329                 return True
330
331         def getProtocol(self):
332                 """Returns the protocol based on the name"""
333                 return re.split('\.', self.name)[1][1:]
334
335         def getPort(self):
336                 """Returns the port based on the name"""
337                 if re.split('\.', self.name)[0][0] == '*':
338                         return '*'
339                 else:
340                         return re.split('\.', self.name)[0][1:]
341
342 class ARecord:
343         """An object representing an A Record (IPv4 address)"""
344         def __init__(self, hostname, address):
345                 self.rrtype = 1
346                 self.hostname = hostname
347                 self.address = address
348
349         def __str__(self):
350                 return self.address
351
352         def isValid(self):
353                 try:
354                         IPv4Address(self.address)
355                         return True
356                 except:
357                         return False
358
359 class AAAARecord:
360         """An object representing an AAAA Record (IPv6 address)"""
361         def __init__(self, hostname, address):
362                 self.rrtype = 28
363                 self.hostname = hostname
364                 self.address = address
365
366         def __str__(self):
367                 return self.address
368
369         def isValid(self):
370                 try:
371                         IPv6Address(self.address)
372                         return True
373                 except:
374                         return False
375
376 # Exceptions
377 class RecordValidityException(Exception):
378         pass
379
380 class InsecureLookupException(Exception):
381         pass
382
383 class DNSLookupError(Exception):
384         pass
385
386 if __name__ == '__main__':
387         import argparse
388         # create the parser
389         parser = argparse.ArgumentParser(description='Create and verify DANE records.', epilog='This tool has a few limitations')
390
391         subparsers = parser.add_subparsers(title='Functions', help='Available functions, see %(prog)s function -h for function-specific help')
392         parser_verify = subparsers.add_parser('verify', help='Verify a TLSA record, exit 0 when all TLSA records are matched, exit 2 when a record does not match the received certificate, exit 1 on error.', epilog='Caveat: For TLSA validation, this program chases through the certificate chain offered by the server, not its local certificates.')
393         parser_verify.set_defaults(function='verify')
394         parser_create = subparsers.add_parser('create', help='Create a TLSA record')
395         parser_create.set_defaults(function='create')
396
397         parser.add_argument('-4', dest='ipv4', action='store_true',help='use ipv4 networking only')
398         parser.add_argument('-6', dest='ipv6', action='store_true',help='use ipv6 networking only')
399         parser.add_argument('--insecure', action='store_true', default=False, help='Allow use of non-dnssec secured answers')
400         parser.add_argument('--resolvconf', metavar='/PATH/TO/RESOLV.CONF', action='store', default='', help='Use a recursive resolver from resolv.conf')
401         parser.add_argument('-v', '--version', action='version', version='%(prog)s v0.2', help='show version and exit')
402         parser.add_argument('host', metavar="hostname")
403
404         parser_verify.add_argument('--port', '-p', action='store', default='443', help='The port, or \'*\' where running TLS is located (default: %(default)s).')
405         parser_verify.add_argument('--protocol', action='store', choices=['tcp','udp','sctp'], default='tcp', help='The protocol the TLS service is using (default: %(default)s).')
406         parser_verify.add_argument('--only-rr', '-o', action='store_true', help='Only verify that the TLSA resource record is correct (do not check certificate)')
407         parser_verify.add_argument('--ca-cert', metavar='/PATH/TO/CERTSTORE', action='store', default = '/etc/ssl/certs/', help='Path to a CA certificate or a directory containing the certificates (default: %(default)s)')
408         parser_verify.add_argument('--quiet', '-q', action='store_true', help='Only print the result of the validation')
409
410         parser_create.add_argument('--port', '-p', action='store', type=int, default=443, help='The port where running TLS is located (default: %(default)s).')
411         parser_create.add_argument('--protocol', action='store', choices=['tcp','udp','sctp'], default='tcp', help='The protocol the TLS service is using (default: %(default)s).')
412         parser_create.add_argument('--certificate', '-c', help='The certificate used for the host. If certificate is empty, the certificate will be downloaded from the server')
413         parser_create.add_argument('--output', '-o', action='store', default='generic', choices=['generic','rfc','both'], help='The type of output. Generic (RFC 3597, TYPE52), RFC (TLSA) or both (default: %(default)s).')
414
415         # Usage of the certificate
416         parser_create.add_argument('--usage', '-u', action='store', type=int, default=1, choices=[0,1,2,3], help='The Usage of the Certificate for Association. \'0\' for CA, \'1\' for End Entity, \'2\' for trust-anchor, \'3\' for ONLY End-Entity match (default: %(default)s).')
417         parser_create.add_argument('--selector', '-s', action='store', type=int, default=0, choices=[0,1], help='The Selector for the Certificate for Association. \'0\' for Full Certificate, \'1\' for SubjectPublicKeyInfo (default: %(default)s).')
418         parser_create.add_argument('--mtype', '-m', action='store', type=int, default=1, choices=[0,1,2], help='The Matching Type of the Certificate for Association. \'0\' for Exact match, \'1\' for SHA-256 hash, \'2\' for SHA-512 (default: %(default)s).')
419
420         args = parser.parse_args()
421         import pprint
422         pprint.pprint(args)
423         if args.ipv4 == True and args.ipv6 == True: 
424                 print "Cannot have only ipv4 and only ipv6 at the same time"
425                 sys.exit()
426         elif args.ipv4 == True:
427                 check_ipv6 = False
428         elif args.ipv6 == True:
429                 check_ipv4 = False
430
431         if args.host[-1] != '.':
432                 args.host += '.'
433
434         global resolvconf
435         if args.resolvconf:
436                 if os.path.isfile(args.resolvconf):
437                         resolvconf = args.resolvconf
438                 else:
439                         print >> sys.stdout, '%s is not a file. Unable to use it as resolv.conf' % args.resolvconf
440                         sys.exit(1)
441         else:
442                 resolvconf = None
443
444         # not operations are fun!
445         secure = not args.insecure
446
447         if args.function == 'verify':
448                 records = getTLSA(args.host, args.port, args.protocol, secure)
449                 if len(records) == 0:
450                         sys.exit(1)
451
452                 for record in records:
453                         pre_exit = 0
454                         # First, check if the first three fields have correct values.
455                         if not args.quiet:
456                                 print 'Received the following record for name %s:' % record.name
457                                 print '\tUsage:\t\t\t\t%d (%s)' % (record.usage, {0:'CA Constraint', 1:'End-Entity Constraint + chain to CA', 2:'Trust Anchor', 3:'End-Entity'}.get(record.usage, 'INVALID'))
458                                 print '\tSelector:\t\t\t%d (%s)' % (record.selector, {0:'Certificate', 1:'SubjectPublicKeyInfo'}.get(record.selector, 'INVALID'))
459                                 print '\tMatching Type:\t\t\t%d (%s)' % (record.mtype, {0:'Full Certificate', 1:'SHA-256', 2:'SHA-512'}.get(record.mtype, 'INVALID'))
460                                 print '\tCertificate for Association:\t%s' % record.cert
461
462                         try:
463                                 record.isValid(raiseException=True)
464                         except RecordValidityException, e:
465                                 print >> sys.stderr, 'Error: %s' % str(e)
466                                 continue
467                         else:
468                                 if not args.quiet:
469                                         print 'This record is valid (well-formed).'
470
471                         if args.only_rr:
472                                 # Go to the next record
473                                 continue
474
475                         # When we are here, The user also wants to verify the certificates with the record
476                         if args.protocol != 'tcp':
477                                 print >> sys.stderr, 'Only SSL over TCP is supported (sorry)'
478                                 sys.exit(0)
479
480                         if not args.quiet:
481                                 print 'Attempting to verify the record with the TLS service...'
482                         if check_ipv4 and check_ipv6:
483                                 addresses = getA(args.host, secure=secure) + getAAAA(args.host, secure=secure)
484                         elif check_ipv4:
485                                 addresses = getA(args.host, secure=secure) 
486                         else:
487                                 addresses = getAAAA(args.host, secure=secure)
488                                 
489                         for address in addresses:
490                                 if not args.quiet:
491                                         print 'Got the following IP: %s' % str(address)
492                                 # We do the certificate handling here, as M2Crypto keeps segfaulting when we do it in a method
493                                 ctx = SSL.Context()
494                                 if os.path.isfile(args.ca_cert):
495                                         if ctx.load_verify_locations(cafile=args.ca_cert) != 1: raise Exception('No CA cert')
496                                 elif os.path.exists(args.ca_cert):
497                                         if ctx.load_verify_locations(capath=args.ca_cert) != 1: raise Exception('No CA certs')
498                                 else:
499                                         print >> sys.stderr, '%s is neither a file nor a directory, unable to continue' % args.ca_cert
500                                         sys.exit(1)
501                                 # Don't error when the verification fails in the SSL handshake
502                                 ctx.set_verify(SSL.verify_none, depth=9)
503                                 if  check_ipv6 and isinstance(address, AAAARecord):
504                                         sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
505                                         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
506                                 elif  check_ipv4 and isinstance(address, ARecord):
507                                         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
508                                         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
509                                 else:
510                                         sock = None
511                                 connection = SSL.Connection(ctx, sock=sock)
512                                 try:
513                                         connection.connect((str(address), int(args.port)))
514                                 except SSL.Checker.WrongHost, e:
515                                         # The name on the remote cert doesn't match the hostname because we connect on IP, not hostname (as we want secure lookup)
516                                         pass
517                                 except socket.error, e:
518                                         print 'Cannot connect to %s: %s' % (address, str(e))
519                                         continue
520                                 chain = connection.get_peer_cert_chain()
521                                 verify_result = connection.get_verify_result()
522
523                                 # Good, now let's verify
524                                 if not verifyCertNameWithHostName(cert=chain[0], hostname=str(args.host), with_msg=True):
525                                         # The name on the cert doesn't match the hostname... we don't verify the TLSA record
526                                         print 'Not checking the TLSA record.'
527                                         continue
528                                 if record.usage == 1: # End-host cert
529                                         cert = chain[0]
530                                         if verifyCertMatch(record, cert):
531                                                 if verify_result == 0: # The cert chains to a valid CA cert according to the system-certificates
532                                                         print 'SUCCESS (Usage 1): Certificate offered by the server matches the one mentioned in the TLSA record and chains to a valid CA certificate'
533                                                 else:
534                                                         print 'FAIL (Usage 1): Certificate offered by the server matches the one mentioned in the TLSA record but the following error was raised during PKIX validation: %s' % getVerificationErrorReason(verify_result)
535                                                         if pre_exit == 0: pre_exit = 2
536                                                 if not args.quiet: print 'The matched certificate has Subject: %s' % cert.get_subject()
537                                         else:
538                                                 print 'FAIL: Certificate offered by the server does not match the TLSA record'
539                                                 if pre_exit == 0: pre_exit = 2
540
541                                 elif record.usage == 0: # CA constraint
542                                         matched = False
543                                         # Remove the first (= End-Entity cert) from the chain
544                                         chain = chain[1:]
545                                         for cert in chain:
546                                                 if verifyCertMatch(record, cert):
547                                                         matched = True
548                                                         continue
549                                         if matched:
550                                                 if cert.check_ca():
551                                                         if verify_result == 0:
552                                                                 print 'SUCCESS (Usage 0): A certificate in the certificate chain offered by the server matches the one mentioned in the TLSA record and is a CA certificate'
553                                                         else:
554                                                                 print 'FAIL (Usage 0): A certificate in the certificate chain offered by the server matches the one mentioned in the TLSA record and is a CA certificate, but the following error was raised during PKIX validation:' % getVerificationErrorReason(verify_result)
555                                                                 if pre_exit == 0: pre_exit = 2
556                                                 else:
557                                                         print 'FAIL (Usage 0): A certificate in the certificate chain offered by the server matches the one mentioned in the TLSA record but is not a CA certificate'
558                                                         if pre_exit == 0: pre_exit = 2
559                                                 if not args.quiet: print 'The matched certificate has Subject: %s' % cert.get_subject()
560                                         else:
561                                                 print 'FAIL (Usage 0): No certificate in the certificate chain offered by the server matches the TLSA record'
562                                                 if pre_exit == 0: pre_exit = 2
563
564                                 elif record.usage == 2: # Usage 2, use the cert in the record as trust anchor
565                                         #FIXME: doesnt comply to the spec
566                                         matched = False
567                                         previous_issuer = None
568                                         for cert in chain:
569                                                 if previous_issuer:
570                                                         if not str(previous_issuer) == str(cert.get_subject()): # The chain cannot be valid
571                                                                 print "FAIL: Certificates don't chain"
572                                                                 break
573                                                         previous_issuer = cert.get_issuer()
574                                                 if verifyCertMatch(record, cert):
575                                                         matched = True
576                                                         continue
577                                         if matched:
578                                                 print 'SUCCESS (usage 2): A certificate in the certificate chain (including the end-entity certificate) offered by the server matches the TLSA record'
579                                                 if not args.quiet: print 'The matched certificate has Subject: %s' % cert.get_subject()
580                                         else:
581                                                 print 'FAIL (usage 2): No certificate in the certificate chain (including the end-entity certificate) offered by the server matches the TLSA record'
582                                                 if pre_exit == 0: pre_exit = 2
583
584                                 elif record.usage == 3: # EE cert MUST match
585                                         if verifyCertMatch(record,chain[0]):
586                                                 print 'SUCCESS (usage 3): The certificate offered by the server matches the TLSA record'
587                                                 if not args.quiet: print 'The matched certificate has Subject: %s' % chain[0].get_subject()
588                                         else:
589                                                 print 'FAIL (usage 3): The certificate offered by the server does not match the TLSA record'
590                                                 if pre_exit == 0: pre_exit = 2
591
592                                 # Cleanup, just in case
593                                 connection.clear()
594                                 connection.close()
595                                 ctx.close()
596
597                         # END for address in addresses
598                 # END for record in records
599                 sys.exit(pre_exit)
600         # END if args.verify
601
602         else: # we want to create
603                 cert = None
604                 if not args.certificate:
605                         if args.protocol != 'tcp':
606                                 print >> sys.stderr, 'Only SSL over TCP is supported (sorry)'
607                                 sys.exit(1)
608
609                         print 'No certificate specified on the commandline, attempting to retrieve it from the server %s' % (args.host)
610                         connection_port = args.port
611                         if args.port == '*':
612                                 sys.stdout.write('The port specified on the commandline is *, please specify the port of the TLS service on %s (443): ' % args.host)
613                                 input_ok = False
614                                 while not input_ok:
615                                         user_input = raw_input()
616                                         if user_input == '':
617                                                 connection_port = 443
618                                                 break
619                                         try:
620                                                 if 1 <= int(user_input) <= 65535:
621                                                         connection_port = user_input
622                                                         input_ok = True
623                                         except:
624                                                 sys.stdout.write('Port %s not numerical or within correct range (1 <= port <= 65535), try again (hit enter for default 443): ' % user_input)
625                         # Get the address records for the host
626                         try:
627                                 if check_ipv4 and check_ipv6:
628                                         addresses = getA(args.host, secure=secure) + getAAAA(args.host, secure=secure)
629                                 elif check_ipv4:
630                                         addresses = getA(args.host, secure=secure) 
631                                 else:
632                                         addresses = getAAAA(args.host, secure=secure)
633
634                         except InsecureLookupException, e:
635                                 print >> sys.stderr, str(e)
636                                 sys.exit(1)
637
638                         for address in addresses:
639                                 print 'Attempting to get certificate from %s' % str(address)
640                                 # We do the certificate handling here, as M2Crypto keeps segfaulting when try to do stuff with the cert if we don't
641                                 ctx = SSL.Context()
642                                 ctx.set_verify(SSL.verify_none, depth=9)
643                                 if check_ipv6 and isinstance(address, AAAARecord):
644                                         sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
645                                         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
646                                 if check_ipv4 and isinstance(address, ARecord):
647                                         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
648                                         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
649                                 else:
650                                         sock = None
651                                 connection = SSL.Connection(ctx, sock=sock)
652                                 try:
653                                         connection.connect((str(address), int(connection_port)))
654                                 except SSL.Checker.WrongHost:
655                                         pass
656                                 except socket.error, e:
657                                         print 'Cannot connect to %s: %s' % (address, str(e))
658                                         continue
659
660                                 chain = connection.get_peer_cert_chain()
661                                 for chaincert in chain:
662                                         if int(args.usage) == 1 or int(args.usage) == 3:
663                                                 # The first cert is the end-entity cert
664                                                 print 'Got a certificate with Subject: %s' % chaincert.get_subject()
665                                                 cert = chaincert
666                                                 break
667                                         else:
668                                                 if (int(args.usage) == 0 and chaincert.check_ca()) or int(args.usage) == 2:
669                                                         sys.stdout.write('Got a certificate with the following Subject:\n\t%s\nUse this as certificate to match? [y/N] ' % chaincert.get_subject())
670                                                         input_ok = False
671                                                         while not input_ok:
672                                                                 user_input = raw_input()
673                                                                 if user_input in ['','n','N']:
674                                                                         input_ok=True
675                                                                 elif user_input in ['y', 'Y']:
676                                                                         input_ok = True
677                                                                         cert = chaincert
678                                                                 else:
679                                                                         sys.stdout.write('Please answer Y or N')
680                                                 if cert:
681                                                         break
682
683                                 if cert: # Print the requested records based on the retrieved certificates
684                                         if args.output == 'both':
685                                                 print genTLSA(args.host, args.protocol, args.port, cert, 'draft', args.usage, args.selector, args.mtype)
686                                                 print genTLSA(args.host, args.protocol, args.port, cert, 'rfc', args.usage, args.selector, args.mtype)
687                                         else:
688                                                 print genTLSA(args.host, args.protocol, args.port, cert, args.output, args.usage, args.selector, args.mtype)
689
690                                 # Clear the cert from memory (to stop M2Crypto from segfaulting)
691                                 # And cleanup the connection and context
692                                 cert=None
693                                 connection.clear()
694                                 connection.close()
695                                 ctx.close()
696
697                 else: # Pass the path to the certificate to the genTLSA function
698                         if args.output == 'both':
699                                 print genTLSA(args.host, args.protocol, args.port, args.certificate, 'draft', args.usage, args.selector, args.mtype)
700                                 print genTLSA(args.host, args.protocol, args.port, args.certificate, 'rfc', args.usage, args.selector, args.mtype)
701                         else:
702                                 print genTLSA(args.host, args.protocol, args.port, args.certificate, args.output, args.usage, args.selector, args.mtype)