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).
9 # Usage is explained when running this program with --help
11 # This tool is loosly based on the dane tool in the sshfp package by Paul
12 # Wouters and Christopher Olah from xelerance.com.
14 # Copyright Pieter Lexis (pieter.lexis@os3.nl)
16 # License: GNU GENERAL PUBLIC LICENSE Version 2 or later
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
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.
36 # check if valid vars were passed
37 if hostname[-1] != '.':
40 certificate = loadCert(certificate)
42 raise Exception('Cannot load certificate from disk')
44 # Create the record without a certificate
46 record = TLSARecord(name='%s._%s.%s'%(port,protocol,hostname), usage=usage, selector=selector, mtype=mtype, cert ='')
48 record = TLSARecord(name='_%s._%s.%s'%(port,protocol,hostname), usage=usage, selector=selector, mtype=mtype, cert ='')
49 # Check if the record is valid
51 if record.selector == 0:
52 # Hash the Full certificate
53 record.cert = getHash(certificate, record.mtype)
55 # Hash only the SubjectPublicKeyInfo
56 record.cert = getHash(certificate.get_pubkey(), record.mtype)
58 record.isValid(raiseException=True)
60 if output == 'generic':
61 return record.getRecord(generic=True)
62 return record.getRecord()
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"""
68 records = getRecords(hostname, rrtype='A', secure=secure)
69 except InsecureLookupException, e:
72 except DNSLookupError, e:
73 print 'Unable to resolve %s: %s' % (hostname, str(e))
76 for record in records:
77 ret.append(ARecord(hostname, str(IPv4Address(int(b2a_hex(record),16)))))
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"""
84 records = getRecords(hostname, rrtype='AAAA', secure=secure)
85 except InsecureLookupException, e:
88 except DNSLookupError, e:
89 print 'Unable to resolve %s: %s' % (hostname, str(e))
92 for record in records:
93 ret.append(AAAARecord(hostname, str(IPv6Address(int(b2a_hex(record),16)))))
96 def getVerificationErrorReason(num):
97 """This function returns the name of the X509 Error based on int(num)
99 # These were taken from the M2Crypto.m2 code
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)]
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."""
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)
141 if type(rrtype) == str:
142 if 'RR_TYPE_' + rrtype in dir(unbound):
143 rrtype = getattr(unbound, 'RR_TYPE_' + rrtype)
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.')
149 status, result = ctx.resolve(hostname, rrtype=rrtype)
150 if status == 0 and result.havedata:
151 if not result.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')
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
160 raise DNSLookupError('Unsuccesful lookup or no data returned for rrtype %s.' % rrtype)
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)
166 certificate = certificate.as_der()
168 return b2a_hex(certificate)
170 return sha256(certificate).hexdigest()
172 return sha512(certificate).hexdigest()
174 raise Exception('mtype should be 0,1,2')
176 def getTLSA(hostname, port=443, protocol='tcp', secure=True):
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
182 if hostname[-1] != '.':
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)
189 records = getRecords('*._%s.%s' % (protocol.lower(), hostname), rrtype=52, secure=secure)
191 records = getRecords('_%s._%s.%s' % (port, protocol.lower(), hostname), rrtype=52, secure=secure)
192 except InsecureLookupException, e:
195 except DNSLookupError, e:
196 print 'Unable to resolve %s: %s' % (hostname, str(e))
199 for record in records:
200 hexdata = b2a_hex(record)
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:]))
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:]))
207 def loadCert(certificate):
208 """Returns an M2Crypto.X509.X509 object"""
209 if isinstance(certificate, X509.X509):
210 # nothing to be done :-)
213 # Maybe we were passed a path
214 return X509.load_cert(certificate)
216 # Can't load the cert
217 raise Exception('Unable to load certificate %s.' % certificate)
219 def verifyCertMatch(record, cert):
221 Verify the certificate with the record.
222 record should be a TLSARecord and cert should be a M2Crypto.X509.X509
224 if not isinstance(cert, X509.X509):
226 if not isinstance(record, TLSARecord):
229 if record.selector == 1:
230 certhash = getHash(cert.get_pubkey(), record.mtype)
232 certhash = getHash(cert, record.mtype)
237 if certhash == record.cert:
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):
246 if not isinstance(hostname, str):
249 if hostname[-1] == '.':
250 hostname = hostname[0:-1]
252 # Ugly string comparison to see if the name on the ee-cert matches with the name provided on the commandline
254 altnames_on_cert = cert.get_ext('subjectAltName').get_value()
256 altnames_on_cert = ''
257 if hostname in (str(cert.get_subject()) + altnames_on_cert):
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)
265 """When instanciated, this class contains all the fields of a TLSA record.
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
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)
281 raise Exception('Invalid value passed, unable to create a TLSARecord')
283 def getRecord(self, generic=False):
284 """Returns the RR string of this TLSARecord, either in rfc (default) or generic format"""
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)
289 def _toHex(self, val):
290 """Helper function to create hex strings from integers"""
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"""
297 if not 1 <= int(self.getPort()) <= 65535:
298 err.append('Port %s not within correct range (1 <= port <= 65535)' % self.getPort())
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])
315 if not raiseException:
318 msg = 'The TLSA record is invalid.'
320 msg += '\n\t%s' % error
321 raise RecordValidityException(msg)
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):
331 def getProtocol(self):
332 """Returns the protocol based on the name"""
333 return re.split('\.', self.name)[1][1:]
336 """Returns the port based on the name"""
337 if re.split('\.', self.name)[0][0] == '*':
340 return re.split('\.', self.name)[0][1:]
343 """An object representing an A Record (IPv4 address)"""
344 def __init__(self, hostname, address):
346 self.hostname = hostname
347 self.address = address
354 IPv4Address(self.address)
360 """An object representing an AAAA Record (IPv6 address)"""
361 def __init__(self, hostname, address):
363 self.hostname = hostname
364 self.address = address
371 IPv6Address(self.address)
377 class RecordValidityException(Exception):
380 class InsecureLookupException(Exception):
383 class DNSLookupError(Exception):
386 if __name__ == '__main__':
389 parser = argparse.ArgumentParser(description='Create and verify DANE records.', epilog='This tool has a few limitations')
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')
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")
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')
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).')
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).')
420 args = parser.parse_args()
423 if args.ipv4 == True and args.ipv6 == True:
424 print "Cannot have only ipv4 and only ipv6 at the same time"
426 elif args.ipv4 == True:
428 elif args.ipv6 == True:
431 if args.host[-1] != '.':
436 if os.path.isfile(args.resolvconf):
437 resolvconf = args.resolvconf
439 print >> sys.stdout, '%s is not a file. Unable to use it as resolv.conf' % args.resolvconf
444 # not operations are fun!
445 secure = not args.insecure
447 if args.function == 'verify':
448 records = getTLSA(args.host, args.port, args.protocol, secure)
449 if len(records) == 0:
452 for record in records:
454 # First, check if the first three fields have correct values.
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
463 record.isValid(raiseException=True)
464 except RecordValidityException, e:
465 print >> sys.stderr, 'Error: %s' % str(e)
469 print 'This record is valid (well-formed).'
472 # Go to the next record
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)'
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)
485 addresses = getA(args.host, secure=secure)
487 addresses = getAAAA(args.host, secure=secure)
489 for address in addresses:
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
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')
499 print >> sys.stderr, '%s is neither a file nor a directory, unable to continue' % args.ca_cert
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)
511 connection = SSL.Connection(ctx, sock=sock)
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)
517 except socket.error, e:
518 print 'Cannot connect to %s: %s' % (address, str(e))
520 chain = connection.get_peer_cert_chain()
521 verify_result = connection.get_verify_result()
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.'
528 if record.usage == 1: # End-host cert
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'
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()
538 print 'FAIL: Certificate offered by the server does not match the TLSA record'
539 if pre_exit == 0: pre_exit = 2
541 elif record.usage == 0: # CA constraint
543 # Remove the first (= End-Entity cert) from the chain
546 if verifyCertMatch(record, cert):
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'
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
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()
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
564 elif record.usage == 2: # Usage 2, use the cert in the record as trust anchor
565 #FIXME: doesnt comply to the spec
567 previous_issuer = None
570 if not str(previous_issuer) == str(cert.get_subject()): # The chain cannot be valid
571 print "FAIL: Certificates don't chain"
573 previous_issuer = cert.get_issuer()
574 if verifyCertMatch(record, cert):
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()
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
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()
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
592 # Cleanup, just in case
597 # END for address in addresses
598 # END for record in records
602 else: # we want to create
604 if not args.certificate:
605 if args.protocol != 'tcp':
606 print >> sys.stderr, 'Only SSL over TCP is supported (sorry)'
609 print 'No certificate specified on the commandline, attempting to retrieve it from the server %s' % (args.host)
610 connection_port = 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)
615 user_input = raw_input()
617 connection_port = 443
620 if 1 <= int(user_input) <= 65535:
621 connection_port = user_input
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
627 if check_ipv4 and check_ipv6:
628 addresses = getA(args.host, secure=secure) + getAAAA(args.host, secure=secure)
630 addresses = getA(args.host, secure=secure)
632 addresses = getAAAA(args.host, secure=secure)
634 except InsecureLookupException, e:
635 print >> sys.stderr, str(e)
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
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)
651 connection = SSL.Connection(ctx, sock=sock)
653 connection.connect((str(address), int(connection_port)))
654 except SSL.Checker.WrongHost:
656 except socket.error, e:
657 print 'Cannot connect to %s: %s' % (address, str(e))
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()
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())
672 user_input = raw_input()
673 if user_input in ['','n','N']:
675 elif user_input in ['y', 'Y']:
679 sys.stdout.write('Please answer Y or N')
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)
688 print genTLSA(args.host, args.protocol, args.port, cert, args.output, args.usage, args.selector, args.mtype)
690 # Clear the cert from memory (to stop M2Crypto from segfaulting)
691 # And cleanup the connection and context
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)
702 print genTLSA(args.host, args.protocol, args.port, args.certificate, args.output, args.usage, args.selector, args.mtype)