1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
2
3# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
4#
5# Permission to use, copy, modify, and distribute this software and its
6# documentation for any purpose with or without fee is hereby granted,
7# provided that the above copyright notice and this permission notice
8# appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18import base64
19import calendar
20import struct
21import time
22
23import dns.dnssec
24import dns.exception
25import dns.rdata
26import dns.rdatatype
27
28
29class BadSigTime(dns.exception.DNSException):
30
31    """Time in DNS SIG or RRSIG resource record cannot be parsed."""
32
33
34def sigtime_to_posixtime(what):
35    if len(what) != 14:
36        raise BadSigTime
37    year = int(what[0:4])
38    month = int(what[4:6])
39    day = int(what[6:8])
40    hour = int(what[8:10])
41    minute = int(what[10:12])
42    second = int(what[12:14])
43    return calendar.timegm((year, month, day, hour, minute, second,
44                            0, 0, 0))
45
46
47def posixtime_to_sigtime(what):
48    return time.strftime('%Y%m%d%H%M%S', time.gmtime(what))
49
50
51class RRSIG(dns.rdata.Rdata):
52
53    """RRSIG record
54
55    @ivar type_covered: the rdata type this signature covers
56    @type type_covered: int
57    @ivar algorithm: the algorithm used for the sig
58    @type algorithm: int
59    @ivar labels: number of labels
60    @type labels: int
61    @ivar original_ttl: the original TTL
62    @type original_ttl: long
63    @ivar expiration: signature expiration time
64    @type expiration: long
65    @ivar inception: signature inception time
66    @type inception: long
67    @ivar key_tag: the key tag
68    @type key_tag: int
69    @ivar signer: the signer
70    @type signer: dns.name.Name object
71    @ivar signature: the signature
72    @type signature: string"""
73
74    __slots__ = ['type_covered', 'algorithm', 'labels', 'original_ttl',
75                 'expiration', 'inception', 'key_tag', 'signer',
76                 'signature']
77
78    def __init__(self, rdclass, rdtype, type_covered, algorithm, labels,
79                 original_ttl, expiration, inception, key_tag, signer,
80                 signature):
81        super(RRSIG, self).__init__(rdclass, rdtype)
82        self.type_covered = type_covered
83        self.algorithm = algorithm
84        self.labels = labels
85        self.original_ttl = original_ttl
86        self.expiration = expiration
87        self.inception = inception
88        self.key_tag = key_tag
89        self.signer = signer
90        self.signature = signature
91
92    def covers(self):
93        return self.type_covered
94
95    def to_text(self, origin=None, relativize=True, **kw):
96        return '%s %d %d %d %s %s %d %s %s' % (
97            dns.rdatatype.to_text(self.type_covered),
98            self.algorithm,
99            self.labels,
100            self.original_ttl,
101            posixtime_to_sigtime(self.expiration),
102            posixtime_to_sigtime(self.inception),
103            self.key_tag,
104            self.signer.choose_relativity(origin, relativize),
105            dns.rdata._base64ify(self.signature)
106        )
107
108    @classmethod
109    def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
110        type_covered = dns.rdatatype.from_text(tok.get_string())
111        algorithm = dns.dnssec.algorithm_from_text(tok.get_string())
112        labels = tok.get_int()
113        original_ttl = tok.get_ttl()
114        expiration = sigtime_to_posixtime(tok.get_string())
115        inception = sigtime_to_posixtime(tok.get_string())
116        key_tag = tok.get_int()
117        signer = tok.get_name()
118        signer = signer.choose_relativity(origin, relativize)
119        chunks = []
120        while 1:
121            t = tok.get().unescape()
122            if t.is_eol_or_eof():
123                break
124            if not t.is_identifier():
125                raise dns.exception.SyntaxError
126            chunks.append(t.value.encode())
127        b64 = b''.join(chunks)
128        signature = base64.b64decode(b64)
129        return cls(rdclass, rdtype, type_covered, algorithm, labels,
130                   original_ttl, expiration, inception, key_tag, signer,
131                   signature)
132
133    def to_wire(self, file, compress=None, origin=None):
134        header = struct.pack('!HBBIIIH', self.type_covered,
135                             self.algorithm, self.labels,
136                             self.original_ttl, self.expiration,
137                             self.inception, self.key_tag)
138        file.write(header)
139        self.signer.to_wire(file, None, origin)
140        file.write(self.signature)
141
142    @classmethod
143    def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
144        header = struct.unpack('!HBBIIIH', wire[current: current + 18])
145        current += 18
146        rdlen -= 18
147        (signer, cused) = dns.name.from_wire(wire[: current + rdlen], current)
148        current += cused
149        rdlen -= cused
150        if origin is not None:
151            signer = signer.relativize(origin)
152        signature = wire[current: current + rdlen].unwrap()
153        return cls(rdclass, rdtype, header[0], header[1], header[2],
154                   header[3], header[4], header[5], header[6], signer,
155                   signature)
156
157    def choose_relativity(self, origin=None, relativize=True):
158        self.signer = self.signer.choose_relativity(origin, relativize)
159