import unittest
from cloudproof_cover_crypt import (
Attribute,
CoverCrypt,
MasterPublicKey,
MasterSecretKey,
Policy,
PolicyAxis,
SymmetricKey,
UserSecretKey,
)
class TestPolicy(unittest.TestCase):
def policy(self) -> Policy:
policy = Policy()
policy.add_axis(
PolicyAxis(
'Country',
[
('France', False),
('UK', False),
('Spain', False),
('Germany', False),
],
False,
)
)
policy.add_axis(
PolicyAxis(
'Secrecy', [('Low', False), ('Medium', False), ('High', True)], True
)
)
return policy
def test_attribute(self) -> None:
attr = Attribute('Country', 'France')
self.assertEqual(attr.to_string(), 'Country::France')
new_attr = Attribute.from_string('Country::Japan')
self.assertIsInstance(new_attr, Attribute)
self.assertEqual(new_attr.get_axis(), 'Country')
self.assertEqual(new_attr.get_name(), 'Japan')
def test_policy_axis(self) -> None:
country_axis = PolicyAxis(
'Country',
[('France', False), ('UK', False), 'Spain', 'Germany'],
False,
)
self.assertEqual(
country_axis.to_string(),
'Country: [AttributeBuilder { name: "France", encryption_hint: Classic }, AttributeBuilder { name: "UK", encryption_hint: Classic }, AttributeBuilder { name: "Spain", encryption_hint: Classic }, AttributeBuilder { name: "Germany", encryption_hint: Classic }], hierarchical: false',
)
secrecy_axis = PolicyAxis(
'Secrecy',
[('Low', False), 'Medium', ('High', True)],
True,
)
self.assertEqual(
secrecy_axis.to_string(),
'Secrecy: [AttributeBuilder { name: "Low", encryption_hint: Classic }, AttributeBuilder { name: "Medium", encryption_hint: Classic }, AttributeBuilder { name: "High", encryption_hint: Hybridized }], hierarchical: true',
)
self.assertTrue(PolicyAxis('Test', [], False).is_empty())
self.assertEqual(country_axis.len(), 4)
self.assertEqual(len(country_axis.get_attributes()), 4)
self.assertFalse(country_axis.is_hierarchical())
self.assertTrue(secrecy_axis.is_hierarchical())
self.assertEqual(secrecy_axis.get_name(), 'Secrecy')
def test_edit_policy(self) -> None:
policy = self.policy()
self.assertEqual(len(policy.attributes()), 7)
policy.rename_attribute(Attribute('Country', 'Spain'), 'Espagne')
with self.assertRaises(Exception):
policy.rename_attribute(Attribute('Country', 'Japan'), 'Japon')
self.assertEqual(len(policy.attributes()), 7)
new_attr = Attribute('Country', 'Japan')
policy.add_attribute(new_attr, False)
self.assertEqual(len(policy.attributes()), 8)
duplicate_attr = Attribute('Country', 'France')
with self.assertRaises(Exception):
policy.add_attribute(duplicate_attr, False)
missing_dimension = Attribute('Missing', 'dimension')
with self.assertRaises(Exception):
policy.add_attribute(missing_dimension, False)
delete_attr = Attribute('Country', 'Espagne')
policy.remove_attribute(delete_attr)
self.assertEqual(len(policy.attributes()), 7)
with self.assertRaises(Exception):
policy.remove_attribute(delete_attr)
with self.assertRaises(Exception):
policy.remove_attribute(missing_dimension)
policy.remove_attribute(new_attr)
policy.remove_attribute(Attribute('Country', 'France'))
policy.remove_attribute(Attribute('Country', 'UK'))
policy.remove_axis('Country')
self.assertEqual(len(policy.attributes()), 3)
new_dimension = PolicyAxis(
'DimensionTest',
[
('Attr1', False),
('Attr2', False),
],
False,
)
policy.add_axis(new_dimension)
self.assertEqual(len(policy.attributes()), 5)
policy.remove_axis('DimensionTest')
self.assertEqual(len(policy.attributes()), 3)
with self.assertRaises(Exception):
policy.remove_axis('MissingDim')
with self.assertRaises(Exception):
policy.remove_attribute(Attribute('Secrecy', 'Top Secret'))
policy.remove_axis('Secrecy')
self.assertEqual(len(policy.attributes()), 0)
def test_policy_cloning_serialization(self) -> None:
policy = self.policy()
copy_policy = policy.deep_copy()
self.assertIsInstance(copy_policy, Policy)
json_str = policy.to_bytes()
self.assertEqual(json_str, copy_policy.to_bytes())
deserialized_policy = Policy.from_bytes(json_str)
self.assertIsInstance(deserialized_policy, Policy)
with self.assertRaises(Exception):
Policy.from_bytes('wrong data format'.encode())
class TestKeyGeneration(unittest.TestCase):
def setUp(self) -> None:
country_axis = PolicyAxis(
'Country',
[('France', False), ('UK', False), ('Spain', False), ('Germany', False)],
False,
)
secrecy_axis = PolicyAxis(
'Secrecy', [('Low', False), ('Medium', False), ('High', True)], True
)
self.policy = Policy()
self.policy.add_axis(country_axis)
self.policy.add_axis(secrecy_axis)
self.cc = CoverCrypt()
self.msk, self.pk = self.cc.generate_master_keys(self.policy)
def test_master_key_serialization(self) -> None:
msk_bytes = self.msk.to_bytes()
self.assertIsInstance(MasterSecretKey.from_bytes(msk_bytes), MasterSecretKey)
with self.assertRaises(Exception):
MasterSecretKey.from_bytes(b'wrong data')
pk_bytes = self.pk.to_bytes()
self.assertIsInstance(MasterPublicKey.from_bytes(pk_bytes), MasterPublicKey)
with self.assertRaises(Exception):
MasterPublicKey.from_bytes(b'wrong data')
def test_user_key_serialization(self) -> None:
target_policy = 'Secrecy::High && (Country::France || Country::Spain)'
usk = self.cc.generate_user_secret_key(
self.msk,
target_policy,
self.policy,
)
usk_bytes = usk.to_bytes()
deserialized_usk = UserSecretKey.from_bytes(usk_bytes)
self.assertIsInstance(deserialized_usk, UserSecretKey)
self.cc.rekey_master_keys(target_policy, self.policy, self.msk, self.pk)
self.cc.refresh_user_secret_key(
deserialized_usk,
self.msk,
keep_old_accesses=True,
)
with self.assertRaises(Exception):
UserSecretKey.from_bytes(b'wrong data')
def test_sym_key_serialization(self) -> None:
sym_key, _ = self.cc.encrypt_header(
self.policy,
'Secrecy::High && Country::Germany',
self.pk,
None,
None,
)
sym_key_bytes = sym_key.to_bytes()
self.assertIsInstance(SymmetricKey.from_bytes(sym_key_bytes), SymmetricKey)
with self.assertRaises(Exception):
SymmetricKey.from_bytes(b'wrong data')
class TestEncryption(unittest.TestCase):
def setUp(self) -> None:
country_axis = PolicyAxis(
'Country',
[('France', False), ('UK', False), ('Spain', False), ('Germany', False)],
False,
)
secrecy_axis = PolicyAxis(
'Secrecy', [('Low', False), ('Medium', False), ('High', True)], True
)
self.policy = Policy()
self.policy.add_axis(country_axis)
self.policy.add_axis(secrecy_axis)
self.cc = CoverCrypt()
self.msk, self.pk = self.cc.generate_master_keys(self.policy)
self.plaintext = b'My secret data'
self.header_metadata = bytes([0, 0, 0, 0, 0, 0, 0, 1])
self.authenticated_data = b'auth'
def test_simple_encryption_decryption_without_metadata(self) -> None:
ciphertext = self.cc.encrypt(
self.policy, 'Secrecy::Medium && Country::Germany', self.pk, self.plaintext
)
sec_high_ger_user = self.cc.generate_user_secret_key(
self.msk,
'Secrecy::High && Country::Germany',
self.policy,
)
plaintext, _ = self.cc.decrypt(sec_high_ger_user, ciphertext)
self.assertEqual(plaintext, self.plaintext)
def test_simple_encryption_decryption_with_metadata(self) -> None:
ciphertext = self.cc.encrypt(
self.policy,
'Secrecy::High && Country::France',
self.pk,
self.plaintext,
self.header_metadata,
self.authenticated_data,
)
sec_high_fr_sp_user = self.cc.generate_user_secret_key(
self.msk,
'Secrecy::High && (Country::France || Country::Spain)',
self.policy,
)
plaintext, header_metadata = self.cc.decrypt(
sec_high_fr_sp_user, ciphertext, self.authenticated_data
)
self.assertEqual(plaintext, self.plaintext)
self.assertEqual(header_metadata, bytes(self.header_metadata))
sec_low_fr_sp_user = self.cc.generate_user_secret_key(
self.msk, 'Secrecy::Low && (Country::France || Country::Spain)', self.policy
)
with self.assertRaises(Exception):
self.cc.decrypt(sec_low_fr_sp_user, ciphertext, self.authenticated_data)
def test_rekey_prune_encryption_decryption(self) -> None:
target_policy = 'Secrecy::High && Country::France'
ciphertext = self.cc.encrypt(
self.policy,
target_policy,
self.pk,
self.plaintext,
self.header_metadata,
self.authenticated_data,
)
user1 = self.cc.generate_user_secret_key(
self.msk,
'Secrecy::High && (Country::France || Country::Spain)',
self.policy,
)
user2 = self.cc.generate_user_secret_key(
self.msk,
'Secrecy::High && (Country::France || Country::Spain)',
self.policy,
)
self.cc.rekey_master_keys(target_policy, self.policy, self.msk, self.pk)
new_plaintext = b'My secret data 2'
new_ciphertext = self.cc.encrypt(
self.policy,
target_policy,
self.pk,
new_plaintext,
self.header_metadata,
self.authenticated_data,
)
with self.assertRaises(Exception):
self.cc.decrypt(user1, new_ciphertext, self.authenticated_data)
self.cc.refresh_user_secret_key(
user1,
self.msk,
keep_old_accesses=True,
)
plaintext, _ = self.cc.decrypt(user1, ciphertext, self.authenticated_data)
self.assertEqual(plaintext, self.plaintext)
self.cc.refresh_user_secret_key(
user2,
self.msk,
keep_old_accesses=False,
)
with self.assertRaises(Exception):
self.cc.decrypt(user2, ciphertext, self.authenticated_data)
plaintext, _ = self.cc.decrypt(user2, new_ciphertext, self.authenticated_data)
self.assertEqual(bytes(plaintext), new_plaintext)
self.cc.prune_master_secret_key(target_policy, self.policy, self.msk)
self.cc.refresh_user_secret_key(
user1,
self.msk,
keep_old_accesses=True,
)
with self.assertRaises(Exception):
self.cc.decrypt(user2, ciphertext, self.authenticated_data)
plaintext, _ = self.cc.decrypt(user2, new_ciphertext, self.authenticated_data)
self.assertEqual(bytes(plaintext), new_plaintext)
def test_decomposed_encryption_decryption(self) -> None:
sym_key, enc_header = self.cc.encrypt_header(
self.policy,
'Secrecy::Medium && Country::UK',
self.pk,
self.header_metadata,
self.authenticated_data,
)
ciphertext = self.cc.encrypt_symmetric_block(
sym_key, self.plaintext, self.authenticated_data
)
sec_med_uk_user = self.cc.generate_user_secret_key(
self.msk, 'Secrecy::Medium && Country::UK', self.policy
)
decrypted_sym_key, decrypted_metadata = self.cc.decrypt_header(
sec_med_uk_user, enc_header, self.authenticated_data
)
self.assertEqual(decrypted_metadata, bytes(self.header_metadata))
decrypted_data = self.cc.decrypt_symmetric_block(
decrypted_sym_key, ciphertext, self.authenticated_data
)
self.assertEqual(bytes(decrypted_data), self.plaintext)
def test_add_attribute(self) -> None:
decryption_policy = 'Secrecy::Low'
low_secret_usk = self.cc.generate_user_secret_key(
self.msk, decryption_policy, self.policy
)
self.policy.add_attribute(Attribute('Country', 'Japan'), False)
self.cc.update_master_keys(self.policy, self.msk, self.pk)
plaintext = b'My secret data'
ciphertext = self.cc.encrypt(
self.policy,
'Secrecy::Low && Country::Japan',
self.pk,
plaintext,
)
with self.assertRaises(Exception):
self.cc.decrypt(low_secret_usk, ciphertext)
def test_delete_attribute(self) -> None:
decryption_policy = 'Secrecy::High && (Country::France || Country::UK)'
usk = self.cc.generate_user_secret_key(self.msk, decryption_policy, self.policy)
plaintext = b'My secret data'
ciphertext = self.cc.encrypt(
self.policy,
'Secrecy::High && Country::France',
self.pk,
plaintext,
)
self.policy.remove_attribute(Attribute('Country', 'France'))
self.cc.update_master_keys(self.policy, self.msk, self.pk)
decrypted_text, _ = self.cc.decrypt(usk, ciphertext)
self.assertEqual(decrypted_text, plaintext)
self.cc.refresh_user_secret_key(usk, self.msk, True)
with self.assertRaises(Exception):
self.cc.decrypt(usk, ciphertext)
def test_disable_attribute(self) -> None:
decryption_policy = 'Secrecy::High && Country::France'
usk = self.cc.generate_user_secret_key(self.msk, decryption_policy, self.policy)
plaintext = b'My secret data'
ciphertext = self.cc.encrypt(
self.policy,
'Secrecy::High && Country::France',
self.pk,
plaintext,
)
self.policy.disable_attribute(Attribute('Country', 'France'))
self.cc.update_master_keys(self.policy, self.msk, self.pk)
with self.assertRaises(Exception):
self.cc.encrypt(
self.policy,
'Secrecy::High && Country::France',
self.pk,
b'Test',
)
self.cc.refresh_user_secret_key(usk, self.msk, False)
decrypted_text, _ = self.cc.decrypt(usk, ciphertext)
self.assertEqual(decrypted_text, plaintext)
self.cc.rekey_master_keys(
'Secrecy::High && Country::France', self.policy, self.msk, self.pk
)
def test_rename_attribute(self) -> None:
decryption_policy = 'Secrecy::High && Country::Spain'
usk = self.cc.generate_user_secret_key(self.msk, decryption_policy, self.policy)
plaintext = b'My secret data'
ciphertext = self.cc.encrypt(
self.policy,
'Secrecy::High && Country::Spain',
self.pk,
plaintext,
)
self.policy.rename_attribute(Attribute('Country', 'Spain'), 'Espagne')
self.cc.update_master_keys(self.policy, self.msk, self.pk)
self.cc.refresh_user_secret_key(usk, self.msk, False)
decrypted_text, _ = self.cc.decrypt(usk, ciphertext)
self.assertEqual(decrypted_text, plaintext)
if __name__ == '__main__':
unittest.main()