diff options
-rw-r--r-- | tests/crypto_test.py | 328 | ||||
-rw-r--r-- | tests/static/gpg-keys/ambig1-pub.gpg | 30 | ||||
-rw-r--r-- | tests/static/gpg-keys/ambig1-sec.gpg | 57 | ||||
-rw-r--r-- | tests/static/gpg-keys/ambig2-pub.gpg | bin | 0 -> 1235 bytes | |||
-rw-r--r-- | tests/static/gpg-keys/ambig2-sec.gpg | bin | 0 -> 2536 bytes | |||
-rw-r--r-- | tests/static/gpg-keys/pub.gpg (renamed from tests/static/pub.gpg) | 0 | ||||
-rw-r--r-- | tests/static/gpg-keys/sec.gpg (renamed from tests/static/sec.gpg) | 0 | ||||
-rw-r--r-- | tests/utilities.py | 45 |
8 files changed, 427 insertions, 33 deletions
diff --git a/tests/crypto_test.py b/tests/crypto_test.py index 51cb48ba..506d5868 100644 --- a/tests/crypto_test.py +++ b/tests/crypto_test.py @@ -5,6 +5,7 @@ from __future__ import absolute_import import os import shutil +import signal import subprocess import tempfile import unittest @@ -12,10 +13,80 @@ import unittest import gpgme import mock +from alot import crypto +from alot.errors import GPGProblem, GPGCode + from . import utilities -from alot import crypto -from alot.errors import GPGProblem + +MOD_CLEAN = utilities.ModuleCleanup() + +# A useful single fingerprint for tests that only care about one key. This +# key will not be ambiguous +FPR = "F74091D4133F87D56B5D343C1974EC55FBC2D660" + +# Some additional keys, these keys may be ambigiuos +EXTRA_FPRS = [ + "DD19862809A7573A74058FF255937AFBB156245D", + "2071E9C8DB4EF5466F4D233CF730DF92C4566CE7", +] + +DEVNULL = open('/dev/null', 'w') +MOD_CLEAN.add_cleanup(DEVNULL.close) + + +@MOD_CLEAN.wrap_setup +def setUpModule(): + home = tempfile.mkdtemp() + MOD_CLEAN.add_cleanup(shutil.rmtree, home) + mock_home = mock.patch.dict(os.environ, {'GNUPGHOME': home}) + mock_home.start() + MOD_CLEAN.add_cleanup(mock_home.stop) + + ctx = gpgme.Context() + ctx.armor = True + + # Add the public and private keys. They have no password + search_dir = os.path.join(os.path.dirname(__file__), 'static/gpg-keys') + for each in os.listdir(search_dir): + if os.path.splitext(each)[1] == '.gpg': + with open(os.path.join(search_dir, each)) as f: + ctx.import_(f) + + +@MOD_CLEAN.wrap_teardown +def tearDownModule(): + # Kill any gpg-agent's that have been opened + lookfor = 'gpg-agent --homedir {}'.format(os.environ['GNUPGHOME']) + + out = subprocess.check_output(['ps', 'xo', 'pid,cmd'], stderr=DEVNULL) + for each in out.strip().split('\n'): + pid, cmd = each.strip().split(' ', 1) + if cmd.startswith(lookfor): + os.kill(int(pid), signal.SIGKILL) + + +def make_key(revoked=False, expired=False, invalid=False, can_encrypt=True, + can_sign=True): + mock_key = mock.create_autospec(gpgme.Key) + mock_key.uids = [mock.Mock(uid=u'mocked')] + mock_key.revoked = revoked + mock_key.expired = expired + mock_key.invalid = invalid + mock_key.can_encrypt = can_encrypt + mock_key.can_sign = can_sign + + return mock_key + + +def make_uid(email, revoked=False, invalid=False, validity=gpgme.VALIDITY_FULL): + uid = mock.Mock() + uid.email = email + uid.revoked = revoked + uid.invalid = invalid + uid.validity = validity + + return uid class TestHashAlgorithmHelper(unittest.TestCase): @@ -35,36 +106,12 @@ class TestHashAlgorithmHelper(unittest.TestCase): crypto.RFC3156_micalg_from_algo(gpgme.MD_NONE) -class TestSignature(utilities.TestCaseClassCleanup): - - FPR = "F74091D4133F87D56B5D343C1974EC55FBC2D660" +class TestDetachedSignatureFor(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Create a temporary directory to use as gnupg's home directory. This - # allows us to import keys without dirtying the user's keyring - home = tempfile.mkdtemp() - cls.addClassCleanup(shutil.rmtree, home) - mock_home = mock.patch.dict(os.environ, {'GNUPGHOME': home}) - mock_home.start() - cls.addClassCleanup(mock_home.stop) - - # create a single context to use class wide. - cls.ctx = gpgme.Context() - cls.ctx.armor = True - - # Add the public and private keys. They have no password - here = os.path.dirname(__file__) - with open(os.path.join(here, 'static/pub.gpg')) as f: - cls.ctx.import_(f) - with open(os.path.join(here, 'static/sec.gpg')) as f: - cls.ctx.import_(f) - - cls.key = cls.ctx.get_key(cls.FPR) - - def test_detached_signature_for(self): + def test_valid_signature_generated(self): + ctx = gpgme.Context() to_sign = "this is some text.\nit is more than nothing.\n" - _, detached = crypto.detached_signature_for(to_sign, self.key) + _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR)) with tempfile.NamedTemporaryFile(delete=False) as f: f.write(detached) @@ -76,12 +123,17 @@ class TestSignature(utilities.TestCaseClassCleanup): text = f.name self.addCleanup(os.unlink, f.name) - res = subprocess.check_call(['gpg', '--verify', sig, text]) + res = subprocess.check_call(['gpg', '--verify', sig, text], + stdout=DEVNULL, stderr=DEVNULL) self.assertEqual(res, 0) + +class TestVerifyDetached(unittest.TestCase): + def test_verify_signature_good(self): + ctx = gpgme.Context() to_sign = "this is some text.\nIt's something\n." - _, detached = crypto.detached_signature_for(to_sign, self.key) + _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR)) try: crypto.verify_detached(to_sign, detached) @@ -89,9 +141,219 @@ class TestSignature(utilities.TestCaseClassCleanup): raise AssertionError def test_verify_signature_bad(self): + ctx = gpgme.Context() to_sign = "this is some text.\nIt's something\n." similar = "this is some text.\r\n.It's something\r\n." - _, detached = crypto.detached_signature_for(to_sign, self.key) + _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR)) with self.assertRaises(GPGProblem): crypto.verify_detached(similar, detached) + + +class TestValidateKey(unittest.TestCase): + + def test_valid(self): + try: + crypto.validate_key(make_key()) + except GPGProblem as e: + raise AssertionError(e) + + def test_revoked(self): + with self.assertRaises(GPGProblem) as caught: + crypto.validate_key(make_key(revoked=True)) + + self.assertEqual(caught.exception.code, GPGCode.KEY_REVOKED) + + def test_expired(self): + with self.assertRaises(GPGProblem) as caught: + crypto.validate_key(make_key(expired=True)) + + self.assertEqual(caught.exception.code, GPGCode.KEY_EXPIRED) + + def test_invalid(self): + with self.assertRaises(GPGProblem) as caught: + crypto.validate_key(make_key(invalid=True)) + + self.assertEqual(caught.exception.code, GPGCode.KEY_INVALID) + + def test_encrypt(self): + with self.assertRaises(GPGProblem) as caught: + crypto.validate_key(make_key(can_encrypt=False), encrypt=True) + + self.assertEqual(caught.exception.code, GPGCode.KEY_CANNOT_ENCRYPT) + + def test_encrypt_no_check(self): + try: + crypto.validate_key(make_key(can_encrypt=False)) + except GPGProblem as e: + raise AssertionError(e) + + def test_sign(self): + with self.assertRaises(GPGProblem) as caught: + crypto.validate_key(make_key(can_sign=False), sign=True) + + self.assertEqual(caught.exception.code, GPGCode.KEY_CANNOT_SIGN) + + def test_sign_no_check(self): + try: + crypto.validate_key(make_key(can_sign=False)) + except GPGProblem as e: + raise AssertionError(e) + + +class TestCheckUIDValidity(unittest.TestCase): + + def test_valid_single(self): + key = make_key() + key.uids[0] = make_uid(mock.sentinel.EMAIL) + ret = crypto.check_uid_validity(key, mock.sentinel.EMAIL) + self.assertTrue(ret) + + def test_valid_multiple(self): + key = make_key() + key.uids = [ + make_uid(mock.sentinel.EMAIL), + make_uid(mock.sentinel.EMAIL1), + ] + + ret = crypto.check_uid_validity(key, mock.sentinel.EMAIL1) + self.assertTrue(ret) + + def test_invalid_email(self): + key = make_key() + key.uids[0] = make_uid(mock.sentinel.EMAIL) + ret = crypto.check_uid_validity(key, mock.sentinel.EMAIL1) + self.assertFalse(ret) + + def test_invalid_revoked(self): + key = make_key() + key.uids[0] = make_uid(mock.sentinel.EMAIL, revoked=True) + ret = crypto.check_uid_validity(key, mock.sentinel.EMAIL) + self.assertFalse(ret) + + def test_invalid_invalid(self): + key = make_key() + key.uids[0] = make_uid(mock.sentinel.EMAIL, invalid=True) + ret = crypto.check_uid_validity(key, mock.sentinel.EMAIL) + self.assertFalse(ret) + + def test_invalid_not_enough_trust(self): + key = make_key() + key.uids[0] = make_uid(mock.sentinel.EMAIL, + validity=gpgme.VALIDITY_UNDEFINED) + ret = crypto.check_uid_validity(key, mock.sentinel.EMAIL) + self.assertFalse(ret) + + +class TestListKeys(unittest.TestCase): + + def test_list_no_hints(self): + # This only tests that you get 3 keys back (the number in our test + # keyring), it might be worth adding tests to check more about the keys + # returned + values = crypto.list_keys() + self.assertEqual(len(list(values)), 3) + + def test_list_hint(self): + values = crypto.list_keys(hint="ambig") + self.assertEqual(len(list(values)), 2) + + +class TestGetKey(unittest.TestCase): + + def test_plain(self): + # Test the uid of the only identity attached to the key we generated. + ctx = gpgme.Context() + expected = ctx.get_key(FPR).uids[0].uid + actual = crypto.get_key(FPR).uids[0].uid + self.assertEqual(expected, actual) + + def test_validate(self): + # Since we already test validation we're only going to test validate + # once. + ctx = gpgme.Context() + expected = ctx.get_key(FPR).uids[0].uid + actual = crypto.get_key(FPR, validate=True, encrypt=True, sign=True).uids[0].uid + self.assertEqual(expected, actual) + + def test_missing_key(self): + with self.assertRaises(GPGProblem) as caught: + crypto.get_key('z') + self.assertEqual(caught.exception.code, GPGCode.NOT_FOUND) + + @mock.patch('alot.crypto.check_uid_validity', mock.Mock(return_value=True)) + def test_signed_only_true(self): + try: + crypto.get_key(FPR, signed_only=True) + except GPGProblem as e: + raise AssertionError(e) + + @mock.patch('alot.crypto.check_uid_validity', mock.Mock(return_value=False)) + def test_signed_only_false(self): + with self.assertRaises(GPGProblem) as e: + crypto.get_key(FPR, signed_only=True) + self.assertEqual(e.exception.code, GPGCode.NOT_FOUND) + + @staticmethod + def _context_mock(): + error = gpgme.GpgmeError() + error.code = gpgme.ERR_AMBIGUOUS_NAME + context_mock = mock.Mock() + context_mock.get_key = mock.Mock(side_effect=error) + + return context_mock + + def test_ambiguous_one_valid(self): + invalid_key = make_key(invalid=True) + valid_key = make_key() + + with mock.patch('alot.crypto.gpgme.Context', + mock.Mock(return_value=self._context_mock())), \ + mock.patch('alot.crypto.list_keys', + mock.Mock(return_value=[valid_key, invalid_key])): + key = crypto.get_key('placeholder') + self.assertIs(key, valid_key) + + def test_ambiguous_two_valid(self): + with mock.patch('alot.crypto.gpgme.Context', + mock.Mock(return_value=self._context_mock())), \ + mock.patch('alot.crypto.list_keys', + mock.Mock(return_value=[make_key(), make_key()])): + with self.assertRaises(crypto.GPGProblem) as cm: + crypto.get_key('placeholder') + self.assertEqual(cm.exception.code, GPGCode.AMBIGUOUS_NAME) + + def test_ambiguous_no_valid(self): + with mock.patch('alot.crypto.gpgme.Context', + mock.Mock(return_value=self._context_mock())), \ + mock.patch('alot.crypto.list_keys', + mock.Mock(return_value=[make_key(invalid=True), + make_key(invalid=True)])): + with self.assertRaises(crypto.GPGProblem) as cm: + crypto.get_key('placeholder') + self.assertEqual(cm.exception.code, GPGCode.NOT_FOUND) + + +class TestEncrypt(unittest.TestCase): + + def test_encrypt(self): + to_encrypt = "this is a string\nof data." + encrypted = crypto.encrypt(to_encrypt, keys=[crypto.get_key(FPR)]) + + with tempfile.NamedTemporaryFile(delete=False) as f: + f.write(encrypted) + enc_file = f.name + self.addCleanup(os.unlink, enc_file) + + dec = subprocess.check_output(['gpg', '--decrypt', enc_file], + stderr=DEVNULL) + self.assertEqual(to_encrypt, dec) + + +class TestDecrypt(unittest.TestCase): + + def test_decrypt(self): + to_encrypt = "this is a string\nof data." + encrypted = crypto.encrypt(to_encrypt, keys=[crypto.get_key(FPR)]) + _, dec = crypto.decrypt_verify(encrypted) + self.assertEqual(to_encrypt, dec) diff --git a/tests/static/gpg-keys/ambig1-pub.gpg b/tests/static/gpg-keys/ambig1-pub.gpg new file mode 100644 index 00000000..8bc893d6 --- /dev/null +++ b/tests/static/gpg-keys/ambig1-pub.gpg @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFlmXXwBCACip8btluktWTxaUdyDLpBS2GrXgDe1M/zT0TMaNN8p0r0oUnNf +a7dSHb4QLbaY31d3ftt5IoaSXENnJP2WREujQdQ9SlXb4sVNo6W37t0NtKGn9kqp +T2ajgXj1lJ+ZiULHRlSmBoA2blFeABE4PRgef+x6aDJpMtODWG/2NaWw/gFn6kqS +OGyqMp0nM3OHeEwZAjf+n1f07wqJHK+m1V3I2rY4wm5LST0kZXJGYFDfjaTuTOOC +yPMyhWoqJ/CCWavO47MRdYrlM6qUbVBTQ8DSBGZO2yuF/ILLICC8d/ODGva+kNDq +Bm4PmYvVrB0osfxMXVBaxezwOKkDiE3w4fOXABEBAAG0GWFtYmlnIDxhbWJpZ0Bl +eGFtcGxlLmNvbT6JAVQEEwEIAD4WIQTdGYYoCadXOnQFj/JVk3r7sVYkXQUCWWZd +fAIbAwUJA8JnAAULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRBVk3r7sVYkXXte +B/sGzTrrHk7da9GqLtJPYPiI4uckInb7xcxOANd2MMO27ZdBcy+77tdVNDmGnmUP +W1KW0APTL5JL8XNo39r7eKGfp66g6qF8Lto/X22DIyv5tld1XARwXt5z19P/oA9F +BTwSkqP/pWg5rYJwSmZwNdYF+kFTTokTxZgXDVfQcDa4qnPRy4wZSoiQewE3itdn +fwexxuhXmS406UPLBhmFItGH4BThk0z5b0EVLt1zvff6q1po3hlcZr+2u71WvYzF +ufJfWQr5xo6PX1Po16Da5Pz+mnuaz7vT+i3RwLo1wOsu7rXNAAVzg3VstGKAobGE +ix+ET7uB05xJLFLt6jlVg2ChuQENBFlmXXwBCADLAS1JN3UykZbL3JCiatPe6Ce6 +ErkzEwnECJxTyg2UqsrQJ/SdPRCJ8wyQ0jWBezn/4MNCiJoacPR+YVo6CVi/R/Kc +7qfiqxVp5mfxSf4qpbC1esZ0L20VdhUnWKc+YvSUhGPIe73ruiDVXt+QnyZiLm9B +niUNaEL/j8GI9o5J6y2v3IQJwO9cmVKuQa2aE1c9zG4ZxIrzlgrI8bF2jcm/olx6 +a1X55TsqQEA/CEl5tsyr5gOBa/4qXc1STUCZthEKffbDsSH+8d+26Y7Qw201BWWQ +6Hx/8jnyaKzmP+ANymG2Bj4lioEY96Qu6vzzQ4RwUvkcJB6K1Osr/diwhkeJABEB +AAGJATwEGAEIACYWIQTdGYYoCadXOnQFj/JVk3r7sVYkXQUCWWZdfAIbDAUJA8Jn +AAAKCRBVk3r7sVYkXY01CACDoMkoAYNeCa43g11zywczl4ZyGLQuDD1BomKP3Jur +58sNo6OjYd0bRSUtn6V7vPKaMIm1b+VF2L9/c0Oia807yr+7h36XoriTas1iAqkp +gx3ZQ06daenq/074edPvnrknzYpnzCZM5SQcyCTU6QPgLU2rNZAl75CH2SfpY0SK +HUMHjiD+HKueNrAstqsY1DJdO+aQDdYxLJxuax2003+qriX0hDp0X7Nym4v+cd1X +xgsVMkqFIYkwJXUlooMRLAb1ifkUEiezsr5i5iudwrsIb7WjcDEXOFSHRXpT63z0 +glpUupwfhHcODTOmIgyhrfbmN60eiEcojfSdMsWi2ktr +=sqEe +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/static/gpg-keys/ambig1-sec.gpg b/tests/static/gpg-keys/ambig1-sec.gpg new file mode 100644 index 00000000..35be3bcb --- /dev/null +++ b/tests/static/gpg-keys/ambig1-sec.gpg @@ -0,0 +1,57 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=r1WA +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/static/gpg-keys/ambig2-pub.gpg b/tests/static/gpg-keys/ambig2-pub.gpg Binary files differnew file mode 100644 index 00000000..e52e2f62 --- /dev/null +++ b/tests/static/gpg-keys/ambig2-pub.gpg diff --git a/tests/static/gpg-keys/ambig2-sec.gpg b/tests/static/gpg-keys/ambig2-sec.gpg Binary files differnew file mode 100644 index 00000000..a7158131 --- /dev/null +++ b/tests/static/gpg-keys/ambig2-sec.gpg diff --git a/tests/static/pub.gpg b/tests/static/gpg-keys/pub.gpg index c94f951d..c94f951d 100644 --- a/tests/static/pub.gpg +++ b/tests/static/gpg-keys/pub.gpg diff --git a/tests/static/sec.gpg b/tests/static/gpg-keys/sec.gpg index f00b017f..f00b017f 100644 --- a/tests/static/sec.gpg +++ b/tests/static/gpg-keys/sec.gpg diff --git a/tests/utilities.py b/tests/utilities.py index aae79552..ed169b59 100644 --- a/tests/utilities.py +++ b/tests/utilities.py @@ -98,3 +98,48 @@ class TestCaseClassCleanup(unittest.TestCase): # TODO: addCleanups success if part of the success of the test, # what should we do here? func(*args, **kwargs) + + +class ModuleCleanup(object): + """Class for managing module level setup and teardown fixtures. + + Because of the way unittest is implemented it's rather difficult to write + elegent fixtures because setUpModule and tearDownModule must exist when the + module is initialized, so you can't do things like assign the methods to + setUpModule and tearDownModule, nor can you just do some globals + manipulation. + """ + + def __init__(self): + self.__stack = [] + + def do_cleanups(self): + while self.__stack: + func, args, kwargs = self.__stack.pop() + func(*args, **kwargs) + + def add_cleanup(self, func, *args, **kwargs): + self.__stack.append((func, args, kwargs)) + + def wrap_teardown(self, teardown): + + @functools.wraps(teardown) + def wrapper(): + try: + teardown() + finally: + self.do_cleanups() + + return wrapper + + def wrap_setup(self, setup): + + @functools.wraps(setup) + def wrapper(): + try: + setup() + except Exception: + self.do_cleanups() + raise + + return wrapper |