Ticket #4610: dnssec.patch

File dnssec.patch, 13.1 KB (added by Jean-Paul Calderone, 9 years ago)
  • twisted/names/test/test_dns.py

     
    1212    from StringIO import StringIO
    1313
    1414import struct
     15import re
    1516
    1617from twisted.python.failure import Failure
    1718from twisted.internet import address, task
     
    12461247            dns.Record_SPF('foo', 'bar', ttl=10),
    12471248            dns.Record_SPF('foo', 'bar', ttl=10),
    12481249            dns.Record_SPF('foo', 'bar', ttl=100))
     1250
     1251
     1252
     1253class DnssecTests(unittest.TestCase):
     1254    """
     1255    Tests for the DNSSEC records & functions - RRSIG, NSEC, DNSKEY, DS
     1256    """
     1257
     1258    def _mk_rrsig(self, signame, signature, type=dns.A, algorithm=5, labels=1, original_ttl=2, expiration=0, inception=1, keytag=2):
     1259        rrsig = struct.pack('!HBBIIIH', type, algorithm, labels, original_ttl, expiration, inception, keytag)
     1260        for label in signame.split('.'):
     1261            rrsig += struct.pack('!B', len(label))
     1262            rrsig += label
     1263        rrsig += '\x00'
     1264        rrsig += signature
     1265
     1266        return rrsig
     1267
     1268    def test_rrsig_decode(self):
     1269        fields = {
     1270                'signame': 'foo.bar',
     1271                'signature': 'thesignature',
     1272                'type': dns.A,
     1273                'algorithm': 5,
     1274                'labels': 3,
     1275                'original_ttl': 300,
     1276                'expiration': 100000,
     1277                'inception': 200000,
     1278                'keytag': 5678,
     1279                }
     1280
     1281        rrsig = self._mk_rrsig(**fields)
     1282
     1283        rr = dns.Record_RRSIG()
     1284        rr.decode(StringIO(rrsig), len(rrsig))
     1285
     1286        self.assertEqual(
     1287                rr,
     1288                dns.Record_RRSIG(**fields)
     1289                )
     1290
     1291    def test_rrsig_encode(self):
     1292        rr = dns.Record_RRSIG(
     1293                signame='foo.bar',
     1294                signature='thesig',
     1295                type=dns.A,
     1296                algorithm=5,
     1297                labels=3,
     1298                original_ttl=0xbbbb,
     1299                expiration=0xaaaaaaaa,
     1300                inception=0xcccccccc,
     1301                keytag=0xfffe,
     1302                )
     1303        strio = StringIO()
     1304        rr.encode(strio)
     1305        val = strio.getvalue()
     1306
     1307        expect = str(
     1308                '\x00\x01'  # A
     1309                '\x05'      # algo=5
     1310                '\x03'      # labels=3
     1311                '\x00\x00\xbb\xbb'  # ttl
     1312                '\xaa\xaa\xaa\xaa'  # expiration
     1313                '\xcc\xcc\xcc\xcc'  # inception
     1314                '\xff\xfe'          # keytag
     1315                '\x03foo\x03bar\x00'    # foo.bar
     1316                'thesig'
     1317                )
     1318
     1319
     1320        self.assertEqual(
     1321                val,
     1322                expect,
     1323                )
     1324
     1325    def _mk_dnskey(self, flags=0, protocol=0, algorithm=0, key=''):
     1326        dnskey = struct.pack('!HBB', flags, protocol, algorithm)
     1327        dnskey += key
     1328        return dnskey
     1329
     1330    def test_dnskey_decode(self):
     1331        key = str(
     1332                'AwEAAcdYhgqRE+Z5NkzrKGl3fE6aTAtzMJfxWo8fK02j'
     1333                'niePZIEOmG75pGZAjUHh29iyfYHU394VewgNXQYjhryi'
     1334                'j4pdZ7U9DN/kpu6RNvcwPn6F+y/Hz5qsNTFZ/GIjU83J'
     1335                'RrVsU8fTpCY27pik6S5JRJ5l1nHVwptaTlSiLEL+FgQj'
     1336                )   # keytag==51561 - depends exactly on "fields" below!
     1337        key = key.decode('base64')
     1338
     1339        fields = {
     1340                'flags': 256,
     1341                'protocol': 3,
     1342                'algorithm': 5,
     1343                'key': key,
     1344                }
     1345
     1346        dnskey = self._mk_dnskey(**fields)
     1347
     1348        rr = dns.Record_DNSKEY()
     1349        rr.decode(StringIO(dnskey), len(dnskey))
     1350
     1351        self.assertEqual(
     1352                rr,
     1353                dns.Record_DNSKEY(**fields),
     1354                )
     1355        self.assertEqual(
     1356                rr.tag(),
     1357                51561,
     1358                )
     1359
     1360    def test_dnskey_encode(self):
     1361        rr = dns.Record_DNSKEY(flags=257, protocol=3, algorithm=5, key='thekey')
     1362        strio = StringIO()
     1363        rr.encode(strio)
     1364        val = strio.getvalue()
     1365
     1366        self.assertEqual(
     1367                val,
     1368                '\x01\x01\x03\x05thekey',
     1369                )
     1370
     1371    def _keytime2sec(self):
     1372        # convert 20100702204922 into seconds since 1970
     1373        m = re.match('(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})')
     1374        y,m,d,h,m,s = [int(g) for g in m.groups()]
     1375        retu
     1376
     1377
     1378    def test_rrsig_validate(self):
     1379        # check that Record_RRSIG can validate signatures given the original RRs and the key
     1380        # FIXME: should really generate ourselves some test data, not using (old) public data
     1381
     1382        original = [
     1383                dns.RRHeader('www.ic.ac.uk', type=dns.A, cls=dns.IN, ttl=3600, payload=dns.Record_A('155.198.140.14'))
     1384                ]
     1385
     1386        key = str(
     1387                'AwEAAcdYhgqRE+Z5NkzrKGl3fE6aTAtzMJfxWo8fK02j'
     1388                'niePZIEOmG75pGZAjUHh29iyfYHU394VewgNXQYjhryi'
     1389                'j4pdZ7U9DN/kpu6RNvcwPn6F+y/Hz5qsNTFZ/GIjU83J'
     1390                'RrVsU8fTpCY27pik6S5JRJ5l1nHVwptaTlSiLEL+FgQj'
     1391                )   # keytag==51561
     1392        key = key.decode('base64')
     1393        keyrr = dns.Record_DNSKEY(flags=256, protocol=3, algorithm=5, key=key)
     1394
     1395        sigval = str(
     1396                'h574y3uK6FAWZcN5YdAiuZ8E4VOoZf0np7Fkd6kxzoj0'
     1397                'vLROww2MBERn66OyOZ+nWEojr3YyuVk04E0MUKe915Py'
     1398                'GY9dC49RoX/vwM5l25ScgtUJo7K4CgE9X8/7pIXMZ2Xn'
     1399                '/CNAPkqKSKywzLgZkENwOSVn3WSZdW6weqJ5e+k='
     1400                )
     1401        sigval = sigval.decode('base64')
     1402
     1403        sig = dns.Record_RRSIG(type=dns.A, algorithm=5, labels=4, original_ttl=3600,
     1404                expiration=1280695879,  # Aug  1, 2010 21:51:19
     1405                inception=1278103762,   # Jul  2, 2010 21:49:22
     1406                keytag=51561,
     1407                signature=sigval,
     1408                signame='ic.ac.uk',
     1409                )
     1410
     1411        isvalid = sig.validate(keyrr, original)
     1412        self.assertEqual(isvalid, True)
     1413
  • twisted/names/dns.py

     
    4747import warnings
    4848
    4949import struct, random, types, socket
     50import time
     51try:
     52    import hashlib
     53except:
     54    hashlib = None
    5055
    5156try:
     57    import Crypto.Util
     58    import Crypto.PublicKey.RSA
     59    _has_crypto = True
     60except:
     61    _has_crypto = False
     62
     63try:
    5264    import cStringIO as StringIO
    5365except ImportError:
    5466    import StringIO
     
    8294NAPTR = 35
    8395A6 = 38
    8496DNAME = 39
     97RRSIG = 46
     98NSEC = 47
     99DNSKEY = 48
    85100SPF = 99
    86101
    87102QUERY_TYPES = {
     
    111126    NAPTR: 'NAPTR',
    112127    A6: 'A6',
    113128    DNAME: 'DNAME',
     129    RRSIG: 'RRSIG',
     130    NSEC: 'NSEC',
     131    DNSKEY: 'DNSKEY',
    114132    SPF: 'SPF'
    115133}
    116134
     
    547565    def encode(self, strio, compDict = None):
    548566        self.name.encode(strio, compDict)
    549567
     568    def canonical(self, strio):
     569        n = Name(self.name.lower())
     570        n.encode(strio)
    550571
    551572    def decode(self, strio, length = None):
    552573        self.name = Name()
    553574        self.name.decode(strio)
    554575
    555 
    556576    def __hash__(self):
    557577        return hash(self.name)
    558578
     
    14141434        return hash((self.preference, self.name))
    14151435
    14161436
     1437class Record_RRSIG(tputil.FancyEqMixin, tputil.FancyStrMixin):
     1438    implements(IEncodable, IRecord)
    14171439
     1440    TYPE = RRSIG
     1441
     1442    fancybasename = 'RRSIG'
     1443    showAttributes = compareAttributes = ('type', 'algorithm', 'labels', 'original_ttl', 'expiration', 'inception', 'keytag', 'signame', 'signature')
     1444
     1445    def __init__(self, signame='', signature='', type=A, algorithm=5, labels=0, original_ttl=0, expiration=None, inception=None, keytag=0):
     1446        self.signame = Name(signame)
     1447        self.signature = signature
     1448        self.type = type
     1449        self.algorithm = algorithm
     1450        self.labels = labels
     1451        self.original_ttl = original_ttl
     1452        if inception is None:
     1453            inception = int(time.time())
     1454        self.inception = inception
     1455        if expiration is None:
     1456            expiration = self.inception + 3600
     1457        self.expiration = expiration
     1458        self.keytag = keytag
     1459
     1460    _fmt = '!HBBIIIH'
     1461    _fmt_size = struct.calcsize(_fmt)
     1462
     1463    def encode(self, strio, compDict=None):
     1464        strio.write(struct.pack(self._fmt, self.type, self.algorithm, self.labels, self.original_ttl, self.expiration, self.inception, self.keytag))
     1465        self.signame.encode(strio, compDict)
     1466        strio.write(self.signature)
     1467
     1468    def decode(self, strio, length):
     1469        if length < self._fmt_size + 1:
     1470            raise Exception('payload too short')
     1471        hdr = readPrecisely(strio, self._fmt_size)
     1472        self.type, self.algorithm, self.labels, self.original_ttl, self.expiration, self.inception, self.keytag = struct.unpack(self._fmt, hdr)
     1473
     1474        length -= self._fmt_size
     1475
     1476        start = strio.tell()
     1477        self.signame = Name()
     1478        self.signame.decode(strio, length)
     1479        end = strio.tell()
     1480
     1481        length -= end - start
     1482
     1483        self.signature = readPrecisely(strio, length)
     1484
     1485    def validate(self, key, original):
     1486        # build the payload to validate
     1487        strio = StringIO.StringIO()
     1488        strio.write(struct.pack(self._fmt, self.type, self.algorithm, self.labels, self.original_ttl, self.expiration, self.inception, self.keytag))
     1489        self.signame.encode(strio)
     1490
     1491        canon = []
     1492
     1493        for rr in original:
     1494            if not rr.type==self.type:
     1495                continue
     1496            payload = rr.payload
     1497
     1498            # FIXME: assert rr.type=self.type?
     1499            # FIXME: assert rr.name==self.rrheader.name?
     1500
     1501            # RFC4034 section 6.2
     1502            io = StringIO.StringIO()
     1503
     1504            if hasattr(payload, 'canonical'):
     1505                payload.canonical(io)
     1506            else:
     1507                payload.encode(io)
     1508
     1509            val = io.getvalue()
     1510            canon.append((val, rr.name, rr.cls))
     1511
     1512        for payload,name,cls in sorted(canon):
     1513            name.encode(strio)
     1514            strio.write(struct.pack('!HHIH', self.type, cls, self.original_ttl, len(payload)))
     1515            strio.write(payload)
     1516           
     1517        sigvalue = strio.getvalue()
     1518        return key.verify(sigvalue, self.signature)
     1519
     1520class Record_DNSKEY(tputil.FancyEqMixin, tputil.FancyStrMixin):
     1521    implements(IEncodable, IRecord)
     1522
     1523    TYPE = DNSKEY
     1524
     1525    fancybasename = 'DNSKEY'
     1526    showAttributes = compareAttributes = ('flags', 'protocol', 'algorithm', 'key')
     1527
     1528    def __init__(self, flags=0, protocol=3, algorithm=5, key=''):
     1529        self.flags = flags
     1530        self.protocol = protocol
     1531        self.algorithm = algorithm
     1532        self.key = key
     1533
     1534    def tag(self):
     1535        data = struct.pack('!HBB', self.flags, self.protocol, self.algorithm) + self.key
     1536        v = 0
     1537        for i in range(len(data)):
     1538            if i & 1:
     1539                v += ord(data[i])
     1540            else:
     1541                v += ord(data[i]) << 8
     1542        v += (v >> 16) & 0xffff
     1543        return v & 0xffff
     1544
     1545    _fmt = '!HBB'
     1546    _fmt_size = struct.calcsize(_fmt)
     1547
     1548    def decode(self, strio, length):
     1549        if length < self._fmt_size:
     1550            raise Exception('too short')
     1551        hdr = readPrecisely(strio, self._fmt_size)
     1552        self.flags, self.protocol, self.algorithm = struct.unpack(self._fmt, hdr)
     1553        length -= self._fmt_size
     1554
     1555        self.key = readPrecisely(strio, length)
     1556
     1557    def encode(self, strio, compDict=None):
     1558        strio.write(struct.pack(self._fmt, self.flags, self.protocol, self.algorithm))
     1559        strio.write(self.key)
     1560
     1561    def verify(self, body, sig):
     1562        if self.algorithm==5:
     1563            return self.verify_rsa_sha1(body, sig)
     1564        raise Exception('unhandled algorithm')
     1565
     1566    def verify_rsa_sha1(self, data, sigvalue):
     1567        # key is either:
     1568        # 1-255=N (exponent len)
     1569        # N bytes exponent
     1570        # rest modulus
     1571        #
     1572        # or
     1573        #
     1574        # \x00
     1575        # 2 bytes exponent len
     1576        # N bytes exponent
     1577        # rest modulus
     1578        key = self.key
     1579        if key[0]=='\x00':
     1580            _ignore, explen = struct.unpack('!BH', key[3:])
     1581            key = key[3:]
     1582        else:
     1583            explen = ord(key[0])
     1584            key = key[1:]
     1585
     1586        exponent = Crypto.Util.number.bytes_to_long(key[:explen])
     1587        modbytes = key[explen:]
     1588        modlen = len(modbytes)
     1589        modulus = Crypto.Util.number.bytes_to_long(modbytes)
     1590
     1591        # RSA-SHA1
     1592        # RFC3110 section 3
     1593        hash = hashlib.new('sha1', data).digest()
     1594        prefix = '\x30\x21\x30\x09\x06\x05\x2B\x0E\x03\x02\x1A\x05\x00\x04\x14'
     1595        padlen = modlen - len(hash) - len(prefix) - 3
     1596        hash = '\x01' + '\xff'*padlen + '\x00' + prefix + hash
     1597
     1598        #print "verifying"
     1599        #print self.tag()
     1600        #print exponent
     1601        #for k in ('modbytes', 'data', 'hash', 'sigvalue'):
     1602        #    v = locals()[k]
     1603        #    print "%10s %4d %r" % (k, len(v), v)
     1604
     1605        # signature -> number
     1606        sig = Crypto.Util.number.bytes_to_long(sigvalue)
     1607
     1608        # verify
     1609        key = Crypto.PublicKey.RSA.construct((modulus, exponent))
     1610
     1611        return key.verify(hash, (sig,''))
     1612       
     1613
    14181614# Oh god, Record_TXT how I hate thee.
    14191615class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
    14201616    """