def test_void_container_confs(container_conf): key_storage_pool = DummyKeyStoragePool() with pytest.raises(ConfigurationError, match="Empty .* list"): encrypt_data_into_container( data=b"stuffs", conf=container_conf, keychain_uid=None, metadata=None, key_storage_pool=key_storage_pool )
def test_container_encryption_and_decryption(container_conf): data = b"abc" # get_random_bytes(random.randint(1, 1000)) keychain_uid = random.choice( [None, uuid.UUID("450fc293-b702-42d3-ae65-e9cc58e5a62a")]) metadata = random.choice([None, dict(a=[123])]) container = encrypt_data_into_container(data=data, conf=container_conf, keychain_uid=keychain_uid, metadata=metadata) # pprint.pprint(container, width=120) assert container["keychain_uid"] if keychain_uid: assert container["keychain_uid"] == keychain_uid result_data = decrypt_data_from_container(container=container) # pprint.pprint(result, width=120) assert result_data == data result_metadata = extract_metadata_from_container(container=container) assert result_metadata == metadata container["container_format"] = "OAJKB" with pytest.raises(ValueError, match="Unknown container format"): decrypt_data_from_container(container=container)
def test_recursive_shamir_secrets_and_strata(): keychain_uid = generate_uuid0() data = b"qssd apk_$82" container = encrypt_data_into_container( data=data, conf=RECURSIVE_CONTAINER_CONF, keychain_uid=keychain_uid, metadata=None ) data_decrypted = decrypt_data_from_container( container=container, ) assert data_decrypted == data
def test_get_encryption_configuration_summary(): data = b"some data whatever" summary = get_encryption_configuration_summary(SIMPLE_CONTAINER_CONF) assert summary == textwrap.dedent("""\ Data encryption layer 1: AES_CBC Key encryption layers: RSA_OAEP (by local device) Signatures: SHA256/DSA_DSS (by local device) """) # Ending by newline! container = encrypt_data_into_container(data=data, conf=SIMPLE_CONTAINER_CONF, keychain_uid=None, metadata=None) summary2 = get_encryption_configuration_summary(container) assert summary2 == summary # Identical summary for conf and generated containers! # Simulate a conf with remote escrow webservices CONF_WITH_ESCROW = copy.deepcopy(COMPLEX_CONTAINER_CONF) CONF_WITH_ESCROW["data_encryption_strata"][0]["key_encryption_strata"][0][ "key_escrow"] = dict(url="http://www.mydomain.com/json") summary = get_encryption_configuration_summary(CONF_WITH_ESCROW) assert summary == textwrap.dedent("""\ Data encryption layer 1: AES_EAX Key encryption layers: RSA_OAEP (by www.mydomain.com) Signatures: Data encryption layer 2: AES_CBC Key encryption layers: RSA_OAEP (by local device) Signatures: SHA3_512/DSA_DSS (by local device) Data encryption layer 3: CHACHA20_POLY1305 Key encryption layers: RSA_OAEP (by local device) RSA_OAEP (by local device) Signatures: SHA3_256/RSA_PSS (by local device) SHA512/ECC_DSS (by local device) """) # Ending by newline! _public_key = generate_asymmetric_keypair( key_type="RSA_OAEP")["public_key"] with patch.object(JsonRpcProxy, "get_public_key", return_value=_public_key, create=True) as mock_method: container = encrypt_data_into_container(data=data, conf=CONF_WITH_ESCROW, keychain_uid=None, metadata=None) summary2 = get_encryption_configuration_summary(container) assert (summary2 == summary ) # Identical summary for conf and generated containers! # Test unknown escrow structure CONF_WITH_BROKEN_ESCROW = copy.deepcopy(SIMPLE_CONTAINER_CONF) CONF_WITH_BROKEN_ESCROW["data_encryption_strata"][0][ "key_encryption_strata"][0]["key_escrow"] = dict(abc=33) with pytest.raises(ValueError, match="Unrecognized key escrow"): get_encryption_configuration_summary(CONF_WITH_BROKEN_ESCROW)
def _do_encrypt(data, key_storage_pool): container = encrypt_data_into_container(data, conf=EXAMPLE_CONTAINER_CONF, metadata=None, key_storage_pool=key_storage_pool) return container
def _do_encrypt(data): container = encrypt_data_into_container(data, conf=EXAMPLE_CONTAINER_CONF, metadata=None) return container
def test_passphrase_mapping_during_decryption(tmp_path): keychain_uid = generate_uuid0() keychain_uid_escrow = generate_uuid0() local_passphrase = "b^yep&ts" key_storage_uid1 = keychain_uid_escrow # FIXME why mix key and storage uids ? passphrase1 = "tata" key_storage_uid2 = generate_uuid0() passphrase2 = "2çès" key_storage_uid3 = generate_uuid0() passphrase3 = "zaizoadsxsnd123" all_passphrases = [local_passphrase, passphrase1, passphrase2, passphrase3] key_storage_pool = DummyKeyStoragePool() key_storage_pool._register_fake_imported_storage_uids( storage_uids=[key_storage_uid1, key_storage_uid2, key_storage_uid3] ) local_key_storage = key_storage_pool.get_local_key_storage() generate_asymmetric_keypair_for_storage( key_type="RSA_OAEP", key_storage=local_key_storage, keychain_uid=keychain_uid, passphrase=local_passphrase ) key_storage1 = key_storage_pool.get_imported_key_storage(key_storage_uid1) generate_asymmetric_keypair_for_storage( key_type="RSA_OAEP", key_storage=key_storage1, keychain_uid=keychain_uid_escrow, passphrase=passphrase1 ) key_storage2 = key_storage_pool.get_imported_key_storage(key_storage_uid2) generate_asymmetric_keypair_for_storage( key_type="RSA_OAEP", key_storage=key_storage2, keychain_uid=keychain_uid, passphrase=passphrase2 ) key_storage3 = key_storage_pool.get_imported_key_storage(key_storage_uid3) generate_asymmetric_keypair_for_storage( key_type="RSA_OAEP", key_storage=key_storage3, keychain_uid=keychain_uid, passphrase=passphrase3 ) local_escrow_id = get_escrow_id(LOCAL_ESCROW_MARKER) share_escrow1 = dict(escrow_type="authentication_device", authentication_device_uid=key_storage_uid1) share_escrow1_id = get_escrow_id(share_escrow1) share_escrow2 = dict(escrow_type="authentication_device", authentication_device_uid=key_storage_uid2) share_escrow2_id = get_escrow_id(share_escrow2) share_escrow3 = dict(escrow_type="authentication_device", authentication_device_uid=key_storage_uid3) share_escrow3_id = get_escrow_id(share_escrow3) container_conf = dict( data_encryption_strata=[ dict( data_encryption_algo="AES_CBC", key_encryption_strata=[ dict(key_encryption_algo="RSA_OAEP", key_escrow=LOCAL_ESCROW_MARKER), dict( key_encryption_algo=SHARED_SECRET_MARKER, key_shared_secret_threshold=2, key_shared_secret_escrows=[ dict(key_encryption_strata=[ dict(key_encryption_algo="RSA_OAEP", key_escrow=share_escrow1, keychain_uid=keychain_uid_escrow)],), dict(key_encryption_strata=[ dict(key_encryption_algo="RSA_OAEP", key_escrow=share_escrow2)],), dict(key_encryption_strata=[ dict(key_encryption_algo="RSA_OAEP", key_escrow=share_escrow3)],), ], ), ], data_signatures=[ dict( message_digest_algo="SHA256", signature_algo="DSA_DSS", signature_escrow=LOCAL_ESCROW_MARKER, # Uses separate keypair, no passphrase here ) ], ) ] ) data = b"sjzgzj" container = encrypt_data_into_container( data=data, conf=container_conf, keychain_uid=keychain_uid, key_storage_pool=key_storage_pool, metadata=None ) # FIXME we must TEST that keychain_uid_escrow is necessary for decryption for example by deleting it before a decrypt() with pytest.raises(DecryptionError, match="2 valid .* missing for reconstitution"): decrypt_data_from_container(container, key_storage_pool=key_storage_pool) with pytest.raises(DecryptionError, match="2 valid .* missing for reconstitution"): decrypt_data_from_container( container, key_storage_pool=key_storage_pool, passphrase_mapper={local_escrow_id: all_passphrases} ) # Doesn't help share escrows with pytest.raises(DecryptionError, match="1 valid .* missing for reconstitution"): decrypt_data_from_container( container, key_storage_pool=key_storage_pool, passphrase_mapper={share_escrow1_id: all_passphrases} ) # Unblocks 1 share escrow with pytest.raises(DecryptionError, match="1 valid .* missing for reconstitution"): decrypt_data_from_container( container, key_storage_pool=key_storage_pool, passphrase_mapper={share_escrow1_id: all_passphrases, share_escrow2_id: [passphrase3]}, ) # No changes with pytest.raises(DecryptionError, match="Could not decrypt private key"): decrypt_data_from_container( container, key_storage_pool=key_storage_pool, passphrase_mapper={share_escrow1_id: all_passphrases, share_escrow3_id: [passphrase3]}, ) with pytest.raises(DecryptionError, match="Could not decrypt private key"): decrypt_data_from_container( container, key_storage_pool=key_storage_pool, passphrase_mapper={ local_escrow_id: ["qsdqsd"], share_escrow1_id: all_passphrases, share_escrow3_id: [passphrase3], }, ) decrypted = decrypt_data_from_container( container, key_storage_pool=key_storage_pool, passphrase_mapper={ local_escrow_id: [local_passphrase], share_escrow1_id: all_passphrases, share_escrow3_id: [passphrase3], }, ) assert decrypted == data # Passphrases of `None` key are always used decrypted = decrypt_data_from_container( container, key_storage_pool=key_storage_pool, passphrase_mapper={ local_escrow_id: [local_passphrase], share_escrow1_id: ["dummy-passphrase"], share_escrow3_id: [passphrase3], None: all_passphrases, }, ) assert decrypted == data # Proper forwarding of parameters in container storage class storage = ContainerStorage(tmp_path, key_storage_pool=key_storage_pool) storage.enqueue_file_for_encryption( "beauty.txt", data=data, metadata=None, keychain_uid=keychain_uid, encryption_conf=container_conf ) storage.wait_for_idle_state() container_names = storage.list_container_names(as_sorted=True) print(">> container_names", container_names) with pytest.raises(DecryptionError): storage.decrypt_container_from_storage("beauty.txt.crypt") decrypted = storage.decrypt_container_from_storage("beauty.txt.crypt", passphrase_mapper={None: all_passphrases}) assert decrypted == data
def test_shamir_container_encryption_and_decryption(shamir_container_conf, escrow_dependencies_builder): data = b"abc" # get_random_bytes(random.randint(1, 1000)) # FIXME reactivate ??? keychain_uid = random.choice([None, uuid.UUID("450fc293-b702-42d3-ae65-e9cc58e5a62a")]) metadata = random.choice([None, dict(a=[123])]) container = encrypt_data_into_container( data=data, conf=shamir_container_conf, keychain_uid=keychain_uid, metadata=metadata ) assert container["keychain_uid"] if keychain_uid: assert container["keychain_uid"] == keychain_uid escrow_dependencies = gather_escrow_dependencies(containers=[container]) assert escrow_dependencies == escrow_dependencies_builder(container["keychain_uid"]) assert isinstance(container["data_ciphertext"], bytes) result_data = decrypt_data_from_container(container=container) assert result_data == data data_encryption_shamir = {} # Delete 1, 2 and too many share(s) from cipherdict key for data_encryption in container["data_encryption_strata"]: for key_encryption in data_encryption["key_encryption_strata"]: if key_encryption["key_encryption_algo"] == SHARED_SECRET_MARKER: data_encryption_shamir = data_encryption key_ciphertext_shares = load_from_json_bytes(data_encryption_shamir["key_ciphertext"]) # 1 share is deleted del key_ciphertext_shares["shares"][-1] data_encryption_shamir["key_ciphertext"] = dump_to_json_bytes(key_ciphertext_shares) result_data = decrypt_data_from_container(container=container) assert result_data == data # Another share is deleted del key_ciphertext_shares["shares"][-1] data_encryption_shamir["key_ciphertext"] = dump_to_json_bytes(key_ciphertext_shares) result_data = decrypt_data_from_container(container=container) assert result_data == data # Another share is deleted and now there aren't enough valid ones to decipher data del key_ciphertext_shares["shares"][-1] data_encryption_shamir["key_ciphertext"] = dump_to_json_bytes(key_ciphertext_shares) with pytest.raises(DecryptionError, match="share.*missing"): decrypt_data_from_container(container=container) result_metadata = extract_metadata_from_container(container=container) assert result_metadata == metadata container["container_format"] = "OAJKB" with pytest.raises(ValueError, match="Unknown container format"): decrypt_data_from_container(container=container)
def test_standard_container_encryption_and_decryption(container_conf, escrow_dependencies_builder): data = b"abc" # get_random_bytes(random.randint(1, 1000)) keychain_uid = random.choice([None, uuid.UUID("450fc293-b702-42d3-ae65-e9cc58e5a62a")]) key_storage_pool = DummyKeyStoragePool() metadata = random.choice([None, dict(a=[123])]) container = encrypt_data_into_container( data=data, conf=container_conf, keychain_uid=keychain_uid, metadata=metadata, key_storage_pool=key_storage_pool ) assert container["keychain_uid"] if keychain_uid: assert container["keychain_uid"] == keychain_uid local_keypair_identifiers = key_storage_pool.get_local_key_storage()._cached_keypairs print(">>> Test local_keypair_identifiers ->", list(local_keypair_identifiers.keys())) escrow_dependencies = gather_escrow_dependencies(containers=[container]) print("GOTTEN DEPENDENCIES:") pprint(escrow_dependencies) print("THEORETICAL DEPENDENCIES:") pprint(escrow_dependencies_builder(container["keychain_uid"])) assert escrow_dependencies == escrow_dependencies_builder(container["keychain_uid"]) # Check that all referenced keys were really created during encryption (so keychain_uid overriding works fine) for escrow_dependency_structs in escrow_dependencies.values(): for escrow_dependency_struct in escrow_dependency_structs.values(): escrow_conf, keypairs_identifiers = escrow_dependency_struct escrow = get_escrow_proxy(escrow_conf, key_storage_pool=key_storage_pool) for keypairs_identifier in keypairs_identifiers: assert escrow.fetch_public_key(**keypairs_identifier, must_exist=True) all_authorization_results = request_decryption_authorizations( escrow_dependencies=escrow_dependencies, request_message="Decryption needed", key_storage_pool=key_storage_pool ) # Generic check of data structure for authorization_results in all_authorization_results.values(): assert not authorization_results["has_errors"] assert "accepted" in authorization_results["response_message"] keypair_statuses = authorization_results["keypair_statuses"] assert keypair_statuses["accepted"] for keypair_identifiers in keypair_statuses["accepted"]: assert keypair_identifiers["key_type"] in SUPPORTED_ENCRYPTION_ALGOS assert isinstance(keypair_identifiers["keychain_uid"], UUID) assert not keypair_statuses["authorization_missing"] assert not keypair_statuses["missing_passphrase"] assert not keypair_statuses["missing_private_key"] result_data = decrypt_data_from_container(container=container, key_storage_pool=key_storage_pool) # pprint.pprint(result, width=120) assert result_data == data result_metadata = extract_metadata_from_container(container=container) assert result_metadata == metadata container["container_format"] = "OAJKB" with pytest.raises(ValueError, match="Unknown container format"): decrypt_data_from_container(container=container)
def test_filesystem_container_loading_and_dumping(tmp_path, container_conf): data = b"jhf" * 200 keychain_uid = random.choice([None, uuid.UUID("450fc293-b702-42d3-ae65-e9cc58e5a62a")]) metadata = random.choice([None, dict(a=[123])]) container = encrypt_data_into_container( data=data, conf=container_conf, keychain_uid=keychain_uid, metadata=metadata ) container_ciphertext_before_dump = container["data_ciphertext"] container_without_ciphertext = copy.deepcopy(container) del container_without_ciphertext["data_ciphertext"] # CASE 1 - MONOLITHIC JSON FILE container_filepath = tmp_path / "mycontainer_monolithic.crypt" dump_container_to_filesystem(container_filepath, container=container, offload_data_ciphertext=False) container_reloaded = load_from_json_file(container_filepath) assert container_reloaded["data_ciphertext"] == container_ciphertext_before_dump # NO OFFLOADING assert load_container_from_filesystem(container_filepath) == container # UNCHANGED from original container_truncated = load_container_from_filesystem(container_filepath, include_data_ciphertext=False) assert "data_ciphertext" not in container_truncated assert container_truncated == container_without_ciphertext assert container["data_ciphertext"] == container_ciphertext_before_dump # Original dict unchanged size1 = get_container_size_on_filesystem(container_filepath) assert size1 assert container_filepath.exists() #delete_container_from_filesystem(container_filepath) #assert not container_filepath.exists() # CASE 2 - OFFLOADED CIPHERTEXT FILE container_filepath = tmp_path / "mycontainer_offloaded.crypt" dump_container_to_filesystem(container_filepath, container=container) # OVERWRITE, with offloading by default container_reloaded = load_from_json_file(container_filepath) assert container_reloaded["data_ciphertext"] == "[OFFLOADED]" container_offloaded_filepath = Path(str(container_filepath) + ".data") offloaded_data_reloaded = container_offloaded_filepath.read_bytes() assert offloaded_data_reloaded == container_ciphertext_before_dump # WELL OFFLOADED as DIRECT BYTES assert load_container_from_filesystem(container_filepath) == container # UNCHANGED from original container_truncated = load_container_from_filesystem(container_filepath, include_data_ciphertext=False) assert "data_ciphertext" not in container_truncated assert container_truncated == container_without_ciphertext assert container["data_ciphertext"] == container_ciphertext_before_dump # Original dict unchanged size2 = get_container_size_on_filesystem(container_filepath) assert size2 < size1 # Overhead of base64 encoding in monolithic file! assert size1 < size2 + 1000 # Overhead remaings limited though assert container_filepath.exists() assert container_offloaded_filepath.exists() delete_container_from_filesystem(container_filepath) assert not container_filepath.exists() assert not container_offloaded_filepath.exists()