# -*- coding: utf-8 -*- import os import sys from Bcfg2.Compat import b64decode from mock import Mock, MagicMock, patch # add all parent testsuite directories to sys.path to allow (most) # relative imports in python 2.4 path = os.path.dirname(__file__) while path != "/": if os.path.basename(path).lower().startswith("test"): sys.path.append(path) if os.path.basename(path) == "testsuite": break path = os.path.dirname(path) from common import * try: from Bcfg2.Encryption import * HAS_CRYPTO = True except ImportError: HAS_CRYPTO = False if can_skip or HAS_CRYPTO: class TestEncryption(Bcfg2TestCase): plaintext = """foo bar baz รถ \t\tquux """ + "a" * 16384 # 16K is completely arbitrary iv = "0123456789ABCDEF" salt = "01234567" algo = "des_cbc" @skipUnless(HAS_CRYPTO, "Encryption libraries not found") def setUp(self): pass def test_str_crypt(self): """ test str_encrypt/str_decrypt """ key = "a simple key" # simple symmetrical test with no options crypted = str_encrypt(self.plaintext, key) self.assertEqual(self.plaintext, str_decrypt(crypted, key)) # symmetrical test with lots of options crypted = str_encrypt(self.plaintext, key, iv=self.iv, salt=self.salt, algorithm=self.algo) self.assertEqual(self.plaintext, str_decrypt(crypted, key, iv=self.iv, algorithm=self.algo)) # test that different algorithms are actually used self.assertNotEqual(str_encrypt(self.plaintext, key), str_encrypt(self.plaintext, key, algorithm=self.algo)) # test that different keys are actually used self.assertNotEqual(str_encrypt(self.plaintext, key), str_encrypt(self.plaintext, "different key")) # test that different IVs are actually used self.assertNotEqual(str_encrypt(self.plaintext, key, iv=self.iv), str_encrypt(self.plaintext, key)) # test that errors are raised on bad decrypts crypted = str_encrypt(self.plaintext, key, algorithm=self.algo) self.assertRaises(EVPError, str_decrypt, crypted, "bogus key", algorithm=self.algo) self.assertRaises(EVPError, str_decrypt, crypted, key) # bogus algorithm def test_ssl_crypt(self): """ test ssl_encrypt/ssl_decrypt """ passwd = "a simple passphrase" # simple symmetrical test crypted = ssl_encrypt(self.plaintext, passwd) self.assertEqual(self.plaintext, ssl_decrypt(crypted, passwd)) # more complex symmetrical test crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo, salt=self.salt) self.assertEqual(self.plaintext, ssl_decrypt(crypted, passwd, algorithm=self.algo)) # test that different algorithms are actually used self.assertNotEqual(ssl_encrypt(self.plaintext, passwd), ssl_encrypt(self.plaintext, passwd, algorithm=self.algo)) # test that different passwords are actually used self.assertNotEqual(ssl_encrypt(self.plaintext, passwd), ssl_encrypt(self.plaintext, "different pass")) # there's no reasonable test we can do to see if the # output is base64-encoded, unfortunately, but if it's # obviously not we fail crypted = ssl_encrypt(self.plaintext, passwd) self.assertRegexpMatches(crypted, r'^[A-Za-z0-9+/]+[=]{0,2}$') # test that errors are raised on bad decrypts crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo) self.assertRaises(EVPError, ssl_decrypt, crypted, "bogus passwd", algorithm=self.algo) self.assertRaises(EVPError, ssl_decrypt, crypted, passwd) # bogus algorithm def test_get_algorithm(self): setup = Mock() # we don't care what the default is, as long as there is # one setup.cfp.get.return_value = ALGORITHM self.assertRegexpMatches(get_algorithm(setup), r'^[a-z0-9]+_[a-z0-9_]+$') setup.cfp.get.assert_called_with(CFG_SECTION, CFG_ALGORITHM, default=ALGORITHM) setup.cfp.get.return_value = self.algo self.assertEqual(get_algorithm(setup), self.algo) setup.cfp.get.assert_called_with(CFG_SECTION, CFG_ALGORITHM, default=ALGORITHM) # test that get_algorithm converts algorithms given in # OpenSSL style to M2Crypto style setup.cfp.get.return_value = "DES-EDE3-CFB8" self.assertEqual(get_algorithm(setup), "des_ede3_cfb8") setup.cfp.get.assert_called_with(CFG_SECTION, CFG_ALGORITHM, default=ALGORITHM) def test_get_passphrases(self): setup = Mock() setup.cfp.has_section.return_value = False self.assertEqual(get_passphrases(setup), dict()) setup.cfp.has_section.return_value = True setup.cfp.options.return_value = ["foo", "bar", CFG_ALGORITHM] setup.cfp.get.return_value = "passphrase" self.assertItemsEqual(get_passphrases(setup), dict(foo="passphrase", bar="passphrase")) @patch("Bcfg2.Encryption.get_passphrases") def test_bruteforce_decrypt(self, mock_passphrases): passwd = "a simple passphrase" crypted = ssl_encrypt(self.plaintext, passwd) setup = Mock() # test with no passphrases given nor in config mock_passphrases.return_value = dict() self.assertRaises(EVPError, bruteforce_decrypt, crypted, setup=setup) mock_passphrases.assert_called_with(setup) # test with good passphrase given in function call mock_passphrases.reset_mock() self.assertEqual(self.plaintext, bruteforce_decrypt(crypted, passphrases=["bogus pass", passwd, "also bogus"])) self.assertFalse(mock_passphrases.called) # test with no good passphrase given nor in config mock_passphrases.reset_mock() self.assertRaises(EVPError, bruteforce_decrypt, crypted, passphrases=["bogus", "also bogus"]) self.assertFalse(mock_passphrases.called) # test with good passphrase in config file mock_passphrases.reset_mock() mock_passphrases.return_value = dict(bogus="bogus", real=passwd, bogus2="also bogus") self.assertEqual(self.plaintext, bruteforce_decrypt(crypted, setup=setup)) mock_passphrases.assert_called_with(setup) # test that passphrases given in function call take # precedence over config mock_passphrases.reset_mock() self.assertRaises(EVPError, bruteforce_decrypt, crypted, setup=setup, passphrases=["bogus", "also bogus"]) self.assertFalse(mock_passphrases.called) # test that different algorithms are used mock_passphrases.reset_mock() crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo) self.assertEqual(self.plaintext, bruteforce_decrypt(crypted, setup=setup, algorithm=self.algo))