Package dkim :: Package tests :: Module test_dkim_ed25519
[hide private]
[frames] | no frames]

Source Code for Module dkim.tests.test_dkim_ed25519

  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  # Copyright (c) 2018 Scott Kitterman <scott@kitterman.com> 
 19   
 20  import email 
 21  import os.path 
 22  import unittest 
 23  import time 
 24   
 25  import dkim 
 26   
 27   
28 -def read_test_data(filename):
29 """Get the content of the given test data file. 30 31 The files live in dkim/tests/data. 32 """ 33 path = os.path.join(os.path.dirname(__file__), 'data', filename) 34 with open(path, 'rb') as f: 35 return f.read()
36 37
38 -class TestFold(unittest.TestCase):
39
40 - def test_short_line(self):
41 self.assertEqual( 42 b"foo", dkim.fold(b"foo"))
43
44 - def test_long_line(self):
45 # The function is terribly broken, not passing even this simple 46 # test. 47 self.assertEqual( 48 b"foo" * 24 + b"\r\n foo", dkim.fold(b"foo" * 25))
49 50
51 -class TestSignAndVerify(unittest.TestCase):
52 """End-to-end signature and verification tests.""" 53
54 - def setUp(self):
55 self.message = read_test_data("ed25519test.msg") 56 self.message2 = read_test_data("ed25519test2.msg") 57 self.message3 = read_test_data("rfc6376.msg") 58 self.message4 = read_test_data("rfc6376.signed.msg") 59 self.key = read_test_data("ed25519test.key") 60 self.rfckey = read_test_data("rfc8032_7_1.key")
61
62 - def dnsfunc(self, domain, timeout=5):
63 sample_dns = """\ 64 k=ed25519; \ 65 p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=""" 66 67 _dns_responses = { 68 'example._domainkey.canonical.com.': sample_dns, 69 'test._domainkey.example.net.': """v=DKIM1; k=ed25519; \ 70 p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=""", 71 'sed._domainkey.test.ex.': read_test_data("eximtest.dns"), 72 'brisbane._domainkey.football.example.com.': """v=DKIM1; k=ed25519; \ 73 p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=""" 74 } 75 try: 76 domain = domain.decode('ascii') 77 except UnicodeDecodeError: 78 return None 79 self.assertTrue(domain in _dns_responses,domain) 80 return _dns_responses[domain]
81
82 - def test_verifies(self):
83 # A message verifies after being signed. 84 for header_algo in (b"simple", b"relaxed"): 85 for body_algo in (b"simple", b"relaxed"): 86 sig = dkim.sign( 87 self.message, b"test", b"example.net", self.key, 88 canonicalize=(header_algo, body_algo), signature_algorithm=b'ed25519-sha256') 89 res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) 90 self.assertTrue(res)
91
92 - def test_rfc8032_verifies(self):
93 # A message using RFC 8032 sample keys verifies after being signed. 94 for header_algo in (b"simple", b"relaxed"): 95 for body_algo in (b"simple", b"relaxed"): 96 sig = dkim.sign( 97 self.message3, b"brisbane", b"football.example.com", self.rfckey, 98 canonicalize=(header_algo, body_algo), signature_algorithm=b'ed25519-sha256') 99 res = dkim.verify(sig + self.message3, dnsfunc=self.dnsfunc) 100 self.assertTrue(res)
101
103 # A message previously signed using RFC 8032 sample keys verifies after being signed. 104 for header_algo in (b"simple", b"relaxed"): 105 for body_algo in (b"simple", b"relaxed"): 106 sig = dkim.sign( 107 self.message3, b"brisbane", b"football.example.com", self.rfckey, 108 canonicalize=(header_algo, body_algo), signature_algorithm=b'ed25519-sha256') 109 d = dkim.DKIM(self.message4) 110 res = d.verify(dnsfunc=self.dnsfunc) 111 self.assertTrue(res)
112
113 - def test_simple_signature(self):
114 # A message verifies after being signed with SHOULD headers 115 for header_algo in (b"simple", b"relaxed"): 116 for body_algo in (b"simple", b"relaxed"): 117 sig = dkim.sign( 118 self.message, b"test", b"example.net", self.key, 119 canonicalize=(header_algo, body_algo), 120 include_headers=(b'from',) + dkim.DKIM.SHOULD, 121 signature_algorithm=b'ed25519-sha256') 122 res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) 123 self.assertTrue(res)
124
125 - def test_verify_third_party(self):
126 # Message signed by prototype Exim implementation 127 res = dkim.verify(self.message2, dnsfunc=self.dnsfunc) 128 self.assertTrue(res)
129
130 - def test_add_body_length(self):
131 sig = dkim.sign( 132 self.message, b"test", b"example.net", self.key, length=True, 133 signature_algorithm=b'ed25519-sha256') 134 msg = email.message_from_string(self.message.decode('utf-8')) 135 self.assertIn('; l=%s' % len(msg.get_payload() + '\n'), sig.decode('utf-8')) 136 res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) 137 self.assertTrue(res)
138
139 - def test_altered_body_fails(self):
140 # An altered body fails verification. 141 for header_algo in (b"simple", b"relaxed"): 142 for body_algo in (b"simple", b"relaxed"): 143 sig = dkim.sign( 144 self.message, b"test", b"example.net", self.key, 145 signature_algorithm=b'ed25519-sha256') 146 res = dkim.verify( 147 sig + self.message + b"foo", dnsfunc=self.dnsfunc) 148 self.assertFalse(res)
149
151 # Domains should be ASCII. Bad ASCII causes verification to fail. 152 sig = dkim.sign(self.message, b"test", b"example.net\xe9", self.key, 153 signature_algorithm=b'ed25519-sha256') 154 res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) 155 self.assertFalse(res)
156
158 # <https://bugs.launchpad.net/ubuntu/+source/pydkim/+bug/587783> 159 # Relaxed-mode header signing is wrong 160 # <https://bugs.launchpad.net/dkimpy/+bug/939128> 161 # Simple-mode signature header verification is wrong 162 # (should ignore FWS anywhere in signature tag: b=) 163 sample_msg = b"""\ 164 From: mbp@canonical.com 165 To: scottk@example.net 166 Subject: this is my 167 test message 168 """.replace(b'\n', b'\r\n') 169 170 sample_privkey = b"""\ 171 fL+5V9EquCZAovKik3pA6Lk9zwCzoEtjIuIqK9ZXHHA=\ 172 """ 173 174 sample_pubkey = """\ 175 yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=\ 176 """ 177 178 for header_mode in [dkim.Relaxed, dkim.Simple]: 179 180 dkim_header = dkim.sign(sample_msg, b'example', b'canonical.com', 181 sample_privkey, canonicalize=(header_mode, dkim.Relaxed), 182 signature_algorithm=b'ed25519-sha256') 183 # Folding dkim_header affects b= tag only, since dkim.sign folds 184 # sig_value with empty b= before hashing, and then appends the 185 # signature. So folding dkim_header again adds FWS to 186 # the b= tag only. This should be ignored even with 187 # simple canonicalization. 188 # http://tools.ietf.org/html/rfc4871#section-3.5 189 signed = dkim.fold(dkim_header) + sample_msg 190 result = dkim.verify(signed,dnsfunc=self.dnsfunc) 191 self.assertTrue(result) 192 dkim_header = dkim.fold(dkim_header) 193 # use a tab for last fold to test tab in FWS bug 194 pos = dkim_header.rindex(b'\r\n ') 195 dkim_header = dkim_header[:pos]+b'\r\n\t'+dkim_header[pos+3:] 196 result = dkim.verify(dkim_header + sample_msg, 197 dnsfunc=self.dnsfunc) 198 self.assertTrue(result)
199
200 - def test_extra_headers(self):
201 # <https://bugs.launchpad.net/dkimpy/+bug/737311> 202 # extra headers above From caused failure 203 #message = read_test_data("test_extra.message") 204 message = read_test_data("message.mbox") 205 for header_algo in (b"simple", b"relaxed"): 206 for body_algo in (b"simple", b"relaxed"): 207 d = dkim.DKIM(message) 208 # bug requires a repeated header to manifest 209 d.should_not_sign.remove(b'received') 210 sig = d.sign(b"test", b"example.net", self.key, 211 signature_algorithm=b'ed25519-sha256', 212 include_headers=d.all_sign_headers(), 213 canonicalize=(header_algo, body_algo)) 214 dv = dkim.DKIM(sig + message) 215 res = dv.verify(dnsfunc=self.dnsfunc) 216 self.assertEqual(d.include_headers,dv.include_headers) 217 s = dkim.select_headers(d.headers,d.include_headers) 218 sv = dkim.select_headers(dv.headers,dv.include_headers) 219 self.assertEqual(s,sv) 220 self.assertTrue(res)
221
222 - def test_multiple_from_fails(self):
223 # <https://bugs.launchpad.net/dkimpy/+bug/644046> 224 # additional From header fields should cause verify failure 225 hfrom = b'From: "Resident Evil" <sales@spammer.com>\r\n' 226 h,b = self.message.split(b'\n\n',1) 227 for header_algo in (b"simple", b"relaxed"): 228 for body_algo in (b"simple", b"relaxed"): 229 sig = dkim.sign( 230 self.message, b"test", b"example.net", self.key, 231 signature_algorithm=b'ed25519-sha256') 232 # adding an unknown header still verifies 233 h1 = h+b'\r\n'+b'X-Foo: bar' 234 message = b'\n\n'.join((h1,b)) 235 res = dkim.verify(sig+message, dnsfunc=self.dnsfunc) 236 self.assertTrue(res) 237 # adding extra from at end should not verify 238 h1 = h+b'\r\n'+hfrom.strip() 239 message = b'\n\n'.join((h1,b)) 240 res = dkim.verify(sig+message, dnsfunc=self.dnsfunc) 241 self.assertFalse(res) 242 # add extra from in front should not verify either 243 h1 = hfrom+h 244 message = b'\n\n'.join((h1,b)) 245 res = dkim.verify(sig+message, dnsfunc=self.dnsfunc) 246 self.assertFalse(res)
247
248 - def test_no_from_fails(self):
249 # Body From is mandatory to be in the message and mandatory to sign 250 sigerror = False 251 sig = '' 252 message = read_test_data('test_nofrom.message') 253 selector = 'test' 254 domain = 'example.net' 255 identity = None 256 try: 257 sig = dkim.sign(message, selector, domain, 258 read_test_data('ed25519test.key'), identity = identity, 259 signature_algorithm=b'ed25519-sha256') 260 except dkim.ParameterError as x: 261 sigerror = True 262 self.assertTrue(sigerror)
263
265 sig = {b'v': b'1', 266 b'a': b'ed25519-sha256', 267 b'b': b'K/UUOt8lCtgjp3kSTogqBm9lY1Yax/NwZ+bKm39/WKzo5KYe3L/6RoIA/0oiDX4kO\n \t Qut49HCV6ZUe6dY9V5qWBwLanRs1sCnObaOGMpFfs8tU4TWpDSVXaNZAqn15XVW0WH\n \t EzOzUfVuatpa1kF4voIgSbmZHR1vN3WpRtcTBe/I=', 268 b'bh': b'n0HUwGCP28PkesXBPH82Kboy8LhNFWU9zUISIpAez7M=', 269 b'c': b'simple/simple', 270 b'd': b'kitterman.com', 271 b'i': b'scott@Kitterman.com', 272 b'h': b'From:To:Subject:Date:Cc:MIME-Version:Content-Type:\n \t Content-Transfer-Encoding:Message-Id', 273 b's': b'2007-00', 274 b't': b'1299525798'} 275 dkim.validate_signature_fields(sig) 276 # try new version 277 sigVer = sig.copy() 278 sigVer[b'v'] = 2 279 self.assertRaises(dkim.ValidationError, dkim.validate_signature_fields, sigVer) 280 # try with x 281 sigX = sig.copy() 282 sigX[b'x'] = b'1399525798' 283 dkim.validate_signature_fields(sig) 284 # try with late t 285 sigX[b't'] = b'1400000000' 286 self.assertRaises(dkim.ValidationError, dkim.validate_signature_fields, sigX) 287 # try without t 288 now = int(time.time()) 289 sigX[b'x'] = str(now+400000).encode('ascii') 290 dkim.validate_signature_fields(sigX) 291 # try when expired a day ago 292 sigX[b'x'] = str(now - 24*3600).encode('ascii') 293 self.assertRaises(dkim.ValidationError, dkim.validate_signature_fields, sigX)
294 295
296 -def test_suite():
297 from unittest import TestLoader 298 return TestLoader().loadTestsFromName(__name__)
299