summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog14
-rw-r--r--dns/dnssec.py194
-rw-r--r--dns/query.py2
-rw-r--r--dns/rdatatype.py2
-rw-r--r--dns/rdtypes/ANY/NSEC3.py6
-rw-r--r--dns/rdtypes/ANY/TLSA.py89
-rw-r--r--dns/rdtypes/ANY/__init__.py1
-rw-r--r--dns/resolver.py12
-rw-r--r--tests/bugs.py5
-rw-r--r--tests/dnssec.py10
-rw-r--r--tests/example3
-rw-r--r--tests/example1.good3
-rw-r--r--tests/example2.good3
-rw-r--r--util/copyrights1
14 files changed, 246 insertions, 99 deletions
diff --git a/ChangeLog b/ChangeLog
index 81650fe..d3eda06 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2012-09-25 Sean Leach
+
+ * added set_flags() method to dns.resolver.Resolver
+
+2012-09-25 Pieter Lexis
+
+ * added support for TLSA RR
+
+2012-08-28 Bob Halley <halley@dnspython.org>
+
+ * dns/rdtypes/ANY/NSEC3.py (NSEC3.from_text): The NSEC3 from_text()
+ method could erroneously emit empty bitmap windows (i.e. windows
+ with a count of 0 bytes); such bitmaps are illegal.
+
2012-04-08 Bob Halley <halley@dnspython.org>
* (Version 1.10.0 released)
diff --git a/dns/dnssec.py b/dns/dnssec.py
index dd6a27a..4f6fc98 100644
--- a/dns/dnssec.py
+++ b/dns/dnssec.py
@@ -126,7 +126,8 @@ def make_ds(name, key, algorithm, origin=None):
return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
len(dsrdata))
-def _find_key(keys, rrsig):
+def _find_candidate_keys(keys, rrsig):
+ candidate_keys=[]
value = keys.get(rrsig.signer)
if value is None:
return None
@@ -141,8 +142,8 @@ def _find_key(keys, rrsig):
for rdata in rdataset:
if rdata.algorithm == rrsig.algorithm and \
key_id(rdata) == rrsig.key_tag:
- return rdata
- return None
+ candidate_keys.append(rdata)
+ return candidate_keys
def _is_rsa(algorithm):
return algorithm in (RSAMD5, RSASHA1,
@@ -217,100 +218,101 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
if isinstance(origin, (str, unicode)):
origin = dns.name.from_text(origin, dns.name.root)
- key = _find_key(keys, rrsig)
- if not key:
- raise ValidationFailure, 'unknown key'
-
- # For convenience, allow the rrset to be specified as a (name, rdataset)
- # tuple as well as a proper rrset
- if isinstance(rrset, tuple):
- rrname = rrset[0]
- rdataset = rrset[1]
- else:
- rrname = rrset.name
- rdataset = rrset
-
- if now is None:
- now = time.time()
- if rrsig.expiration < now:
- raise ValidationFailure, 'expired'
- if rrsig.inception > now:
- raise ValidationFailure, 'not yet valid'
-
- hash = _make_hash(rrsig.algorithm)
-
- if _is_rsa(rrsig.algorithm):
- keyptr = key.key
- (bytes,) = struct.unpack('!B', keyptr[0:1])
- keyptr = keyptr[1:]
- if bytes == 0:
- (bytes,) = struct.unpack('!H', keyptr[0:2])
- keyptr = keyptr[2:]
- rsa_e = keyptr[0:bytes]
- rsa_n = keyptr[bytes:]
- keylen = len(rsa_n) * 8
- pubkey = Crypto.PublicKey.RSA.construct(
- (Crypto.Util.number.bytes_to_long(rsa_n),
- Crypto.Util.number.bytes_to_long(rsa_e)))
- sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),)
- elif _is_dsa(rrsig.algorithm):
- keyptr = key.key
- (t,) = struct.unpack('!B', keyptr[0:1])
- keyptr = keyptr[1:]
- octets = 64 + t * 8
- dsa_q = keyptr[0:20]
- keyptr = keyptr[20:]
- dsa_p = keyptr[0:octets]
- keyptr = keyptr[octets:]
- dsa_g = keyptr[0:octets]
- keyptr = keyptr[octets:]
- dsa_y = keyptr[0:octets]
- pubkey = Crypto.PublicKey.DSA.construct(
- (Crypto.Util.number.bytes_to_long(dsa_y),
- Crypto.Util.number.bytes_to_long(dsa_g),
- Crypto.Util.number.bytes_to_long(dsa_p),
- Crypto.Util.number.bytes_to_long(dsa_q)))
- (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:])
- sig = (Crypto.Util.number.bytes_to_long(dsa_r),
- Crypto.Util.number.bytes_to_long(dsa_s))
- else:
- raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
-
- hash.update(_to_rdata(rrsig, origin)[:18])
- hash.update(rrsig.signer.to_digestable(origin))
-
- if rrsig.labels < len(rrname) - 1:
- suffix = rrname.split(rrsig.labels + 1)[1]
- rrname = dns.name.from_text('*', suffix)
- rrnamebuf = rrname.to_digestable(origin)
- rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
- rrsig.original_ttl)
- rrlist = sorted(rdataset);
- for rr in rrlist:
- hash.update(rrnamebuf)
- hash.update(rrfixed)
- rrdata = rr.to_digestable(origin)
- rrlen = struct.pack('!H', len(rrdata))
- hash.update(rrlen)
- hash.update(rrdata)
-
- digest = hash.digest()
-
- if _is_rsa(rrsig.algorithm):
- # PKCS1 algorithm identifier goop
- digest = _make_algorithm_id(rrsig.algorithm) + digest
- padlen = keylen // 8 - len(digest) - 3
- digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest
- elif _is_dsa(rrsig.algorithm):
- pass
- else:
- # Raise here for code clarity; this won't actually ever happen
- # since if the algorithm is really unknown we'd already have
- # raised an exception above
- raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
+ for candidate_key in _find_candidate_keys(keys, rrsig):
+ if not candidate_key:
+ raise ValidationFailure, 'unknown key'
+
+ # For convenience, allow the rrset to be specified as a (name, rdataset)
+ # tuple as well as a proper rrset
+ if isinstance(rrset, tuple):
+ rrname = rrset[0]
+ rdataset = rrset[1]
+ else:
+ rrname = rrset.name
+ rdataset = rrset
+
+ if now is None:
+ now = time.time()
+ if rrsig.expiration < now:
+ raise ValidationFailure, 'expired'
+ if rrsig.inception > now:
+ raise ValidationFailure, 'not yet valid'
+
+ hash = _make_hash(rrsig.algorithm)
+
+ if _is_rsa(rrsig.algorithm):
+ keyptr = candidate_key.key
+ (bytes,) = struct.unpack('!B', keyptr[0:1])
+ keyptr = keyptr[1:]
+ if bytes == 0:
+ (bytes,) = struct.unpack('!H', keyptr[0:2])
+ keyptr = keyptr[2:]
+ rsa_e = keyptr[0:bytes]
+ rsa_n = keyptr[bytes:]
+ keylen = len(rsa_n) * 8
+ pubkey = Crypto.PublicKey.RSA.construct(
+ (Crypto.Util.number.bytes_to_long(rsa_n),
+ Crypto.Util.number.bytes_to_long(rsa_e)))
+ sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),)
+ elif _is_dsa(rrsig.algorithm):
+ keyptr = candidate_key.key
+ (t,) = struct.unpack('!B', keyptr[0:1])
+ keyptr = keyptr[1:]
+ octets = 64 + t * 8
+ dsa_q = keyptr[0:20]
+ keyptr = keyptr[20:]
+ dsa_p = keyptr[0:octets]
+ keyptr = keyptr[octets:]
+ dsa_g = keyptr[0:octets]
+ keyptr = keyptr[octets:]
+ dsa_y = keyptr[0:octets]
+ pubkey = Crypto.PublicKey.DSA.construct(
+ (Crypto.Util.number.bytes_to_long(dsa_y),
+ Crypto.Util.number.bytes_to_long(dsa_g),
+ Crypto.Util.number.bytes_to_long(dsa_p),
+ Crypto.Util.number.bytes_to_long(dsa_q)))
+ (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:])
+ sig = (Crypto.Util.number.bytes_to_long(dsa_r),
+ Crypto.Util.number.bytes_to_long(dsa_s))
+ else:
+ raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
+
+ hash.update(_to_rdata(rrsig, origin)[:18])
+ hash.update(rrsig.signer.to_digestable(origin))
+
+ if rrsig.labels < len(rrname) - 1:
+ suffix = rrname.split(rrsig.labels + 1)[1]
+ rrname = dns.name.from_text('*', suffix)
+ rrnamebuf = rrname.to_digestable(origin)
+ rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
+ rrsig.original_ttl)
+ rrlist = sorted(rdataset);
+ for rr in rrlist:
+ hash.update(rrnamebuf)
+ hash.update(rrfixed)
+ rrdata = rr.to_digestable(origin)
+ rrlen = struct.pack('!H', len(rrdata))
+ hash.update(rrlen)
+ hash.update(rrdata)
+
+ digest = hash.digest()
+
+ if _is_rsa(rrsig.algorithm):
+ # PKCS1 algorithm identifier goop
+ digest = _make_algorithm_id(rrsig.algorithm) + digest
+ padlen = keylen // 8 - len(digest) - 3
+ digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest
+ elif _is_dsa(rrsig.algorithm):
+ pass
+ else:
+ # Raise here for code clarity; this won't actually ever happen
+ # since if the algorithm is really unknown we'd already have
+ # raised an exception above
+ raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
- if not pubkey.verify(digest, sig):
- raise ValidationFailure, 'verify failure'
+ if pubkey.verify(digest, sig):
+ return
+ raise ValidationFailure, 'verify failure'
def _validate(rrset, rrsigset, keys, origin=None, now=None):
"""Validate an RRset
diff --git a/dns/query.py b/dns/query.py
index c141612..7a1b1f4 100644
--- a/dns/query.py
+++ b/dns/query.py
@@ -339,7 +339,7 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
dns.rdatatype.AXFR.
@type rdtype: int or string
@param rdclass: The class of the zone transfer. The default is
- dns.rdatatype.IN.
+ dns.rdataclass.IN.
@type rdclass: int or string
@param timeout: The number of seconds to wait for each response message.
If None, the default, wait forever.
diff --git a/dns/rdatatype.py b/dns/rdatatype.py
index 380cfcd..f64307a 100644
--- a/dns/rdatatype.py
+++ b/dns/rdatatype.py
@@ -78,6 +78,7 @@ DNSKEY = 48
DHCID = 49
NSEC3 = 50
NSEC3PARAM = 51
+TLSA = 52
HIP = 55
SPF = 99
UNSPEC = 103
@@ -140,6 +141,7 @@ _by_text = {
'DHCID' : DHCID,
'NSEC3' : NSEC3,
'NSEC3PARAM' : NSEC3PARAM,
+ 'TLSA' : TLSA,
'HIP' : HIP,
'SPF' : SPF,
'UNSPEC' : UNSPEC,
diff --git a/dns/rdtypes/ANY/NSEC3.py b/dns/rdtypes/ANY/NSEC3.py
index c7ac737..b42fe4c 100644
--- a/dns/rdtypes/ANY/NSEC3.py
+++ b/dns/rdtypes/ANY/NSEC3.py
@@ -114,7 +114,8 @@ class NSEC3(dns.rdata.Rdata):
prior_rdtype = nrdtype
new_window = nrdtype // 256
if new_window != window:
- windows.append((window, ''.join(bitmap[0:octets])))
+ if octets != 0:
+ windows.append((window, ''.join(bitmap[0:octets])))
bitmap = ['\0'] * 32
window = new_window
offset = nrdtype % 256
@@ -122,7 +123,8 @@ class NSEC3(dns.rdata.Rdata):
bit = offset % 8
octets = byte + 1
bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit))
- windows.append((window, ''.join(bitmap[0:octets])))
+ if octets != 0:
+ windows.append((window, ''.join(bitmap[0:octets])))
return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, windows)
from_text = classmethod(from_text)
diff --git a/dns/rdtypes/ANY/TLSA.py b/dns/rdtypes/ANY/TLSA.py
new file mode 100644
index 0000000..6ca8c0a
--- /dev/null
+++ b/dns/rdtypes/ANY/TLSA.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2005-2007, 2009-2011 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import struct
+
+import dns.rdata
+import dns.rdatatype
+
+class TLSA(dns.rdata.Rdata):
+ """TLSA record
+
+ @ivar usage: The certificate usage
+ @type usage: int
+ @ivar selector: The selector field
+ @type selector: int
+ @ivar mtype: The 'matching type' field
+ @type mtype: int
+ @ivar cert: The 'Certificate Association Data' field
+ @type cert: string
+ @see: RFC 6698"""
+
+ __slots__ = ['usage', 'selector', 'mtype', 'cert']
+
+ def __init__(self, rdclass, rdtype, usage, selector,
+ mtype, cert):
+ super(TLSA, self).__init__(rdclass, rdtype)
+ self.usage = usage
+ self.selector = selector
+ self.mtype = mtype
+ self.cert = cert
+
+ def to_text(self, origin=None, relativize=True, **kw):
+ return '%d %d %d %s' % (self.usage,
+ self.selector,
+ self.mtype,
+ dns.rdata._hexify(self.cert,
+ chunksize=128))
+
+ def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
+ usage = tok.get_uint8()
+ selector = tok.get_uint8()
+ mtype = tok.get_uint8()
+ cert_chunks = []
+ while 1:
+ t = tok.get().unescape()
+ if t.is_eol_or_eof():
+ break
+ if not t.is_identifier():
+ raise dns.exception.SyntaxError
+ cert_chunks.append(t.value)
+ cert = ''.join(cert_chunks)
+ cert = cert.decode('hex_codec')
+ return cls(rdclass, rdtype, usage, selector, mtype, cert)
+
+ from_text = classmethod(from_text)
+
+ def to_wire(self, file, compress = None, origin = None):
+ header = struct.pack("!BBB", self.usage, self.selector, self.mtype)
+ file.write(header)
+ file.write(self.cert)
+
+ def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
+ header = struct.unpack("!BBB", wire[current : current + 3])
+ current += 3
+ rdlen -= 3
+ cert = wire[current : current + rdlen].unwrap()
+ return cls(rdclass, rdtype, header[0], header[1], header[2], cert)
+
+ from_wire = classmethod(from_wire)
+
+ def _cmp(self, other):
+ hs = struct.pack("!BBB", self.usage, self.selector, self.mtype)
+ ho = struct.pack("!BBB", other.usage, other.selector, other.mtype)
+ v = cmp(hs, ho)
+ if v == 0:
+ v = cmp(self.cert, other.cert)
+ return v
diff --git a/dns/rdtypes/ANY/__init__.py b/dns/rdtypes/ANY/__init__.py
index 721e9dd..cfb0be6 100644
--- a/dns/rdtypes/ANY/__init__.py
+++ b/dns/rdtypes/ANY/__init__.py
@@ -33,6 +33,7 @@ __all__ = [
'NSEC',
'NSEC3',
'NSEC3PARAM',
+ 'TLSA',
'PTR',
'RP',
'RRSIG',
diff --git a/dns/resolver.py b/dns/resolver.py
index ce77ac1..1b344a7 100644
--- a/dns/resolver.py
+++ b/dns/resolver.py
@@ -425,6 +425,8 @@ class Resolver(object):
@type ednsflags: int
@ivar payload: The EDNS payload size. The default is 0.
@type payload: int
+ @ivar flags: The message flags to use. The default is None (i.e. not overwritten)
+ @type flags: int
@ivar cache: The cache to use. The default is None.
@type cache: dns.resolver.Cache object
"""
@@ -466,6 +468,7 @@ class Resolver(object):
self.ednsflags = 0
self.payload = 0
self.cache = None
+ self.flags = None
def read_resolv_conf(self, f):
"""Process f as a file in the /etc/resolv.conf format. If f is
@@ -761,6 +764,8 @@ class Resolver(object):
request.use_tsig(self.keyring, self.keyname,
algorithm=self.keyalgorithm)
request.use_edns(self.edns, self.ednsflags, self.payload)
+ if self.flags is not None:
+ request.flags = self.flags
response = None
#
# make a copy of the servers list so we can alter it later.
@@ -898,6 +903,13 @@ class Resolver(object):
self.ednsflags = ednsflags
self.payload = payload
+ def set_flags(self, flags):
+ """Overrides the default flags with your own
+
+ @param flags: The flags to overwrite the default with
+ @type flags: int"""
+ self.flags = flags
+
default_resolver = None
def get_default_resolver():
diff --git a/tests/bugs.py b/tests/bugs.py
index c2fa6b6..312ec3e 100644
--- a/tests/bugs.py
+++ b/tests/bugs.py
@@ -40,5 +40,10 @@ class BugsTestCase(unittest.TestCase):
ttl = dns.ttl.from_text("2147483648")
self.failUnlessRaises(dns.ttl.BadTTL, bad)
+ def test_empty_NSEC3_window(self):
+ rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NSEC3,
+ "1 0 100 ABCD SCBCQHKU35969L2A68P3AD59LHF30715")
+ self.failUnless(rdata.windows == [])
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/dnssec.py b/tests/dnssec.py
index 7b4546a..b997b17 100644
--- a/tests/dnssec.py
+++ b/tests/dnssec.py
@@ -30,6 +30,13 @@ abs_keys = { abs_dnspython_org :
'256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58 LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF')
}
+abs_keys_duplicate_keytag = { abs_dnspython_org :
+ dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'DNSKEY',
+ '257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=',
+ '256 3 5 AwEAAdSSg++++THIS/IS/NOT/THE/CORRECT/KEY++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AaOSydAF',
+ '256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58 LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF')
+ }
+
rel_keys = { dns.name.empty :
dns.rrset.from_text('@', 3600, 'IN', 'DNSKEY',
'257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=',
@@ -95,6 +102,9 @@ class DNSSECValidatorTestCase(unittest.TestCase):
def testAbsoluteRSAGood(self):
dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys, None, when)
+ def testDuplicateKeytag(self):
+ dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys_duplicate_keytag, None, when)
+
def testAbsoluteRSABad(self):
def bad():
dns.dnssec.validate(abs_other_soa, abs_soa_rrsig, abs_keys, None,
diff --git a/tests/example b/tests/example
index 2f753a2..37a90a2 100644
--- a/tests/example
+++ b/tests/example
@@ -165,6 +165,9 @@ srv02 SRV 65535 65535 65535 old-slow-box.example.com.
$TTL 301 ; 5 minutes 1 second
t A 73.80.65.49
$TTL 3600 ; 1 hour
+tlsa1 TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
+tlsa2 TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
+tlsa3 TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
txt01 TXT "foo"
txt02 TXT "foo" "bar"
txt03 TXT "foo"
diff --git a/tests/example1.good b/tests/example1.good
index 0834d17..100842e 100644
--- a/tests/example1.good
+++ b/tests/example1.good
@@ -90,6 +90,9 @@ srv01 3600 IN SRV 0 0 0 .
srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com.
sshfp1 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab
t 301 IN A 73.80.65.49
+tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
+tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
+tlsa3 3600 IN TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
txt01 3600 IN TXT "foo"
txt02 3600 IN TXT "foo" "bar"
txt03 3600 IN TXT "foo"
diff --git a/tests/example2.good b/tests/example2.good
index de4bcd5..ac3261c 100644
--- a/tests/example2.good
+++ b/tests/example2.good
@@ -90,6 +90,9 @@ srv01.example. 3600 IN SRV 0 0 0 .
srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example.com.
sshfp1.example. 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab
t.example. 301 IN A 73.80.65.49
+tlsa1.example. 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
+tlsa2.example. 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
+tlsa3.example. 3600 IN TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
txt01.example. 3600 IN TXT "foo"
txt02.example. 3600 IN TXT "foo" "bar"
txt03.example. 3600 IN TXT "foo"
diff --git a/util/copyrights b/util/copyrights
index f73e9f6..2e044f3 100644
--- a/util/copyrights
+++ b/util/copyrights
@@ -51,6 +51,7 @@
./dns/rdtypes/ANY/SOA.py PYTHON 2003,2004,2005,2006,2007,2009,2010,2011
./dns/rdtypes/ANY/SPF.py PYTHON 2006,2007,2009,2010,2011
./dns/rdtypes/ANY/SSHFP.py PYTHON 2005,2006,2007,2009,2010,2011
+./dns/rdtypes/ANY/TLSA.py PYTHON 2012
./dns/rdtypes/ANY/TXT.py PYTHON 2003,2004,2005,2006,2007,2009,2010,2011
./dns/rdtypes/ANY/X25.py PYTHON 2003,2004,2005,2006,2007,2009,2010,2011
./dns/rdtypes/ANY/__init__.py PYTHON 2003,2004,2005,2006,2007,2009,2010,2011