diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 0000000..6a691e0 --- /dev/null +++ b/.bzrignore @@ -0,0 +1 @@ +/.testrepository diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..3f13aff --- /dev/null +++ b/.testr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_command=PYTHONPATH=. python -m subunit.run $LISTOPT $IDOPTION dkim.tests.test_dkim +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/README b/README index d638456..cebb3e1 100644 --- a/README +++ b/README @@ -21,6 +21,17 @@ To build and install pydkim: python setup.py install +TESTING + +To run pydkim's test suite: + + PYTHONPATH=. python dkim/tests/test_dkim.py + +Alternatively, if you have testrepository installed: + + testr init + testr run + USAGE The pydkim library offers one module called dkim. The sign() function takes an diff --git a/dkim.py b/dkim/__init__.py similarity index 97% rename from dkim.py rename to dkim/__init__.py index 23618bd..b694a07 100644 --- a/dkim.py +++ b/dkim/__init__.py @@ -396,7 +396,7 @@ def sign(message, selector, domain, privkey, identity=None, canonicalize=(Simple return sig + "\r\n" -def verify(message, debuglog=None): +def verify(message, debuglog=None, dnsfunc=dnstxt): """Verify a DKIM signature on an RFC822 formatted message. @param message: an RFC822 formatted message (with either \\n or \\r\\n line endings) @@ -548,7 +548,7 @@ def verify(message, debuglog=None): print >>debuglog, "body hash mismatch (got %s, expected %s)" % (base64.b64encode(bodyhash), sig['bh']) return False - s = dnstxt(sig['s']+"._domainkey."+sig['d']+".") + s = dnsfunc(sig['s']+"._domainkey."+sig['d']+".") if not s: return False a = re.split(r"\s*;\s*", s) @@ -629,11 +629,3 @@ def verify(message, debuglog=None): assert len(v) == len(sig2) # Byte-by-byte compare of signatures return not [1 for x in zip(v, sig2) if x[0] != x[1]] - -if __name__ == "__main__": - message = """From: greg@hewgill.com\r\nSubject: test\r\n message\r\n\r\nHi.\r\n\r\nWe lost the game. Are you hungry yet?\r\n\r\nJoe.\r\n""" - print rfc822_parse(message) - sig = sign(message, "greg", "hewgill.com", open("/home/greg/.domainkeys/rsa.private").read()) - print sig - print verify(sig+message) - #print sign(open("/home/greg/tmp/message").read(), "greg", "hewgill.com", open("/home/greg/.domainkeys/rsa.private").read()) diff --git a/dkim/tests/__init__.py b/dkim/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dkim/tests/data/test.message b/dkim/tests/data/test.message new file mode 100644 index 0000000..8a7254e --- /dev/null +++ b/dkim/tests/data/test.message @@ -0,0 +1,8 @@ +Received: from localhost +Message-ID: +Date: Mon, 01 Jan 2011 01:02:03 +0400 +From: Test User +To: somebody@example.com +Subject: Testing + +This is a test message. diff --git a/dkim/tests/data/test.private b/dkim/tests/data/test.private new file mode 100644 index 0000000..3800be0 --- /dev/null +++ b/dkim/tests/data/test.private @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQi +Y/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqM +KrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB +AoGAH0cxOhFZDgzXWhDhnAJDw5s4roOXN4OhjiXa8W7Y3rhX3FJqmJSPuC8N9vQm +6SVbaLAE4SG5mLMueHlh4KXffEpuLEiNp9Ss3O4YfLiQpbRqE7Tm5SxKjvvQoZZe +zHorimOaChRL2it47iuWxzxSiRMv4c+j70GiWdxXnxe4UoECQQDzJB/0U58W7RZy +6enGVj2kWF732CoWFZWzi1FicudrBFoy63QwcowpoCazKtvZGMNlPWnC7x/6o8Gc +uSe0ga2xAkEA8C7PipPm1/1fTRQvj1o/dDmZp243044ZNyxjg+/OPN0oWCbXIGxy +WvmZbXriOWoSALJTjExEgraHEgnXssuk7QJBALl5ICsYMu6hMxO73gnfNayNgPxd +WFV6Z7ULnKyV7HSVYF0hgYOHjeYe9gaMtiJYoo0zGN+L3AAtNP9huqkWlzECQE1a +licIeVlo1e+qJ6Mgqr0Q7Aa7falZ448ccbSFYEPD6oFxiOl9Y9se9iYHZKKfIcst +o7DUw1/hz2Ck4N5JrgUCQQCyKveNvjzkkd8HjYs0SwM0fPjK16//5qDZ2UiDGnOe +uEzxBDAr518Z8VFbR41in3W4Y3yCDgQlLlcETrS+zYcL +-----END RSA PRIVATE KEY----- diff --git a/dkim/tests/data/test.txt b/dkim/tests/data/test.txt new file mode 100644 index 0000000..5976ea7 --- /dev/null +++ b/dkim/tests/data/test.txt @@ -0,0 +1 @@ +v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB diff --git a/dkim/tests/test_dkim.py b/dkim/tests/test_dkim.py new file mode 100644 index 0000000..aecc326 --- /dev/null +++ b/dkim/tests/test_dkim.py @@ -0,0 +1,72 @@ +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the author be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# +# Copyright (c) 2011 William Grant + +import os.path +import unittest + +import dkim + + +def read_test_data(filename): + """Get the content of the given test data file. + + The files live in dkim/tests/data. + """ + path = os.path.join(os.path.dirname(__file__), 'data', filename) + return open(path).read() + + +class TestFold(unittest.TestCase): + + def test_short_line(self): + self.assertEqual( + "foo", dkim.fold("foo")) + + def DISABLED_test_long_line(self): + # The function is terribly broken, not passing even this simple + # test. + self.assertEqual( + "foo"*24 + "\r\n foo", dkim.fold("foo" * 25)) + + +class TestSignAndVerify(unittest.TestCase): + """End-to-end signature and verification tests.""" + + def setUp(self): + self.message = read_test_data("test.message") + self.key = read_test_data("test.private") + + def dnsfunc(self, domain): + self.assertEqual('test._domainkey.example.com.', domain) + return read_test_data("test.txt") + + def test_verifies(self): + # A message verifies after being signed. + sig = dkim.sign(self.message, "test", "example.com", self.key) + res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) + self.assertTrue(res) + + def test_altered_body_fails(self): + # An altered body fails verification. + sig = dkim.sign(self.message, "test", "example.com", self.key) + res = dkim.verify(sig + self.message + "foo", dnsfunc=self.dnsfunc) + self.assertFalse(res) + + +if __name__ == '__main__': + unittest.main()