diff options
Diffstat (limited to 'soledad/src/leap/soledad/tests/test_crypto.py')
-rw-r--r-- | soledad/src/leap/soledad/tests/test_crypto.py | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/soledad/src/leap/soledad/tests/test_crypto.py b/soledad/src/leap/soledad/tests/test_crypto.py new file mode 100644 index 00000000..c727a2ff --- /dev/null +++ b/soledad/src/leap/soledad/tests/test_crypto.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- +# test_crypto.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +""" +Tests for cryptographic related stuff. +""" + +import os +import shutil +import tempfile +import simplejson as json +import hashlib +import binascii + + +from leap.common.testing.basetest import BaseLeapTest +from Crypto import Random + + +from leap.common.testing.basetest import BaseLeapTest +from leap.soledad import ( + Soledad, + crypto, + target, +) +from leap.soledad.document import SoledadDocument +from leap.soledad.tests import ( + BaseSoledadTest, + KEY_FINGERPRINT, + PRIVATE_KEY, +) +from leap.soledad.tests.u1db_tests import ( + simple_doc, + nested_doc, + TestCaseWithServer, +) + + +class EncryptedSyncTestCase(BaseSoledadTest): + """ + Tests that guarantee that data will always be encrypted when syncing. + """ + + def test_encrypt_decrypt_json(self): + """ + Test encrypting and decrypting documents. + """ + simpledoc = {'key': 'val'} + doc1 = SoledadDocument(doc_id='id') + doc1.content = simpledoc + # encrypt doc + doc1.set_json(target.encrypt_doc(self._soledad._crypto, doc1)) + # assert content is different and includes keys + self.assertNotEqual( + simpledoc, doc1.content, + 'incorrect document encryption') + self.assertTrue(target.ENC_JSON_KEY in doc1.content) + self.assertTrue(target.ENC_SCHEME_KEY in doc1.content) + # decrypt doc + doc1.set_json(target.decrypt_doc(self._soledad._crypto, doc1)) + self.assertEqual( + simpledoc, doc1.content, 'incorrect document encryption') + + +class RecoveryDocumentTestCase(BaseSoledadTest): + + def test_export_recovery_document_raw(self): + rd = self._soledad.export_recovery_document() + secret_id = rd[self._soledad.STORAGE_SECRETS_KEY].items()[0][0] + secret = rd[self._soledad.STORAGE_SECRETS_KEY][secret_id] + self.assertEqual(secret_id, self._soledad._secret_id) + self.assertEqual(secret, self._soledad._secrets[secret_id]) + self.assertTrue(self._soledad.CIPHER_KEY in secret) + self.assertTrue(secret[self._soledad.CIPHER_KEY] == 'aes256') + self.assertTrue(self._soledad.LENGTH_KEY in secret) + self.assertTrue(self._soledad.SECRET_KEY in secret) + + def test_import_recovery_document(self): + rd = self._soledad.export_recovery_document() + s = self._soledad_instance(user='anotheruser@leap.se') + s.import_recovery_document(rd) + s._set_secret_id(self._soledad._secret_id) + self.assertEqual(self._soledad._uuid, + s._uuid, 'Failed setting user uuid.') + self.assertEqual(self._soledad._get_storage_secret(), + s._get_storage_secret(), + 'Failed settinng secret for symmetric encryption.') + + +class SoledadSecretsTestCase(BaseSoledadTest): + + def test__gen_secret(self): + # instantiate and save secret_id + sol = self._soledad_instance(user='user@leap.se') + self.assertTrue(len(sol._secrets) == 1) + secret_id_1 = sol.secret_id + # assert id is hash of secret + self.assertTrue( + secret_id_1 == hashlib.sha256(sol.storage_secret).hexdigest()) + # generate new secret + secret_id_2 = sol._gen_secret() + self.assertTrue(secret_id_1 != secret_id_2) + # re-instantiate + sol = self._soledad_instance( + user='user@leap.se', + secret_id=secret_id_1) + # assert ids are valid + self.assertTrue(len(sol._secrets) == 2) + self.assertTrue(secret_id_1 in sol._secrets) + self.assertTrue(secret_id_2 in sol._secrets) + # assert format of secret 1 + self.assertTrue(sol.storage_secret is not None) + self.assertIsInstance(sol.storage_secret, str) + self.assertTrue(len(sol.storage_secret) == sol.GENERATED_SECRET_LENGTH) + # assert format of secret 2 + sol._set_secret_id(secret_id_2) + self.assertTrue(sol.storage_secret is not None) + self.assertIsInstance(sol.storage_secret, str) + self.assertTrue(len(sol.storage_secret) == sol.GENERATED_SECRET_LENGTH) + # assert id is hash of new secret + self.assertTrue( + secret_id_2 == hashlib.sha256(sol.storage_secret).hexdigest()) + + def test__has_secret(self): + sol = self._soledad_instance(user='user@leap.se') + self.assertTrue(sol._has_secret(), "Should have a secret at " + "this point") + # setting secret id to None should not interfere in the fact we have a + # secret. + sol._set_secret_id(None) + self.assertTrue(sol._has_secret(), "Should have a secret at " + "this point") + # but not being able to decrypt correctly should + sol._secrets[sol.secret_id][sol.SECRET_KEY] = None + self.assertFalse(sol._has_secret()) + + +class MacAuthTestCase(BaseSoledadTest): + + def test_decrypt_with_wrong_mac_raises(self): + """ + Trying to decrypt a document with wrong MAC should raise. + """ + simpledoc = {'key': 'val'} + doc = SoledadDocument(doc_id='id') + doc.content = simpledoc + # encrypt doc + doc.set_json(target.encrypt_doc(self._soledad._crypto, doc)) + self.assertTrue(target.MAC_KEY in doc.content) + self.assertTrue(target.MAC_METHOD_KEY in doc.content) + # mess with MAC + doc.content[target.MAC_KEY] = '1234567890ABCDEF' + # try to decrypt doc + self.assertRaises( + target.WrongMac, + target.decrypt_doc, self._soledad._crypto, doc) + + def test_decrypt_with_unknown_mac_method_raises(self): + """ + Trying to decrypt a document with unknown MAC method should raise. + """ + simpledoc = {'key': 'val'} + doc = SoledadDocument(doc_id='id') + doc.content = simpledoc + # encrypt doc + doc.set_json(target.encrypt_doc(self._soledad._crypto, doc)) + self.assertTrue(target.MAC_KEY in doc.content) + self.assertTrue(target.MAC_METHOD_KEY in doc.content) + # mess with MAC method + doc.content[target.MAC_METHOD_KEY] = 'mymac' + # try to decrypt doc + self.assertRaises( + target.UnknownMacMethod, + target.decrypt_doc, self._soledad._crypto, doc) + + +class SoledadCryptoTestCase(BaseSoledadTest): + + def test_encrypt_decrypt_sym(self): + # generate 256-bit key + key = Random.new().read(32) + iv, cyphertext = self._soledad._crypto.encrypt_sym( + 'data', key, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + plaintext = self._soledad._crypto.decrypt_sym( + cyphertext, key, iv=iv, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertEqual('data', plaintext) + + def test_decrypt_with_wrong_iv_fails(self): + key = Random.new().read(32) + iv, cyphertext = self._soledad._crypto.encrypt_sym( + 'data', key, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + # get a different iv by changing the first byte + rawiv = binascii.a2b_base64(iv) + wrongiv = rawiv + while wrongiv == rawiv: + wrongiv = os.urandom(1) + rawiv[1:] + plaintext = self._soledad._crypto.decrypt_sym( + cyphertext, key, iv=binascii.b2a_base64(wrongiv), + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertNotEqual('data', plaintext) + + def test_decrypt_with_wrong_key_fails(self): + key = Random.new().read(32) + iv, cyphertext = self._soledad._crypto.encrypt_sym( + 'data', key, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + wrongkey = Random.new().read(32) # 256-bits key + # ensure keys are different in case we are extremely lucky + while wrongkey == key: + wrongkey = Random.new().read(32) + plaintext = self._soledad._crypto.decrypt_sym( + cyphertext, wrongkey, iv=iv, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertNotEqual('data', plaintext) |