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
22 from M2Crypto import X509, SSL
23 from binascii import a2b_hex, b2a_hex
24 from hashlib import sha256, sha512
25 from ipaddr import IPv4Address, IPv6Address
28 def genTLSA(hostname, protocol, port, certificate, output='draft', usage=1, selector=0, mtype=1):
29 """This function generates a TLSARecord object using the data passed in the parameters,
30 it then validates the record and returns the RR as a string.
32 # check if valid vars were passed
33 if hostname[-1] != '.':
36 certificate = loadCert(certificate)
38 raise Exception('Cannot load certificate from disk')
40 # Create the record without a certificate
42 record = TLSARecord(name='%s._%s.%s'%(port,protocol,hostname), usage=usage, selector=selector, mtype=mtype, cert ='')
44 record = TLSARecord(name='_%s._%s.%s'%(port,protocol,hostname), usage=usage, selector=selector, mtype=mtype, cert ='')
45 # Check if the record is valid
47 if record.selector == 0:
48 # Hash the Full certificate
49 record.cert = getHash(certificate, record.mtype)
51 # Hash only the SubjectPublicKeyInfo
52 record.cert = getHash(certificate.get_pubkey(), record.mtype)
54 record.isValid(raiseException=True)
57 return record.getRecord(draft=True)
58 return record.getRecord()
60 def getA(hostname, secure=True):
61 """Gets a list of A records for hostname, returns a list of ARecords"""
63 records = getRecords(hostname, rrtype='A', secure=secure)
64 except InsecureLookupException, e:
67 except DNSLookupError, e:
68 print 'Unable to resolve %s: %s' % (hostname, str(e))
71 for record in records:
72 ret.append(ARecord(hostname, str(IPv4Address(int(b2a_hex(record),16)))))
75 def getAAAA(hostname, secure=True):
76 """Gets a list of A records for hostname, returns a list of AAAARecords"""
78 records = getRecords(hostname, rrtype='AAAA', secure=secure)
79 except InsecureLookupException, e:
82 except DNSLookupError, e:
83 print 'Unable to resolve %s: %s' % (hostname, str(e))
86 for record in records:
87 ret.append(AAAARecord(hostname, str(IPv6Address(int(b2a_hex(record),16)))))
90 def getVerificationErrorReason(num):
91 """This function returns the name of the X509 Error based on int(num)
93 # These were taken from the M2Crypto.m2 code
95 50: "X509_V_ERR_APPLICATION_VERIFICATION",
96 22: "X509_V_ERR_CERT_CHAIN_TOO_LONG",
97 10: "X509_V_ERR_CERT_HAS_EXPIRED",
98 9: "X509_V_ERR_CERT_NOT_YET_VALID",
99 28: "X509_V_ERR_CERT_REJECTED",
100 23: "X509_V_ERR_CERT_REVOKED",
101 7: "X509_V_ERR_CERT_SIGNATURE_FAILURE",
102 27: "X509_V_ERR_CERT_UNTRUSTED",
103 12: "X509_V_ERR_CRL_HAS_EXPIRED",
104 11: "X509_V_ERR_CRL_NOT_YET_VALID",
105 8: "X509_V_ERR_CRL_SIGNATURE_FAILURE",
106 18: "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT",
107 14: "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD",
108 13: "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD",
109 15: "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD",
110 16: "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD",
111 24: "X509_V_ERR_INVALID_CA",
112 26: "X509_V_ERR_INVALID_PURPOSE",
113 17: "X509_V_ERR_OUT_OF_MEM",
114 25: "X509_V_ERR_PATH_LENGTH_EXCEEDED",
115 19: "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN",
116 6: "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
117 4: "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE",
118 5: "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE",
119 3: "X509_V_ERR_UNABLE_TO_GET_CRL",
120 2: "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT",
121 20: "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
122 21: "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE",
123 0: "X509_V_OK"}[int(num)]
125 def getRecords(hostname, rrtype='A', secure=True):
126 """Do a lookup of a name and a rrtype, returns a list of binary coded strings. Only queries for rr_class IN."""
128 ctx = unbound.ub_ctx()
129 ctx.add_ta_file('root.key')
130 ctx.set_option("dlv-anchor-file:", "dlv.isc.org.key")
131 # Use the local cache
132 if resolvconf and os.path.isfile(resolvconf):
133 ctx.resolvconf(resolvconf)
135 if type(rrtype) == str:
136 if 'RR_TYPE_' + rrtype in dir(unbound):
137 rrtype = getattr(unbound, 'RR_TYPE_' + rrtype)
139 raise Exception('Error: unknown RR TYPE: %s.' % rrtype)
140 elif type(rrtype) != int:
141 raise Exception('Error: rrtype in wrong format, neither int nor str.')
143 status, result = ctx.resolve(hostname, rrtype=rrtype)
144 if status == 0 and result.havedata:
145 if not result.secure:
147 # The data is insecure and a secure lookup was requested
148 raise InsecureLookupException('Error: query data not secure and secure data requested, unable to continue')
150 print >> sys.stderr, 'Warning: query data is not secure.'
151 # If we are here the data was either secure or insecure data is accepted
152 return result.data.raw
154 raise DNSLookupError('Unsuccesful lookup or no data returned for rrtype %s.' % rrtype)
156 def getHash(certificate, mtype):
157 """Hashes the certificate based on the mtype.
158 The certificate should be an M2Crypto.X509.X509 object (or the result of the get_pubkey() function on said object)
160 certificate = certificate.as_der()
162 return b2a_hex(certificate)
164 return sha256(certificate).hexdigest()
166 return sha512(certificate).hexdigest()
168 raise Exception('mtype should be 0,1,2')
170 def getTLSA(hostname, port=443, protocol='tcp', secure=True):
172 This function tries to do a secure lookup of the TLSA record.
173 At the moment it requests the TYPE65468 record and parses it into a 'valid' TLSA record
174 It returns a list of TLSARecord objects
176 if hostname[-1] != '.':
179 if not protocol.lower() in ['tcp', 'udp', 'sctp']:
180 raise Exception('Error: unknown protocol: %s. Should be one of tcp, udp or sctp' % protocol)
183 records = getRecords('*._%s.%s' % (protocol.lower(), hostname), rrtype=65468, secure=secure)
185 records = getRecords('_%s._%s.%s' % (port, protocol.lower(), hostname), rrtype=65468, secure=secure)
186 except InsecureLookupException, e:
189 except DNSLookupError, e:
190 print 'Unable to resolve %s: %s' % (hostname, str(e))
193 for record in records:
194 hexdata = b2a_hex(record)
196 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:]))
198 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:]))
201 def loadCert(certificate):
202 """Returns an M2Crypto.X509.X509 object"""
203 if isinstance(certificate, X509.X509):
204 # nothing to be done :-)
207 # Maybe we were passed a path
208 return X509.load_cert(certificate)
210 # Can't load the cert
211 raise Exception('Unable to load certificate %s.' % certificate)
213 def verifyCertMatch(record, cert):
215 Verify the certificate with the record.
216 record should be a TLSARecord and cert should be a M2Crypto.X509.X509
218 if not isinstance(cert, X509.X509):
220 if not isinstance(record, TLSARecord):
223 if record.selector == 1:
224 certhash = getHash(cert.get_pubkey(), record.mtype)
226 certhash = getHash(cert, record.mtype)
231 if certhash == record.cert:
237 """When instanciated, this class contains all the fields of a TLSA record.
239 def __init__(self, name, usage, selector, mtype, cert):
240 """name is the name of the RR in the format: /^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([a-z0-9]*\.){2,}$/
241 usage, selector and mtype should be an integer
242 cert should be a hexidecimal string representing the certificate to be matched field
245 self.rrtype = 65468 # TLSA provisional
246 self.rrclass = 1 # IN
247 self.name = str(name)
248 self.usage = int(usage)
249 self.selector = int(selector)
250 self.mtype = int(mtype)
251 self.cert = str(cert)
253 raise Exception('Invalid value passed, unable to create a TLSARecord')
255 def getRecord(self, draft=False):
256 """Returns the RR string of this TLSARecord, either in rfc (default) or draft format"""
258 return '%s IN TYPE65468 \# %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)
259 return '%s IN TLSA %s %s %s %s' % (self.name, self.usage, self.selector, self.mtype, self.cert)
261 def _toHex(self, val):
262 """Helper function to create hex strings from integers"""
265 def isValid(self, raiseException=False):
266 """Check whether all fields in the TLSA record are conforming to the spec and check if the port, protocol and name are good"""
269 if not 1 <= int(self.getPort()) <= 65535:
270 err.append('Port %s not within correct range (1 <= port <= 65535)' % self.getPort())
272 if self.getPort() != '*':
273 err.append('Port %s not a number' % self.getPort())
274 if not self.usage in [0,1,2,3]:
275 err.append('Usage: invalid (%s is not one of 0, 1, 2 or 3)' % self.usage)
276 if not self.selector in [0,1]:
277 err.append('Selector: invalid (%s is not one of 0 or 1)' % self.selector)
278 if not self.mtype in [0,1,2]:
279 err.append('Matching Type: invalid (%s is not one of 0, 1 or 2)' % self.mtype)
280 if not self.isNameValid():
281 err.append('Name (%s) is not in the correct format: _portnumber._transportprotocol.hostname.dom.' % self.name)
282 # A certificate length of 0 is accepted
283 if self.mtype in [1,2] and len(self.cert) != 0:
284 if not len(self.cert) == {1:64,2:128}[self.mtype]:
285 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])
287 if not raiseException:
290 msg = 'The TLSA record is invalid.'
292 msg += '\n\t%s' % error
293 raise RecordValidityException(msg)
297 def isNameValid(self):
298 """Check if the name if in the correct format"""
299 if not re.match('^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([-a-z0-9]*\.){2,}$', self.name):
303 def getProtocol(self):
304 """Returns the protocol based on the name"""
305 return re.split('\.', self.name)[1][1:]
308 """Returns the port based on the name"""
309 if re.split('\.', self.name)[0][0] == '*':
312 return re.split('\.', self.name)[0][1:]
315 """An object representing an A Record (IPv4 address)"""
316 def __init__(self, hostname, address):
318 self.hostname = hostname
319 self.address = address
326 IPv4Address(self.address)
332 """An object representing an AAAA Record (IPv6 address)"""
333 def __init__(self, hostname, address):
335 self.address = address
342 IPv6Address(self.address)
348 class RecordValidityException(Exception):
351 class InsecureLookupException(Exception):
354 class DNSLookupError(Exception):
357 if __name__ == '__main__':
360 parser = argparse.ArgumentParser(description='Create and verify DANE records.', epilog='This tool has a few limitations: it only IPv4 for SSL connections.')
362 subparsers = parser.add_subparsers(title='Functions', help='Available functions, see %(prog)s function -h for function-specific help')
363 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 it\'s local certificates.')
364 parser_verify.set_defaults(function='verify')
365 parser_create = subparsers.add_parser('create', help='Create a TLSA record')
366 parser_create.set_defaults(function='create')
368 #parser.add_argument('-4', dest='ipv4', action='store_true',help='use ipv4 networking only')
369 #parser.add_argument('-6', dest='ipv6', action='store_true',help='use ipv6 networking only')
370 parser.add_argument('--insecure', action='store_true', default=False, help='Allow use of non-dnssec secured answers')
371 parser.add_argument('--resolvconf', metavar='/PATH/TO/RESOLV.CONF', action='store', default='', help='Use a recursive resolver from resolv.conf')
372 parser.add_argument('-v', '--version', action='version', version='%(prog)s v0.2', help='show version and exit')
373 parser.add_argument('host', metavar="hostname")
375 parser_verify.add_argument('--port', '-p', action='store', default='443', help='The port, or \'*\' where running TLS is located (default: %(default)s).')
376 parser_verify.add_argument('--protocol', action='store', choices=['tcp','udp','sctp'], default='tcp', help='The protocol the TLS service is using (default: %(default)s).')
377 parser_verify.add_argument('--only-rr', '-o', action='store_true', help='Only verify that the TLSA resource record is correct (do not check certificate)')
378 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)')
379 parser_verify.add_argument('--quiet', '-q', action='store_true', help='Only print the result of the validation')
381 parser_create.add_argument('--port', '-p', action='store', type=int, default=443, help='The port where running TLS is located (default: %(default)s).')
382 parser_create.add_argument('--protocol', action='store', choices=['tcp','udp','sctp'], default='tcp', help='The protocol the TLS service is using (default: %(default)s).')
383 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')
384 parser_create.add_argument('--output', '-o', action='store', default='draft', choices=['draft','rfc','both'], help='The type of output. Draft (private RRtype, 65468), RFC (TLSA) or both (default: %(default)s).')
386 # Usage of the certificate
387 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).')
388 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).')
389 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).')
391 args = parser.parse_args()
393 if args.host[-1] != '.':
398 if os.path.isfile(args.resolvconf):
399 resolvconf = args.resolvconf
401 print >> sys.stdout, '%s is not a file. Unable to use it as resolv.conf' % args.resolvconf
406 # not operations are fun!
407 secure = not args.insecure
409 if args.function == 'verify':
410 records = getTLSA(args.host, args.port, args.protocol, secure)
411 if len(records) == 0:
414 for record in records:
416 # First, check if the first three fields have correct values.
418 print 'Received the following record for name %s:' % record.name
419 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'))
420 print '\tSelector:\t\t\t%d (%s)' % (record.selector, {0:'Certificate', 1:'SubjectPublicKeyInfo'}.get(record.selector, 'INVALID'))
421 print '\tMatching Type:\t\t\t%d (%s)' % (record.mtype, {0:'Full Certificate', 1:'SHA-256', 2:'SHA-512'}.get(record.mtype, 'INVALID'))
422 print '\tCertificate for Association:\t%s' % record.cert
425 record.isValid(raiseException=True)
426 except RecordValidityException, e:
427 print >> sys.stderr, 'Error: %s' % str(e)
431 print 'This record is valid (well-formed).'
434 # Go to the next record
437 # When we are here, The user also wants to verify the certificates with the record
438 if args.protocol != 'tcp':
439 print >> sys.stderr, 'Only SSL over TCP is supported (sorry)'
443 print 'Attempting to verify the record with the TLS service...'
444 addresses = getA(args.host, secure=secure)
445 for address in addresses:
447 print 'Got the following IP: %s' % str(address)
448 # We do the certificate handling here, as M2Crypto keeps segfaulting when we do it in a method
450 if os.path.isfile(args.ca_cert):
451 if ctx.load_verify_locations(cafile=args.ca_cert) != 1: raise Exception('No CA cert')
452 elif os.path.exists(args.ca_cert):
453 if ctx.load_verify_locations(capath=args.ca_cert) != 1: raise Exception('No CA certs')
455 print >> sys.stderr, '%s is neither a file nor a directory, unable to continue' % args.ca_cert
457 # Don't error when the verification fails in the SSL handshake
458 ctx.set_verify(SSL.verify_none, depth=9)
459 connection = SSL.Connection(ctx)
461 connection.connect((str(address), int(args.port)))
462 except SSL.Checker.WrongHost, e:
463 # The name on the remote cert doesn't match the hostname because we connect on IP, not hostname (as we want secure lookup)
465 chain = connection.get_peer_cert_chain()
466 verify_result = connection.get_verify_result()
468 # Good, now let's verify
469 if record.usage == 1: # End-host cert
471 if verifyCertMatch(record, cert):
472 if verify_result == 0: # The cert chains to a valid CA cert according to the system-certificates
473 print 'SUCCESS (Usage 1): Certificate offered by the server matches the one mentioned in the TLSA record and chains to a valid CA certificate'
475 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)
476 if pre_exit == 0: pre_exit = 2
477 if not args.quiet: print 'The matched certificate has Subject: %s' % cert.get_subject()
479 print 'FAIL: Certificate offered by the server does not match the TLSA record'
480 if pre_exit == 0: pre_exit = 2
482 elif record.usage == 0: # CA constraint
484 # Remove the first (= End-Entity cert) from the chain
487 if verifyCertMatch(record, cert):
492 if verify_result == 0:
493 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'
495 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)
496 if pre_exit == 0: pre_exit = 2
498 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'
499 if pre_exit == 0: pre_exit = 2
500 if not args.quiet: print 'The matched certificate has Subject: %s' % cert.get_subject()
502 print 'FAIL (Usage 0): No certificate in the certificate chain offered by the server matches the TLSA record'
503 if pre_exit == 0: pre_exit = 2
505 elif record.usage == 2: # Usage 2, use the cert in the record as trust anchor
506 #FIXME: doesnt comply to the spec
508 previous_issuer = None
511 if not str(previous_issuer) == str(cert.get_subject()): # The chain cannot be valid
512 print "FAIL: Certificates don't chain"
514 previous_issuer = cert.get_issuer()
515 if verifyCertMatch(record, cert):
519 print 'SUCCESS (usage 2): A certificate in the certificate chain (including the end-entity certificate) offered by the server matches the TLSA record'
520 if not args.quiet: print 'The matched certificate has Subject: %s' % cert.get_subject()
522 print 'FAIL (usage 2): No certificate in the certificate chain (including the end-entity certificate) offered by the server matches the TLSA record'
523 if pre_exit == 0: pre_exit = 2
525 elif record.usage == 3: # EE cert MUST match
526 if verifyCertMatch(record,chain[0]):
527 print 'SUCCESS (usage 3): The certificate offered by the server matches the TLSA record'
528 if not args.quiet: print 'The matched certificate has Subject: %s' % chain[0].get_subject()
530 print 'FAIL (usage 3): The certificate offered by the server does not match the TLSA record'
531 if pre_exit == 0: pre_exit = 2
533 # Cleanup, just in case
538 # END for address in addresses
539 # END for record in records
543 else: # we want to create
545 if not args.certificate:
546 if args.protocol != 'tcp':
547 print >> sys.stderr, 'Only SSL over TCP is supported (sorry)'
550 print 'No certificate specified on the commandline, attempting to retrieve it from the server %s' % (args.host)
551 connection_port = args.port
553 sys.stdout.write('The port specified on the commandline is *, please specify the port of the TLS service on %s (443): ' % args.host)
556 user_input = raw_input()
558 connection_port = 443
561 if 1 <= int(user_input) <= 65535:
562 connection_port = user_input
565 sys.stdout.write('Port %s not numerical or within correct range (1 <= port <= 65535), try again (hit enter for default 443): ' % user_input)
566 # Get the A records for the host
568 addresses = getA(args.host, secure=secure)
569 except InsecureLookupException, e:
570 print >> sys.stderr, str(e)
573 for address in addresses:
574 print 'Attempting to get certificate from %s' % str(address)
575 # We do the certificate handling here, as M2Crypto keeps segfaulting when try to do stuff with the cert if we don't
577 ctx.set_verify(SSL.verify_none, depth=9)
578 connection = SSL.Connection(ctx)
580 connection.connect((str(address), int(connection_port)))
581 except SSL.Checker.WrongHost:
584 chain = connection.get_peer_cert_chain()
585 for chaincert in chain:
586 if int(args.usage) == 1 or int(args.usage) == 3:
587 # The first cert is the end-entity cert
588 print 'Got a certificate with Subject: %s' % chaincert.get_subject()
592 if (int(args.usage) == 0 and chaincert.check_ca()) or int(args.usage) == 2:
593 sys.stdout.write('Got a certificate with the following Subject:\n\t%s\nUse this as certificate to match? [y/N] ' % chaincert.get_subject())
596 user_input = raw_input()
597 if user_input in ['','n','N']:
599 elif user_input in ['y', 'Y']:
603 sys.stdout.write('Please answer Y or N')
607 if cert: # Print the requested records based on the retrieved certificates
608 if args.output == 'both':
609 print genTLSA(args.host, args.protocol, args.port, cert, 'draft', args.usage, args.selector, args.mtype)
610 print genTLSA(args.host, args.protocol, args.port, cert, 'rfc', args.usage, args.selector, args.mtype)
612 print genTLSA(args.host, args.protocol, args.port, cert, args.output, args.usage, args.selector, args.mtype)
614 # Clear the cert from memory (to stop M2Crypto from segfaulting)
615 # And cleanup the connection and context
621 else: # Pass the path to the certificate to the genTLSA function
622 if args.output == 'both':
623 print genTLSA(args.host, args.protocol, args.port, args.certificate, 'draft', args.usage, args.selector, args.mtype)
624 print genTLSA(args.host, args.protocol, args.port, args.certificate, 'rfc', args.usage, args.selector, args.mtype)
626 print genTLSA(args.host, args.protocol, args.port, args.certificate, args.output, args.usage, args.selector, args.mtype)