1# This software is provided 'as-is', without any express or implied
2# warranty.  In no event will the author be held liable for any damages
3# arising from the use of this software.
4#
5# Permission is granted to anyone to use this software for any purpose,
6# including commercial applications, and to alter it and redistribute it
7# freely, subject to the following restrictions:
8#
9# 1. The origin of this software must not be misrepresented; you must not
10#    claim that you wrote the original software. If you use this software
11#    in a product, an acknowledgment in the product documentation would be
12#    appreciated but is not required.
13# 2. Altered source versions must be plainly marked as such, and must not be
14#    misrepresented as being the original software.
15# 3. This notice may not be removed or altered from any source distribution.
16#
17# Copyright (c) 2011 William Grant <me@williamgrant.id.au>
18
19import email
20import os.path
21import unittest
22import time
23
24import dkim
25
26
27def read_test_data(filename):
28    """Get the content of the given test data file.
29
30    The files live in dkim/tests/data.
31    """
32    path = os.path.join(os.path.dirname(__file__), 'data', filename)
33    with open(path, 'rb') as f:
34        return f.read()
35
36
37class TestFold(unittest.TestCase):
38
39    def test_short_line(self):
40        self.assertEqual(
41            b"foo", dkim.fold(b"foo"))
42
43    def test_long_line(self):
44        # The function is terribly broken, not passing even this simple
45        # test.
46        self.assertEqual(
47            b"foo" * 24 + b"\r\n foo", dkim.fold(b"foo" * 25))
48
49    def test_linesep(self):
50        self.assertEqual(
51            b"foo" * 24 + b"\n foo", dkim.fold(b"foo" * 25, linesep=b"\n"))
52
53
54
55class TestSignAndVerify(unittest.TestCase):
56    """End-to-end signature and verification tests."""
57
58    def setUp(self):
59        self.message = read_test_data("test.message")
60        self.message3 = read_test_data("rfc6376.msg")
61        self.message4 = read_test_data("rfc6376.signed.msg")
62        self.message5 = read_test_data("rfc6376.signed.rsa.msg")
63        self.key = read_test_data("test.private")
64        self.rfckey = read_test_data("rfc8032_7_1.key")
65
66    def dnsfunc(self, domain, timeout=5):
67        sample_dns = """\
68k=rsa; s=email;\
69p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\
70b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ=="""
71
72        _dns_responses = {
73          'example._domainkey.canonical.com.': sample_dns,
74          'test._domainkey.example.com.': read_test_data("test.txt"),
75          '20120113._domainkey.gmail.com.': """k=rsa; \
76p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Kd87/UeJjenpabgbFwh\
77+eBCsSTrqmwIYYvywlbhbqoo2DymndFkbjOVIPIldNs/m40KF+yzMn1skyoxcTUGCQ\
78s8g3FgD2Ap3ZB5DekAo5wMmk4wimDO+U8QzI3SD07y2+07wlNWwIt8svnxgdxGkVbb\
79hzY8i+RQ9DpSVpPbF7ykQxtKXkv/ahW3KjViiAH+ghvvIhkx4xYSIc9oSwVmAl5Oct\
80MEeWUwg8Istjqz8BZeTWbf41fbNhte7Y+YqZOwq1Sd0DbvYAD9NOZK9vlfuac0598H\
81Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
82        }
83        try:
84            domain = domain.decode('ascii')
85        except UnicodeDecodeError:
86            return None
87        self.assertTrue(domain in _dns_responses,domain)
88        return _dns_responses[domain]
89
90    def dnsfunc2(self, domain, timeout=5):
91        sample_dns = """\
92k=rsa; \
93p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\
94b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ=="""
95
96        _dns_responses = {
97          'example._domainkey.canonical.com.': sample_dns,
98          'test._domainkey.example.com.': read_test_data("test2.txt"),
99          '20120113._domainkey.gmail.com.': """\
100p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Kd87/UeJjenpabgbFwh\
101+eBCsSTrqmwIYYvywlbhbqoo2DymndFkbjOVIPIldNs/m40KF+yzMn1skyoxcTUGCQ\
102s8g3FgD2Ap3ZB5DekAo5wMmk4wimDO+U8QzI3SD07y2+07wlNWwIt8svnxgdxGkVbb\
103hzY8i+RQ9DpSVpPbF7ykQxtKXkv/ahW3KjViiAH+ghvvIhkx4xYSIc9oSwVmAl5Oct\
104MEeWUwg8Istjqz8BZeTWbf41fbNhte7Y+YqZOwq1Sd0DbvYAD9NOZK9vlfuac0598H\
105Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
106        }
107        try:
108            domain = domain.decode('ascii')
109        except UnicodeDecodeError:
110            return None
111        self.assertTrue(domain in _dns_responses,domain)
112        return _dns_responses[domain]
113
114    def dnsfunc3(self, domain, timeout=5):
115        sample_dns = """\
116k=rsa; \
117p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\
118b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ=="""
119
120        _dns_responses = {
121          'example._domainkey.canonical.com.': sample_dns,
122          'test._domainkey.example.com.': read_test_data("badversion.txt"),
123          '20120113._domainkey.gmail.com.': """\
124p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Kd87/UeJjenpabgbFwh\
125+eBCsSTrqmwIYYvywlbhbqoo2DymndFkbjOVIPIldNs/m40KF+yzMn1skyoxcTUGCQ\
126s8g3FgD2Ap3ZB5DekAo5wMmk4wimDO+U8QzI3SD07y2+07wlNWwIt8svnxgdxGkVbb\
127hzY8i+RQ9DpSVpPbF7ykQxtKXkv/ahW3KjViiAH+ghvvIhkx4xYSIc9oSwVmAl5Oct\
128MEeWUwg8Istjqz8BZeTWbf41fbNhte7Y+YqZOwq1Sd0DbvYAD9NOZK9vlfuac0598H\
129Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
130        }
131        try:
132            domain = domain.decode('ascii')
133        except UnicodeDecodeError:
134            return None
135        self.assertTrue(domain in _dns_responses,domain)
136        return _dns_responses[domain]
137
138    def dnsfunc4(self, domain, timeout=5):
139        sample_dns = """\
140k=rsa; \
141p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\
142b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ=="""
143
144        _dns_responses = {
145          'example._domainkey.canonical.com.': sample_dns,
146          'test._domainkey.example.com.': read_test_data("badk.txt"),
147          '20120113._domainkey.gmail.com.': """\
148p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Kd87/UeJjenpabgbFwh\
149+eBCsSTrqmwIYYvywlbhbqoo2DymndFkbjOVIPIldNs/m40KF+yzMn1skyoxcTUGCQ\
150s8g3FgD2Ap3ZB5DekAo5wMmk4wimDO+U8QzI3SD07y2+07wlNWwIt8svnxgdxGkVbb\
151hzY8i+RQ9DpSVpPbF7ykQxtKXkv/ahW3KjViiAH+ghvvIhkx4xYSIc9oSwVmAl5Oct\
152MEeWUwg8Istjqz8BZeTWbf41fbNhte7Y+YqZOwq1Sd0DbvYAD9NOZK9vlfuac0598H\
153Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
154        }
155        try:
156            domain = domain.decode('ascii')
157        except UnicodeDecodeError:
158            return None
159        self.assertTrue(domain in _dns_responses,domain)
160        return _dns_responses[domain]
161
162    def dnsfunc5(self, domain, timeout=5):
163        sample_dns = """\
164k=rsa; \
165p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\
166b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ=="""
167
168        _dns_responses = {
169          'example._domainkey.canonical.com.': sample_dns,
170          'test._domainkey.football.example.com.': read_test_data("test.txt"),
171          'brisbane._domainkey.football.example.com.': """v=DKIM1; k=ed25519; \
172p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="""
173        }
174        try:
175            domain = domain.decode('ascii')
176        except UnicodeDecodeError:
177            return None
178        self.assertTrue(domain in _dns_responses,domain)
179        return _dns_responses[domain]
180
181    def dnsfunc6(self, domain, timeout=5):
182        sample_dns = """\
183k=rsa; \
184p=MFwwDQYJKoZIhvNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\
185b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ=="""
186
187        _dns_responses = {
188          'test._domainkey.football.example.com.': sample_dns,
189          'brisbane._domainkey.football.example.com.': """v=DKIM1; k=ed25519; \
190p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="""
191        }
192        try:
193            domain = domain.decode('ascii')
194        except UnicodeDecodeError:
195            return None
196        self.assertTrue(domain in _dns_responses,domain)
197        return _dns_responses[domain]
198
199    def test_verifies(self):
200        # A message verifies after being signed.
201        for header_algo in (b"simple", b"relaxed"):
202            for body_algo in (b"simple", b"relaxed"):
203                sig = dkim.sign(
204                    self.message, b"test", b"example.com", self.key,
205                    canonicalize=(header_algo, body_algo))
206                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
207                self.assertTrue(res)
208
209    def test_verifies_nosig(self):
210        # A message without signature does not verify.
211        res = dkim.verify(self.message, dnsfunc=self.dnsfunc)
212        self.assertFalse(res)
213
214    def test_double_verifies(self):
215        # A message also containing a ed25519 signature verifies after being signed with rsa.
216        for header_algo in (b"simple", b"relaxed"):
217            for body_algo in (b"simple", b"relaxed"):
218                sig = dkim.sign(
219                    self.message3, b"test", b"football.example.com", self.key,
220                    canonicalize=(header_algo, body_algo), signature_algorithm=b'rsa-sha256')
221                res = dkim.verify(sig + self.message3, dnsfunc=self.dnsfunc5)
222                self.assertTrue(res)
223
224    def test_double_previous_verifies(self):
225        # A message previously signed using both rsa and ed25519 verifies after being signed.
226        for header_algo in (b"simple", b"relaxed"):
227            for body_algo in (b"simple", b"relaxed"):
228                sig = dkim.sign(
229                    self.message3, b"test", b"football.example.com", self.key,
230                    canonicalize=(header_algo, body_algo), signature_algorithm=b'rsa-sha256')
231                d = dkim.DKIM(self.message4)
232                res = d.verify(dnsfunc=self.dnsfunc5)
233                self.assertTrue(res)
234
235    def test_catch_bad_key(self):
236        # Raise correct error for defective public key.
237        d = dkim.DKIM(self.message5)
238        res = d.verify(dnsfunc=self.dnsfunc6)
239        self.assertFalse(res)
240
241    def test_verifies_lflinesep(self):
242        # A message verifies after being signed.
243        for header_algo in (b"simple", b"relaxed"):
244            for body_algo in (b"simple", b"relaxed"):
245                sig = dkim.sign(
246                    self.message, b"test", b"example.com", self.key,
247                    canonicalize=(header_algo, body_algo), linesep=b"\n")
248                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
249                self.assertFalse(b'\r\n' in sig)
250                self.assertTrue(res)
251
252    def test_implicit_k(self):
253        # A message verifies after being signed when k= tag is not provided.
254        for header_algo in (b"simple", b"relaxed"):
255            for body_algo in (b"simple", b"relaxed"):
256                sig = dkim.sign(
257                    self.message, b"test", b"example.com", self.key,
258                    canonicalize=(header_algo, body_algo))
259                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc2)
260                self.assertTrue(res)
261
262    def test_bad_version(self):
263        # A error is detected if a bad version is used.
264        for header_algo in (b"simple", b"relaxed"):
265            for body_algo in (b"simple", b"relaxed"):
266                sig = dkim.sign(
267                    self.message, b"test", b"example.com", self.key,
268                    canonicalize=(header_algo, body_algo))
269                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc3)
270                self.assertFalse(res)
271
272    def test_unknown_k(self):
273        # A error is detected if an unknown algorithm is in the k= tag.
274        for header_algo in (b"simple", b"relaxed"):
275            for body_algo in (b"simple", b"relaxed"):
276                sig = dkim.sign(
277                    self.message, b"test", b"example.com", self.key,
278                    canonicalize=(header_algo, body_algo))
279                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc4)
280                self.assertFalse(res)
281
282    def test_simple_signature(self):
283        # A message verifies after being signed with SHOULD headers
284        for header_algo in (b"simple", b"relaxed"):
285             for body_algo in (b"simple", b"relaxed"):
286                sig = dkim.sign(
287                    self.message, b"test", b"example.com", self.key,
288                    canonicalize=(header_algo, body_algo),
289                    include_headers=(b'from',) + dkim.DKIM.SHOULD)
290                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
291                self.assertTrue(res)
292
293    def test_string_include(self):
294        # A message can be signed when the include_headers is string
295        for header_algo in (b"simple", b"relaxed"):
296             for body_algo in (b"simple", b"relaxed"):
297                sig = dkim.sign(
298                    self.message, b"test", b"example.com", self.key,
299                    canonicalize=(header_algo, body_algo),
300                    include_headers=('from',) )
301                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
302                self.assertTrue(res)
303
304    def test_add_body_length(self):
305        sig = dkim.sign(
306            self.message, b"test", b"example.com", self.key, length=True)
307        msg = email.message_from_string(self.message.decode('utf-8'))
308        self.assertIn('; l=%s' % len(msg.get_payload() + '\n'), sig.decode('utf-8'))
309        res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
310        self.assertTrue(res)
311
312    def test_altered_body_fails(self):
313        # An altered body fails verification.
314        for header_algo in (b"simple", b"relaxed"):
315            for body_algo in (b"simple", b"relaxed"):
316                sig = dkim.sign(
317                    self.message, b"test", b"example.com", self.key)
318                res = dkim.verify(
319                    sig + self.message + b"foo", dnsfunc=self.dnsfunc)
320                self.assertFalse(res)
321
322    def test_l_verify(self):
323        # Sign with l=, add text, should verify
324        for header_algo in (b"simple", b"relaxed"):
325            for body_algo in (b"simple", b"relaxed"):
326                sig = dkim.sign(
327                    self.message, b"test", b"example.com", self.key,
328                    canonicalize=(header_algo, body_algo), length=True)
329                self.message += b'added more text\n'
330                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
331                self.assertTrue(res)
332
333    def test_present(self):
334        # Test DKIM.present().
335        d = dkim.DKIM(self.message,signature_algorithm=b'rsa-sha256')
336        present = d.present()
337        self.assertFalse(present)
338        sig = d.sign(b"test", b"example.com", self.key)
339        signed = sig + self.message
340        d2 = dkim.DKIM(signed)
341        present = d2.present()
342        self.assertTrue(present)
343
344    def test_badly_encoded_domain_fails(self):
345        # Domains should be ASCII. Bad ASCII causes verification to fail.
346        sig = dkim.sign(self.message, b"test", b"example.com\xe9", self.key)
347        res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
348        self.assertFalse(res)
349
350    def test_dkim_signature_canonicalization(self):
351      # <https://bugs.launchpad.net/ubuntu/+source/pydkim/+bug/587783>
352      # Relaxed-mode header signing is wrong
353      # <https://bugs.launchpad.net/dkimpy/+bug/939128>
354      # Simple-mode signature header verification is wrong
355      # (should ignore FWS anywhere in signature tag: b=)
356      sample_msg = b"""\
357From: mbp@canonical.com
358To: scottk@example.com
359Subject: this is my
360    test message
361""".replace(b'\n', b'\r\n')
362
363      sample_privkey = b"""\
364-----BEGIN RSA PRIVATE KEY-----
365MIIBOwIBAAJBANmBe10IgY+u7h3enWTukkqtUD5PR52Tb/mPfjC0QJTocVBq6Za/
366PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQJAYFUKsD+uMlcFu1D3YNaR
367EGYGXjJ6w32jYGJ/P072M3yWOq2S1dvDthI3nRT8MFjZ1wHDAYHrSpfDNJ3v2fvZ
368cQIhAPgRPmVYn+TGd59asiqG1SZqh+p+CRYHW7B8BsicG5t3AiEA4HYNOohlgWan
3698tKgqLJgUdPFbaHZO1nDyBgvV8hvWZUCIQDDdCq6hYKuKeYUy8w3j7cgJq3ih922
3702qNWwdJCfCWQbwIgTY0cBvQnNe0067WQIpj2pG7pkHZR6qqZ9SE+AjNTHX0CIQCI
371Mgq55Y9MCq5wqzy141rnxrJxTwK9ABo3IAFMWEov3g==
372-----END RSA PRIVATE KEY-----
373"""
374
375      sample_pubkey = """\
376-----BEGIN PUBLIC KEY-----
377MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T
378b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ==
379-----END PUBLIC KEY-----
380"""
381
382      for header_mode in [dkim.Relaxed, dkim.Simple]:
383
384        dkim_header = dkim.sign(sample_msg, b'example', b'canonical.com',
385            sample_privkey, canonicalize=(header_mode, dkim.Relaxed))
386        # Folding dkim_header affects b= tag only, since dkim.sign folds
387        # sig_value with empty b= before hashing, and then appends the
388        # signature.  So folding dkim_header again adds FWS to
389        # the b= tag only.  This should be ignored even with
390        # simple canonicalization.
391        # http://tools.ietf.org/html/rfc4871#section-3.5
392        signed = dkim.fold(dkim_header) + sample_msg
393        result = dkim.verify(signed,dnsfunc=self.dnsfunc,
394                minkey=512)
395        self.assertTrue(result)
396        dkim_header = dkim.fold(dkim_header)
397        # use a tab for last fold to test tab in FWS bug
398        pos = dkim_header.rindex(b'\r\n ')
399        dkim_header = dkim_header[:pos]+b'\r\n\t'+dkim_header[pos+3:]
400        result = dkim.verify(dkim_header + sample_msg,
401                dnsfunc=self.dnsfunc, minkey=512)
402        self.assertTrue(result)
403
404    def test_degenerate_folding(self):
405        # <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=711751>
406        # degenerate folding is ugly but legal
407        message = read_test_data("test2.message")
408        dv = dkim.DKIM(message)
409        res = dv.verify(dnsfunc=self.dnsfunc)
410        self.assertTrue(res)
411
412    def test_extra_headers(self):
413        # <https://bugs.launchpad.net/dkimpy/+bug/737311>
414        # extra headers above From caused failure
415        #message = read_test_data("test_extra.message")
416        message = read_test_data("message.mbox")
417        for header_algo in (b"simple", b"relaxed"):
418            for body_algo in (b"simple", b"relaxed"):
419                d = dkim.DKIM(message)
420                # bug requires a repeated header to manifest
421                d.should_not_sign.remove(b'received')
422                sig = d.sign(b"test", b"example.com", self.key,
423                    include_headers=d.all_sign_headers(),
424                    canonicalize=(header_algo, body_algo))
425                dv = dkim.DKIM(sig + message)
426                res = dv.verify(dnsfunc=self.dnsfunc)
427                self.assertEqual(d.include_headers,dv.include_headers)
428                s = dkim.select_headers(d.headers,d.include_headers)
429                sv = dkim.select_headers(dv.headers,dv.include_headers)
430                self.assertEqual(s,sv)
431                self.assertTrue(res)
432
433    def test_multiple_from_fails(self):
434        # <https://bugs.launchpad.net/dkimpy/+bug/644046>
435        # additional From header fields should cause verify failure
436        hfrom = b'From: "Resident Evil" <sales@spammer.com>\r\n'
437        h,b = self.message.split(b'\n\n',1)
438        for header_algo in (b"simple", b"relaxed"):
439            for body_algo in (b"simple", b"relaxed"):
440                sig = dkim.sign(
441                    self.message, b"test", b"example.com", self.key)
442                # adding an unknown header still verifies
443                h1 = h+b'\r\n'+b'X-Foo: bar'
444                message = b'\n\n'.join((h1,b))
445                res = dkim.verify(sig+message, dnsfunc=self.dnsfunc)
446                self.assertTrue(res)
447                # adding extra from at end should not verify
448                h1 = h+b'\r\n'+hfrom.strip()
449                message = b'\n\n'.join((h1,b))
450                res = dkim.verify(sig+message, dnsfunc=self.dnsfunc)
451                self.assertFalse(res)
452                # add extra from in front should not verify either
453                h1 = hfrom+h
454                message = b'\n\n'.join((h1,b))
455                res = dkim.verify(sig+message, dnsfunc=self.dnsfunc)
456                self.assertFalse(res)
457
458    def test_no_from_fails(self):
459        # Body From is mandatory to be in the message and mandatory to sign
460        sigerror = False
461        sig = ''
462        message = read_test_data('test_nofrom.message')
463        selector = 'test'
464        domain = 'example.com'
465        identity = None
466        try:
467            sig = dkim.sign(message, selector, domain, read_test_data('test.private'), identity = identity)
468        except dkim.ParameterError as x:
469            sigerror = True
470        self.assertTrue(sigerror)
471
472    def test_validate_signature_fields(self):
473      sig = {b'v': b'1',
474      b'a': b'rsa-sha256',
475      b'b': b'K/UUOt8lCtgjp3kSTogqBm9lY1Yax/NwZ+bKm39/WKzo5KYe3L/6RoIA/0oiDX4kO\n \t Qut49HCV6ZUe6dY9V5qWBwLanRs1sCnObaOGMpFfs8tU4TWpDSVXaNZAqn15XVW0WH\n \t EzOzUfVuatpa1kF4voIgSbmZHR1vN3WpRtcTBe/I=',
476      b'bh': b'n0HUwGCP28PkesXBPH82Kboy8LhNFWU9zUISIpAez7M=',
477      b'c': b'simple/simple',
478      b'd': b'kitterman.com',
479      b'i': b'scott@Kitterman.com',
480      b'h': b'From:To:Subject:Date:Cc:MIME-Version:Content-Type:\n \t Content-Transfer-Encoding:Message-Id',
481      b's': b'2007-00',
482      b't': b'1299525798'}
483      dkim.validate_signature_fields(sig)
484      # try new version
485      sigVer = sig.copy()
486      sigVer[b'v'] = 2
487      self.assertRaises(dkim.ValidationError, dkim.validate_signature_fields, sigVer)
488      # try with x
489      sigX = sig.copy()
490      sigX[b'x'] = b'1399525798'
491      dkim.validate_signature_fields(sig)
492      # try with late t
493      sigX[b't'] = b'1400000000'
494      self.assertRaises(dkim.ValidationError, dkim.validate_signature_fields, sigX)
495      # try without t
496      now = int(time.time())
497      sigX[b'x'] = str(now+400000).encode('ascii')
498      dkim.validate_signature_fields(sigX)
499      # try when expired a day ago
500      sigX[b'x'] = str(now - 24*3600).encode('ascii')
501      self.assertRaises(dkim.ValidationError, dkim.validate_signature_fields, sigX)
502
503
504def test_suite():
505    from unittest import TestLoader
506    return TestLoader().loadTestsFromName(__name__)
507