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
예제 #6
0
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()