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