def test_key_storage_pool_basics(tmp_path: Path): pool = FilesystemKeyStoragePool(tmp_path) local_key_storage = pool.get_local_key_storage() assert isinstance(local_key_storage, FilesystemKeyStorage) assert not local_key_storage.list_keypair_identifiers() keypair = generate_asymmetric_keypair_for_storage( key_type="RSA_OAEP", key_storage=local_key_storage, passphrase="xzf".encode()) assert len(local_key_storage.list_keypair_identifiers()) == 1 assert pool.list_imported_key_storage_uids() == [] imported_key_storage_uid = generate_uuid0() mirror_path = tmp_path.joinpath( pool.IMPORTED_STORAGES_DIRNAME, pool.IMPORTED_STORAGE_PREFIX + str(imported_key_storage_uid)) mirror_path.mkdir(parents=True, exist_ok=False) imported_key_storage_uid2 = generate_uuid0() mirror_path2 = tmp_path.joinpath( pool.IMPORTED_STORAGES_DIRNAME, pool.IMPORTED_STORAGE_PREFIX + str(imported_key_storage_uid2)) mirror_path2.mkdir(parents=True, exist_ok=False) assert pool.list_imported_key_storage_uids() == sorted( [imported_key_storage_uid, imported_key_storage_uid2]) with pytest.raises(KeyStorageDoesNotExist, match="not found"): pool.get_imported_key_storage(generate_uuid0()) imported_key_storage = pool.get_imported_key_storage( imported_key_storage_uid) assert isinstance(imported_key_storage, FilesystemKeyStorage) assert not imported_key_storage.list_keypair_identifiers() imported_key_storage.set_keys( keychain_uid=generate_uuid0(), key_type="RSA_OAEP", public_key=keypair["public_key"], private_key=keypair["private_key"], ) assert len(local_key_storage.list_keypair_identifiers()) == 1 # Unchanged assert len(imported_key_storage.list_keypair_identifiers()) == 1 imported_key_storage2 = pool.get_imported_key_storage( imported_key_storage_uid2) assert isinstance(imported_key_storage2, FilesystemKeyStorage) assert not imported_key_storage2.list_keypair_identifiers()
def check_key_storage_free_keys_api(key_storage): """Test the storage regarding the precreation of "free keys", and their subsequent attachment to uids.""" import pytest keychain_uid = generate_uuid0() keychain_uid_other = generate_uuid0() # This blocks free key attachment to this uid+type key_storage.set_keys(keychain_uid=keychain_uid, key_type="type1", public_key=b"whatever1", private_key=b"whatever2") key_storage.add_free_keypair(key_type="type1", public_key=b"public_data", private_key=b"private_data") key_storage.add_free_keypair(key_type="type1", public_key=b"public_data2", private_key=b"private_data2") key_storage.add_free_keypair( key_type="type2", public_key=b"public_data_other_type", private_key=b"private_data_other_type" ) assert key_storage.get_free_keypairs_count("type1") == 2 assert key_storage.get_free_keypairs_count("type2") == 1 assert key_storage.get_free_keypairs_count("type3") == 0 with pytest.raises(KeyAlreadyExists, match="Already existing"): key_storage.attach_free_keypair_to_uuid(keychain_uid=keychain_uid, key_type="type1") with pytest.raises(KeyDoesNotExist, match="not found"): key_storage.get_public_key(keychain_uid=keychain_uid, key_type="type2") key_storage.attach_free_keypair_to_uuid(keychain_uid=keychain_uid, key_type="type2") assert b"public_data" in key_storage.get_public_key(keychain_uid=keychain_uid, key_type="type2") assert key_storage.get_free_keypairs_count("type1") == 2 assert key_storage.get_free_keypairs_count("type2") == 0 assert key_storage.get_free_keypairs_count("type3") == 0 key_storage.attach_free_keypair_to_uuid(keychain_uid=keychain_uid_other, key_type="type1") assert key_storage.get_free_keypairs_count("type1") == 1 assert key_storage.get_free_keypairs_count("type2") == 0 assert key_storage.get_free_keypairs_count("type3") == 0 with pytest.raises(KeyDoesNotExist, match="No free keypair of type"): key_storage.attach_free_keypair_to_uuid(keychain_uid=keychain_uid_other, key_type="type2") with pytest.raises(KeyDoesNotExist, match="No free keypair of type"): key_storage.attach_free_keypair_to_uuid(keychain_uid=keychain_uid, key_type="type3") assert key_storage.get_free_keypairs_count("type1") == 1 assert key_storage.get_free_keypairs_count("type2") == 0 assert key_storage.get_free_keypairs_count("type3") == 0 return locals()
def _offloaded_initialize_authenticator(self, form_values, authenticator_path): success = False try: Clock.schedule_once(partial(self._do_update_progress_bar, 10)) initialize_authenticator(authenticator_path, user=form_values["user"], extra_metadata=dict(passphrase_hint=form_values["passphrase_hint"])) filesystem_key_storage = FilesystemKeyStorage(authenticator_path) for i in range(1, GENERATED_KEYS_COUNT+1): # TODO add some logging here key_pair = generate_asymmetric_keypair( key_type="RSA_OAEP", passphrase=form_values["passphrase"] ) filesystem_key_storage.set_keys( keychain_uid=generate_uuid0(), key_type="RSA_OAEP", public_key=key_pair["public_key"], private_key=key_pair["private_key"], ) Clock.schedule_once(partial(self._do_update_progress_bar, 10 + int (i * 90 / GENERATED_KEYS_COUNT))) success = True except Exception as exc: print(">> ERROR IN _offloaded_initialize_authenticator THREAD", exc) # FIXME add logging AND snackbar Clock.schedule_once(partial(self.finish_initialization, success=success))
def test_key_storage_import_key_storage_from_folder(tmp_path: Path): pool_path = tmp_path / "pool" pool_path.mkdir() pool = FilesystemKeyStoragePool(pool_path) assert pool.list_imported_key_storage_uids() == [] assert pool.list_imported_key_storage_metadata() == {} authentication_device_path = tmp_path / "device" authentication_device_path.mkdir() authentication_device = get_fake_authentication_device( authentication_device_path) initialize_authentication_device(authentication_device, user="******") keychain_uid = generate_uuid0() key_type = "RSA_OAEP" remote_key_storage_path = _get_key_storage_folder_path( authentication_device) remote_key_storage = FilesystemKeyStorage(remote_key_storage_path) remote_key_storage.set_keys(keychain_uid=keychain_uid, key_type=key_type, public_key=b"555", private_key=b"okj") # Still untouched of course assert pool.list_imported_key_storage_uids() == [] assert pool.list_imported_key_storage_metadata() == {} pool.import_key_storage_from_folder(remote_key_storage_path) (key_storage_uid, ) = pool.list_imported_key_storage_uids() metadata_mapper = pool.list_imported_key_storage_metadata() assert tuple(metadata_mapper) == (key_storage_uid, ) metadata = metadata_mapper[key_storage_uid] assert metadata["device_uid"] == key_storage_uid assert metadata["user"] == "Jean-Jâcques" with pytest.raises(KeyStorageAlreadyExists, match=str(key_storage_uid)): pool.import_key_storage_from_folder(remote_key_storage_path) shutil.rmtree(authentication_device_path) # Not important anymore assert pool.list_imported_key_storage_uids() == [key_storage_uid] metadata_mapper2 = pool.list_imported_key_storage_metadata() assert metadata_mapper2 == metadata_mapper key_storage = pool.get_imported_key_storage(key_storage_uid) assert key_storage.list_keypair_identifiers() == [ dict(keychain_uid=keychain_uid, key_type=key_type, private_key_present=True) ] assert key_storage.get_public_key(keychain_uid=keychain_uid, key_type=key_type) == b"555" assert key_storage.get_private_key(keychain_uid=keychain_uid, key_type=key_type) == b"okj"
def _generate_authenticator_parameter_tree(key_count, key_value=None): public_keys = [] for count in range(key_count): public_keys.append({ "keychain_uid": generate_uuid0(), "key_algo": "RSA_OAEP", "key_value": key_value or get_random_bytes(20) }) parameters = dict( keystore_owner="keystore_owner", keystore_secret="keystore_secret", keystore_uid=generate_uuid0(), public_keys=public_keys ) return parameters
def check_key_storage_basic_get_set_api(key_storage): """Test the workflow of getters/setters of the storage API, for uid-attached keys.""" import pytest keychain_uid = generate_uuid0() keychain_uid_other = generate_uuid0() key_type = "abxz" with pytest.raises(KeyDoesNotExist, match="not found"): key_storage.get_public_key(keychain_uid=keychain_uid, key_type="abxz") key_storage.set_keys( keychain_uid=keychain_uid, key_type=key_type, public_key=b"public_data", private_key=b"private_data" ) assert ( key_storage.get_public_key(keychain_uid=keychain_uid, key_type="abxz") == b"public_data" ) # Well readable even without any kind of "commit" with pytest.raises(KeyAlreadyExists, match="Already existing"): key_storage.set_keys( keychain_uid=keychain_uid, key_type=key_type, public_key=b"public_data", private_key=b"private_data" ) with pytest.raises(KeyAlreadyExists, match="Already existing"): key_storage.set_keys( keychain_uid=keychain_uid, key_type=key_type, public_key=b"public_data2", private_key=b"private_data2" ) assert key_storage.get_public_key(keychain_uid=keychain_uid, key_type=key_type) == b"public_data" assert key_storage.get_private_key(keychain_uid=keychain_uid, key_type=key_type) == b"private_data" with pytest.raises(KeyDoesNotExist, match="not found"): key_storage.get_public_key(keychain_uid=keychain_uid, key_type=key_type + "_") with pytest.raises(KeyDoesNotExist, match="not found"): key_storage.get_private_key(keychain_uid=keychain_uid, key_type=key_type + "_") with pytest.raises(KeyDoesNotExist, match="not found"): key_storage.get_public_key(keychain_uid=keychain_uid_other, key_type=key_type) with pytest.raises(KeyDoesNotExist, match="not found"): key_storage.get_private_key(keychain_uid=keychain_uid_other, key_type=key_type) return locals()
def test_generate_uuid0(): utc = pytz.UTC some_date = datetime(year=2000, month=6, day=12, tzinfo=timezone.min) some_timestamp = datetime.timestamp(some_date) uuid0 = generate_uuid0(some_timestamp) assert utc.localize(uuid0.datetime) == some_date assert uuid0.datetime_local != some_date.replace( tzinfo=None) # Local TZ is used here assert uuid0.unix_ts == some_timestamp uuids = [generate_uuid0().int for _ in range(1000)] assert len(set(uuids)) == 1000 uuids = [generate_uuid0(some_timestamp).int for _ in range(1000)] assert len(set(uuids)) == 1000 uuid_test = generate_uuid0(0) assert uuid_test.unix_ts != 0 # Can't generate UUIDs with timestamp=0
def _common_authentication_device_initialization(authentication_device: dict, user: str, extra_metadata: dict): assert isinstance(user, str) and user, repr(user) metadata_file = _get_metadata_file_path(authentication_device) metadata_file.parent.mkdir(parents=True, exist_ok=True) metadata = extra_metadata.copy() metadata.update({ "device_uid": generate_uuid0(), "user": user }) # Override keys! dump_to_json_file(metadata_file, metadata) return metadata
def fuzz(): jsonrpc_url = sys.argv[1] if len( sys.argv) > 1 else "http://localhost:8000/json/" trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) for i in range(100): keychain_uid = generate_uuid0() key_cipher_algo = random.choice(SUPPORTED_ASYMMETRIC_KEY_ALGOS) print("Fetching key of type %s... " % key_cipher_algo, end="") trustee_proxy.fetch_public_key(keychain_uid=keychain_uid, key_algo=key_cipher_algo) print("DONE")
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_jsonrpc_trustee_signature(live_server): jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) keychain_uid = generate_uuid0() payload_signature_algo = "DSA_DSS" secret = get_random_bytes(101) secret_too_big = get_random_bytes(150) public_key_signature_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid, key_algo=payload_signature_algo) public_key_signature = load_asymmetric_key_from_pem_bytestring( key_pem=public_key_signature_pem, key_algo=payload_signature_algo) signature = trustee_proxy.get_message_signature( keychain_uid=keychain_uid, message=secret, signature_algo=payload_signature_algo) with pytest.raises(ValidationError, match="too big"): trustee_proxy.get_message_signature( keychain_uid=keychain_uid, message=secret_too_big, signature_algo=payload_signature_algo, ) verify_message_signature( message=secret, signature=signature, key=public_key_signature, signature_algo=payload_signature_algo, ) signature["signature_value"] += b"xyz" with pytest.raises(SignatureVerificationError, match="not authentic|Incorrect signature"): verify_message_signature( message=secret, signature=signature, key=public_key_signature, signature_algo=payload_signature_algo, )
def generate_asymmetric_keypair_for_storage( key_type: str, *, key_storage, keychain_uid: Optional[UUID] = None, passphrase: Optional[AnyStr] = None ) -> dict: """ Shortcut to generate an asymmetric keypair and store it into a key storage. `keychain_uid` is auto-generated if not provided. Returns the generated keypair dict. """ from wacryptolib.key_generation import generate_asymmetric_keypair from wacryptolib.key_storage import KeyStorageBase keychain_uid = keychain_uid or generate_uuid0() keypair = generate_asymmetric_keypair(key_type=key_type, serialize=True, passphrase=passphrase) key_storage.set_keys( keychain_uid=keychain_uid, key_type=key_type, public_key=keypair["public_key"], private_key=keypair["private_key"], ) return keypair
def test_escrow_api_workflow(): key_storage = DummyKeyStorage() escrow_api = EscrowApi(key_storage=key_storage) keychain_uid = generate_uuid0() keychain_uid_other = generate_uuid0() keychain_uid_unexisting = generate_uuid0() secret = get_random_bytes(127) secret_too_big = get_random_bytes(140) for _ in range(2): generate_free_keypair_for_least_provisioned_key_type( key_storage=key_storage, max_free_keys_per_type=10, key_types=["RSA_OAEP", "DSA_DSS"]) assert key_storage.get_free_keypairs_count("DSA_DSS") == 1 assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 1 assert key_storage.get_free_keypairs_count( "RSA_PSS") == 0 # Different from other RSA keys # Keypair is well auto-created by get_public_key(), by default public_key_rsa_oaep_pem = escrow_api.fetch_public_key( keychain_uid=keychain_uid, key_type="RSA_OAEP") with pytest.raises(KeyDoesNotExist, match="not found"): # Key NOT autogenerated escrow_api.fetch_public_key(keychain_uid=generate_uuid0(), key_type="RSA_OAEP", must_exist=True) _public_key_rsa_oaep_pem2 = escrow_api.fetch_public_key( keychain_uid=keychain_uid, key_type="RSA_OAEP") assert _public_key_rsa_oaep_pem2 == public_key_rsa_oaep_pem # Same KEYS! _public_key_rsa_pss_pem = escrow_api.fetch_public_key( keychain_uid=keychain_uid, key_type="RSA_PSS") assert _public_key_rsa_pss_pem != public_key_rsa_oaep_pem # Different KEYS! public_key_rsa_oaep = load_asymmetric_key_from_pem_bytestring( key_pem=public_key_rsa_oaep_pem, key_type="RSA_OAEP") assert key_storage.get_free_keypairs_count("DSA_DSS") == 1 assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 0 # Taken assert key_storage.get_free_keypairs_count("RSA_PSS") == 0 signature = escrow_api.get_message_signature(keychain_uid=keychain_uid, message=secret, signature_algo="DSA_DSS") with pytest.raises(ValueError, match="too big"): escrow_api.get_message_signature(keychain_uid=keychain_uid, message=secret_too_big, signature_algo="DSA_DSS") assert key_storage.get_free_keypairs_count("DSA_DSS") == 0 # Taken assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 0 assert key_storage.get_free_keypairs_count("RSA_PSS") == 0 public_key_dsa_pem = escrow_api.fetch_public_key(keychain_uid=keychain_uid, key_type="DSA_DSS") public_key_dsa = load_asymmetric_key_from_pem_bytestring( key_pem=public_key_dsa_pem, key_type="DSA_DSS") verify_message_signature(message=secret, signature=signature, key=public_key_dsa, signature_algo="DSA_DSS") signature["digest"] += b"xyz" with pytest.raises(SignatureVerificationError, match="Failed.*verification"): verify_message_signature(message=secret, signature=signature, key=public_key_dsa, signature_algo="DSA_DSS") # Keypair is well auto-created by get_message_signature(), even when no more free keys signature = escrow_api.get_message_signature( keychain_uid=keychain_uid_other, message=secret, signature_algo="RSA_PSS") assert signature # Keypair well autocreated by get_public_key(), even when no more free keys public_key_pem = escrow_api.fetch_public_key( keychain_uid=keychain_uid_other, key_type="DSA_DSS") assert public_key_pem cipherdict = _encrypt_via_rsa_oaep(plaintext=secret, key=public_key_rsa_oaep) # Works even without decryption authorization request, by default: decrypted = escrow_api.decrypt_with_private_key(keychain_uid=keychain_uid, encryption_algo="RSA_OAEP", cipherdict=cipherdict) assert decrypted == secret # NO auto-creation of keypair in decrypt_with_private_key() with pytest.raises(KeyDoesNotExist, match="not found"): escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid_unexisting, encryption_algo="RSA_OAEP", cipherdict=cipherdict) wrong_cipherdict = copy.deepcopy(cipherdict) wrong_cipherdict["digest_list"].append(b"aaabbbccc") with pytest.raises(ValueError, match="Ciphertext with incorrect length"): escrow_api.decrypt_with_private_key(keychain_uid=keychain_uid, encryption_algo="RSA_OAEP", cipherdict=wrong_cipherdict) with pytest.raises(ValueError, match="empty"): escrow_api.request_decryption_authorization( keypair_identifiers=[], request_message="I need this decryption!") # Authorization always granted for now, in dummy implementation result = escrow_api.request_decryption_authorization( keypair_identifiers=[ dict(keychain_uid=keychain_uid, key_type="RSA_OAEP") ], request_message="I need this decryption!", ) assert "accepted" in result["response_message"] assert not result["has_errors"] assert result["keypair_statuses"]["accepted"] # TEST PASSPHRASE PROTECTIONS keychain_uid_passphrased = generate_uuid0() good_passphrase = "good_passphrase" keypair_cipher_passphrased = generate_asymmetric_keypair_for_storage( key_type="RSA_OAEP", key_storage=key_storage, keychain_uid=keychain_uid_passphrased, passphrase=good_passphrase) result = escrow_api.request_decryption_authorization( keypair_identifiers=[ dict(keychain_uid=keychain_uid_passphrased, key_type="RSA_OAEP") ], request_message="I need this decryption too!", ) assert "denied" in result["response_message"] assert result["has_errors"] assert result["keypair_statuses"]["missing_passphrase"] result = escrow_api.request_decryption_authorization( keypair_identifiers=[ dict(keychain_uid=keychain_uid_passphrased, key_type="RSA_OAEP") ], request_message="I need this decryption too!", passphrases=["aaa"], ) assert "denied" in result["response_message"] assert result["has_errors"] assert result["keypair_statuses"]["missing_passphrase"] result = escrow_api.request_decryption_authorization( keypair_identifiers=[ dict(keychain_uid=keychain_uid_passphrased, key_type="RSA_OAEP") ], request_message="I need this decryption too!", passphrases=["dsd", good_passphrase], ) assert "accepted" in result["response_message"] assert not result["has_errors"] assert result["keypair_statuses"]["accepted"] public_key_rsa_oaep2 = load_asymmetric_key_from_pem_bytestring( key_pem=keypair_cipher_passphrased["public_key"], key_type="RSA_OAEP") cipherdict = _encrypt_via_rsa_oaep(plaintext=secret, key=public_key_rsa_oaep2) with pytest.raises(DecryptionError, match="not decrypt"): escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid_passphrased, encryption_algo="RSA_OAEP", cipherdict=cipherdict) with pytest.raises(DecryptionError, match="not decrypt"): escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid_passphrased, encryption_algo="RSA_OAEP", cipherdict=cipherdict, passphrases=["something"], ) decrypted = escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid_passphrased, encryption_algo="RSA_OAEP", cipherdict=cipherdict, passphrases=[good_passphrase], ) assert decrypted == secret assert key_storage.get_free_keypairs_count("DSA_DSS") == 0 assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 0 assert key_storage.get_free_keypairs_count("RSA_PSS") == 0
def test_readonly_escrow_api_behaviour(): key_storage = DummyKeyStorage() escrow_api = ReadonlyEscrowApi(key_storage=key_storage) keychain_uid = generate_uuid0() key_type_cipher = "RSA_OAEP" key_type_signature = "RSA_PSS" secret = get_random_bytes(127) for must_exist in (True, False): with pytest.raises(KeyDoesNotExist, match="not found"): escrow_api.fetch_public_key(keychain_uid=keychain_uid, key_type=key_type_signature, must_exist=must_exist) with pytest.raises(KeyDoesNotExist, match="not found"): escrow_api.get_message_signature(keychain_uid=keychain_uid, message=secret, signature_algo="RSA_PSS") # Always accepted for now, dummy implementation result = escrow_api.request_decryption_authorization( keypair_identifiers=[ dict(keychain_uid=keychain_uid, key_type=key_type_cipher) ], request_message="I need this decryption!", ) assert "denied" in result["response_message"] assert result["has_errors"] assert result["keypair_statuses"]["missing_private_key"] # Still no auto-creation of keypair in decrypt_with_private_key() with pytest.raises(KeyDoesNotExist, match="not found"): escrow_api.decrypt_with_private_key(keychain_uid=keychain_uid, encryption_algo=key_type_cipher, cipherdict={}) # Now we generate wanted keys # keypair_cipher = generate_asymmetric_keypair_for_storage( key_type=key_type_cipher, key_storage=key_storage, keychain_uid=keychain_uid) keypair_signature = generate_asymmetric_keypair_for_storage( key_type=key_type_signature, key_storage=key_storage, keychain_uid=keychain_uid) public_key2 = escrow_api.fetch_public_key(keychain_uid=keychain_uid, key_type=key_type_signature, must_exist=must_exist) assert public_key2 == keypair_signature["public_key"] signature = escrow_api.get_message_signature(keychain_uid=keychain_uid, message=secret, signature_algo="RSA_PSS") assert signature and isinstance(signature, dict) private_key_cipher = load_asymmetric_key_from_pem_bytestring( key_pem=keypair_cipher["private_key"], key_type=key_type_cipher) cipherdict = _encrypt_via_rsa_oaep(plaintext=secret, key=private_key_cipher) decrypted = escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid, encryption_algo=key_type_cipher, cipherdict=cipherdict) assert decrypted == secret
def encrypt_data(self, data: bytes, *, conf: dict, keychain_uid=None, metadata=None) -> dict: assert metadata is None or isinstance(metadata, dict), metadata container_format = CONTAINER_FORMAT container_uid = generate_uuid0() # ALWAYS UNIQUE! keychain_uid = (keychain_uid or generate_uuid0() ) # Might be shared by lots of containers conf = copy.deepcopy(conf) # So that we can manipulate it assert isinstance(data, bytes), data assert isinstance(conf, dict), conf data_current = data # Initially unencrypted, might remain so if no strata result_data_encryption_strata = [] for data_encryption_stratum in conf["data_encryption_strata"]: data_encryption_algo = data_encryption_stratum[ "data_encryption_algo"] logger.debug("Generating symmetric key of type %r", data_encryption_algo) symmetric_key = generate_symmetric_key( encryption_algo=data_encryption_algo) logger.debug("Encrypting data with symmetric key of type %r", data_encryption_algo) data_cipherdict = encrypt_bytestring( plaintext=data_current, encryption_algo=data_encryption_algo, key=symmetric_key, ) assert isinstance(data_cipherdict, dict), data_cipherdict data_current = dump_to_json_bytes(data_cipherdict) symmetric_key_data = ( symmetric_key ) # Initially unencrypted, might remain so if no strata result_key_encryption_strata = [] for key_encryption_stratum in data_encryption_stratum[ "key_encryption_strata"]: symmetric_key_cipherdict = self._encrypt_symmetric_key( keychain_uid=keychain_uid, symmetric_key_data=symmetric_key_data, conf=key_encryption_stratum, ) symmetric_key_data = dump_to_json_bytes( symmetric_key_cipherdict) # Remain as bytes all along result_key_encryption_strata.append( key_encryption_stratum) # Unmodified for now data_signatures = [] for signature_conf in data_encryption_stratum["data_signatures"]: signature_value = self._generate_signature( keychain_uid=keychain_uid, data_ciphertext=data_current, conf=signature_conf, ) signature_conf["signature_value"] = signature_value data_signatures.append(signature_conf) result_data_encryption_strata.append( dict( data_encryption_algo=data_encryption_algo, key_ciphertext=symmetric_key_data, key_encryption_strata=result_key_encryption_strata, data_signatures=data_signatures, )) data_ciphertext = ( data_current ) # New fully encrypted (unless data_encryption_strata is empty) return dict( container_format=container_format, container_uid=container_uid, keychain_uid=keychain_uid, data_ciphertext=data_ciphertext, data_encryption_strata=result_data_encryption_strata, metadata=metadata, )
def test_key_storage_list_keypair_identifiers(tmp_path: Path): def _check_key_dict_format(key): print(">> public key detected:", key) assert isinstance(key["keychain_uid"], UUID) assert key["key_type"] in SUPPORTED_ASYMMETRIC_KEY_TYPES assert isinstance(key["private_key_present"], bool) key_storage = FilesystemKeyStorage(tmp_path) assert key_storage.list_keypair_identifiers() == [] # CASE 1 : only one key in storage key_type = random.choice(SUPPORTED_ASYMMETRIC_KEY_TYPES) keychain_uid = generate_uuid0() generate_asymmetric_keypair_for_storage(key_type=key_type, key_storage=key_storage, keychain_uid=keychain_uid) keys_list = key_storage.list_keypair_identifiers() assert isinstance(keys_list, list) assert len(keys_list) == 1 single_key = keys_list[0] _check_key_dict_format(single_key) assert single_key["keychain_uid"] == keychain_uid assert single_key["key_type"] == key_type assert single_key["private_key_present"] # CASE 2 : multiple public keys, with or without private keys for i in range(3): _key_type = random.choice(SUPPORTED_ASYMMETRIC_KEY_TYPES) generate_asymmetric_keypair_for_storage(key_type=_key_type, key_storage=key_storage, passphrase="xzf".encode()) for bad_filename in ( "0e896f1d-a4d0-67d6-7286-056f1ec342e8_RSA_OAEP_public_key.dot", "0e896f1d-a4d0-67d6-7286-056f1ec342e8_RSA_OAEP_publicX_key.pem", "a4d0-67d6-7286-056f1ec342e8_RSA_OAEP_public_key.pem", "WRONGPREFIX_public_key.pem", ): tmp_path.joinpath( bad_filename).touch() # These will be ignored thanks to Regex keys_list = key_storage.list_keypair_identifiers() assert isinstance(keys_list, list) assert len(keys_list) == 4 assert keys_list == sorted( keys_list, key=lambda x: (x["keychain_uid"], x["key_type"])) # Well sorted for some_key in keys_list: _check_key_dict_format(some_key) assert single_key["private_key_present"] # ALWAYS for now for filepath in tmp_path.glob("*" + FilesystemKeyStorage._private_key_suffix): filepath.unlink() keys_list = key_storage.list_keypair_identifiers() assert isinstance(keys_list, list) assert len(keys_list) == 4 for some_key in keys_list: _check_key_dict_format(some_key) assert not some_key["private_key_present"] # Private keys were deleted # CASE 3 : keys all deleted for filepath in tmp_path.glob("*.pem"): filepath.unlink() assert key_storage.list_keypair_identifiers() == []
def test_jsonrpc_trustee_request_decryption_authorization_for_normal_keys( live_server): jsonrpc_url = jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) key_cipher_algo = "RSA_OAEP" with freeze_time( ) as frozen_datetime: # TEST AUTHORIZATION REQUEST HANDLING keychain_uid1 = generate_uuid0() keychain_uid2 = generate_uuid0() keychain_uid3 = generate_uuid0() keychain_uid4 = generate_uuid0() keychain_uid_unexisting = generate_uuid0() all_keypair_identifiers = [ dict(keychain_uid=keychain_uid1, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid2, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid3, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid4, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid_unexisting, key_algo=key_cipher_algo), ] public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid1, key_algo=key_cipher_algo) assert public_key_pem assert not _fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at # Non-pregenerated keys don't have that field set! assert not _fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).attached_at result = trustee_proxy.request_decryption_authorization( keypair_identifiers=[], request_message="I want decryption!") assert result["success_count"] == 0 assert result["too_old_count"] == 0 assert result["not_found_count"] == 0 frozen_datetime.tick(delta=timedelta(minutes=2)) result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_keypair_identifiers, request_message="I want decryption!", ) assert result["success_count"] == 1 assert result["too_old_count"] == 0 assert (result["not_found_count"] == 4 ) # keychain_uid2 and keychain_uid3 not created yet old_decryption_authorized_at = _fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at assert old_decryption_authorized_at public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid2, key_algo=key_cipher_algo) assert public_key_pem public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid3, key_algo=key_cipher_algo) assert public_key_pem frozen_datetime.tick(delta=timedelta(minutes=4)) result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_keypair_identifiers, request_message="I want decryption!", ) assert result["success_count"] == 2 assert result["too_old_count"] == 1 assert result["not_found_count"] == 2 assert (_fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at == old_decryption_authorized_at) # Unchanged assert _fetch_key_object_or_raise( keychain_uid=keychain_uid2, key_algo=key_cipher_algo).decryption_authorized_at assert _fetch_key_object_or_raise( keychain_uid=keychain_uid3, key_algo=key_cipher_algo).decryption_authorized_at with pytest.raises(KeyDoesNotExist, match="not found"): _fetch_key_object_or_raise(keychain_uid=keychain_uid_unexisting, key_algo=key_cipher_algo) public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid4, key_algo=key_cipher_algo) assert public_key_pem frozen_datetime.tick(delta=timedelta(minutes=6)) result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_keypair_identifiers, request_message="I want decryption!", ) assert result["success_count"] == 0 assert result["too_old_count"] == 4 assert result["not_found_count"] == 1 assert (_fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at == old_decryption_authorized_at) # Unchanged del all_keypair_identifiers
def test_jsonrpc_trustee_request_decryption_authorization_for_free_keys( live_server): jsonrpc_url = jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) keychain_uid_free = generate_uuid0() free_key_algo1 = "RSA_OAEP" free_key_algo2 = "ECC_DSS" free_key_algo3 = "DSA_DSS" all_requested_keypair_identifiers = [ dict(keychain_uid=keychain_uid_free, key_algo=free_key_algo1), dict(keychain_uid=keychain_uid_free, key_algo=free_key_algo2), ] sql_keystore = SqlKeystore() with freeze_time( ) as frozen_datetime: # TEST RELATION WITH FREE KEYS ATTACHMENT for i in range(3): # Generate 1 free keypair per type has_generated = generate_free_keypair_for_least_provisioned_key_algo( keystore=sql_keystore, max_free_keys_per_algo=1, key_algos=[free_key_algo1, free_key_algo2, free_key_algo3]) assert has_generated keys_generated_before_datetime = timezone.now() public_key_pem1 = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid_free, key_algo=free_key_algo1) assert public_key_pem1 # This key will not have early-enough request for authorization public_key_pem3 = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid_free, key_algo=free_key_algo3) assert public_key_pem3 result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_requested_keypair_identifiers, request_message="I want early decryption!", ) assert result["success_count"] == 1 assert result["too_old_count"] == 0 assert result[ "not_found_count"] == 1 # free_key_algo2 is not attached yet frozen_datetime.tick(delta=timedelta(minutes=6)) public_key_pem2 = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid_free, key_algo=free_key_algo2) assert public_key_pem2 result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_requested_keypair_identifiers, request_message="I want later decryption!", ) assert result[ "success_count"] == 1 # It's attachment time which counts! assert result["too_old_count"] == 1 # First key is too old now assert result["not_found_count"] == 0 keypair_obj = TrusteeKeypair.objects.get( keychain_uid=keychain_uid_free, key_algo=free_key_algo1) assert keypair_obj.created_at <= keys_generated_before_datetime assert keypair_obj.attached_at assert keypair_obj.decryption_authorized_at first_authorized_at = keypair_obj.decryption_authorized_at keypair_obj = TrusteeKeypair.objects.get( keychain_uid=keychain_uid_free, key_algo=free_key_algo2) assert keypair_obj.created_at <= keys_generated_before_datetime assert keypair_obj.attached_at assert keypair_obj.decryption_authorized_at assert keypair_obj.decryption_authorized_at >= first_authorized_at + timedelta( minutes=5) keypair_obj = TrusteeKeypair.objects.get( keychain_uid=keychain_uid_free, key_algo=free_key_algo3) assert keypair_obj.created_at <= keys_generated_before_datetime assert keypair_obj.attached_at assert not keypair_obj.decryption_authorized_at # Never requested
def test_jsonrpc_trustee_encrypt_decrypt_cryptainer(live_server): jsonrpc_url = jsonrpc_url = _get_trustee_jsonrpc_url(live_server) cryptoconf = dict(payload_cipher_layers=[ # First we encrypt with local key and sign via main remote trustee dict( payload_cipher_algo="AES_EAX", key_cipher_layers=[ dict(key_cipher_algo="RSA_OAEP", key_cipher_trustee=dict( trustee_type=CRYPTAINER_TRUSTEE_TYPES. JSONRPC_API_TRUSTEE, url=jsonrpc_url)) ], payload_signatures=[ dict( payload_digest_algo="SHA512", payload_signature_algo="DSA_DSS", payload_signature_trustee=dict( trustee_type=CRYPTAINER_TRUSTEE_TYPES. JSONRPC_API_TRUSTEE, url=jsonrpc_url), ) ], ) ]) # CASE 1: authorization request well sent a short time after creation of "keychain_uid" keypair, so decryption is accepted with freeze_time() as frozen_datetime: keychain_uid = generate_uuid0() payload = get_random_bytes(101) cryptainer = encrypt_payload_into_cryptainer( payload=payload, cryptoconf=cryptoconf, cryptainer_metadata=None, keychain_uid=keychain_uid, keystore_pool=None, # Unused by this config actually ) frozen_datetime.tick(delta=timedelta(minutes=3)) with pytest.raises(AuthorizationError): decrypt_payload_from_cryptainer(cryptainer=cryptainer, keystore_pool=None) # Access automatically granted for now, with this trustee, when keys are young trustee_dependencies = gather_trustee_dependencies( cryptainers=[cryptainer]) decryption_authorization_requests_result = request_decryption_authorizations( trustee_dependencies, keystore_pool=None, request_message="I need access to this") print(">>>>> request_decryption_authorizations is", decryption_authorization_requests_result) decrypted_data = decrypt_payload_from_cryptainer(cryptainer=cryptainer, keystore_pool=None) assert decrypted_data == payload frozen_datetime.tick( delta=timedelta(hours=23) ) # Once authorization is granted, it stays so for a long time decrypted_data = decrypt_payload_from_cryptainer(cryptainer=cryptainer, keystore_pool=None) assert decrypted_data == payload frozen_datetime.tick( delta=timedelta(hours=2) ) # Authorization has expired, and grace period to get one has long expired with pytest.raises( AuthorizationError, match="Decryption authorization is only valid from"): decrypt_payload_from_cryptainer(cryptainer=cryptainer, keystore_pool=None) # CASE 2: authorization request sent too late after creation of "keychain_uid" keypair, so decryption is rejected with freeze_time() as frozen_datetime: keychain_uid = generate_uuid0() data = get_random_bytes(101) local_keystore = DummyKeystore() cryptainer = encrypt_payload_into_cryptainer( payload=payload, cryptoconf=cryptoconf, cryptainer_metadata=None, keychain_uid=keychain_uid, keystore_pool=None, # Unused by this config actually ) frozen_datetime.tick( delta=timedelta(minutes=6)) # More than the 5 minutes grace period with pytest.raises(AuthorizationError, match="Decryption not authorized"): decrypt_payload_from_cryptainer(cryptainer=cryptainer, keystore_pool=None)
def test_escrow_api_workflow(): key_storage = DummyKeyStorage() escrow_api = EscrowApi(key_storage=key_storage) keychain_uid = generate_uuid0() keychain_uid_other = generate_uuid0() keychain_uid_unexisting = generate_uuid0() secret = get_random_bytes(127) secret_too_big = get_random_bytes(140) for _ in range(2): generate_free_keypair_for_least_provisioned_key_type( key_storage=key_storage, max_free_keys_per_type=10, key_types=["RSA_OAEP", "DSA_DSS"], ) assert key_storage.get_free_keypairs_count("DSA_DSS") == 1 assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 1 assert ( key_storage.get_free_keypairs_count("RSA_PSS") == 0 ) # Different from other RSA keys # Keypair is well auto-created by get_public_key() public_key_rsa_oaep_pem = escrow_api.get_public_key( keychain_uid=keychain_uid, key_type="RSA_OAEP" ) _public_key_rsa_oaep_pem2 = escrow_api.get_public_key( keychain_uid=keychain_uid, key_type="RSA_OAEP" ) assert _public_key_rsa_oaep_pem2 == public_key_rsa_oaep_pem # Same KEYS! _public_key_rsa_pss_pem = escrow_api.get_public_key( keychain_uid=keychain_uid, key_type="RSA_PSS" ) assert _public_key_rsa_pss_pem != public_key_rsa_oaep_pem # Different KEYS! public_key_rsa_oaep = load_asymmetric_key_from_pem_bytestring( key_pem=public_key_rsa_oaep_pem, key_type="RSA_OAEP" ) assert key_storage.get_free_keypairs_count("DSA_DSS") == 1 assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 0 # Taken assert key_storage.get_free_keypairs_count("RSA_PSS") == 0 signature = escrow_api.get_message_signature( keychain_uid=keychain_uid, message=secret, signature_algo="DSA_DSS" ) with pytest.raises(ValueError, match="too big"): escrow_api.get_message_signature( keychain_uid=keychain_uid, message=secret_too_big, signature_algo="DSA_DSS" ) assert key_storage.get_free_keypairs_count("DSA_DSS") == 0 # Taken assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 0 assert key_storage.get_free_keypairs_count("RSA_PSS") == 0 public_key_dsa_pem = escrow_api.get_public_key( keychain_uid=keychain_uid, key_type="DSA_DSS" ) public_key_dsa = load_asymmetric_key_from_pem_bytestring( key_pem=public_key_dsa_pem, key_type="DSA_DSS" ) verify_message_signature( message=secret, signature=signature, key=public_key_dsa, signature_algo="DSA_DSS", ) signature["digest"] += b"xyz" with pytest.raises(ValueError, match="not authentic"): verify_message_signature( message=secret, signature=signature, key=public_key_dsa, signature_algo="DSA_DSS", ) # Keypair is well auto-created by get_message_signature(), even when no more free keys signature = escrow_api.get_message_signature( keychain_uid=keychain_uid_other, message=secret, signature_algo="RSA_PSS" ) assert signature # Keypair well autocreated by get_public_key(), even when no more free keys public_key_pem = escrow_api.get_public_key( keychain_uid=keychain_uid_other, key_type="DSA_DSS" ) assert public_key_pem cipherdict = _encrypt_via_rsa_oaep(plaintext=secret, key=public_key_rsa_oaep) # Works even without decryption authorization request, by default: decrypted = escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid, encryption_algo="RSA_OAEP", cipherdict=cipherdict ) # NO auto-creation of keypair in decrypt_with_private_key() with pytest.raises(ValueError, match="Unexisting"): escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid_unexisting, encryption_algo="RSA_OAEP", cipherdict=cipherdict, ) cipherdict["digest_list"].append(b"aaabbbccc") with pytest.raises(ValueError, match="Ciphertext with incorrect length"): escrow_api.decrypt_with_private_key( keychain_uid=keychain_uid, encryption_algo="RSA_OAEP", cipherdict=cipherdict ) assert decrypted == secret result = escrow_api.request_decryption_authorization( keypair_identifiers=[(keychain_uid, "RSA_OAEP")], request_message="I need this decryption!", ) assert result["response_message"] with pytest.raises(ValueError, match="empty"): escrow_api.request_decryption_authorization( keypair_identifiers=[], request_message="I need this decryption!" ) assert key_storage.get_free_keypairs_count("DSA_DSS") == 0 assert key_storage.get_free_keypairs_count("ECC_DSS") == 0 assert key_storage.get_free_keypairs_count("RSA_OAEP") == 0 assert key_storage.get_free_keypairs_count("RSA_PSS") == 0
def test_jsonrpc_trustee_decryption_authorization_flags(live_server): jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) keychain_uid = generate_uuid0() keychain_uid_bad = generate_uuid0() key_cipher_algo = "RSA_OAEP" secret = get_random_bytes(101) public_encryption_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid, key_algo=key_cipher_algo) public_encryption_key = load_asymmetric_key_from_pem_bytestring( key_pem=public_encryption_key_pem, key_algo=key_cipher_algo) cipherdict = _encrypt_via_rsa_oaep( plaintext=secret, key_dict=dict(key=public_encryption_key)) def _attempt_decryption(): return trustee_proxy.decrypt_with_private_key( keychain_uid=keychain_uid, cipher_algo=key_cipher_algo, cipherdict=cipherdict, ) with freeze_time() as frozen_datetime: with pytest.raises(AuthorizationError, match="Decryption not authorized"): _attempt_decryption() keypair_obj = TrusteeKeypair.objects.get(keychain_uid=keychain_uid, key_algo=key_cipher_algo) keypair_obj.decryption_authorized_at = timezone.now() + timedelta( hours=2) keypair_obj.save() with pytest.raises( AuthorizationError, match="Decryption authorization is only valid from"): _attempt_decryption() # Too early frozen_datetime.tick(delta=timedelta(hours=3)) decrypted = _attempt_decryption() assert decrypted == secret # It works! with pytest.raises(KeyDoesNotExist, match="not found"): trustee_proxy.decrypt_with_private_key( keychain_uid=keychain_uid_bad, cipher_algo=key_cipher_algo, cipherdict=cipherdict, ) cipherdict["digest_list"].append(b"aaabbbccc") with pytest.raises(DecryptionError, match="Ciphertext with incorrect length"): trustee_proxy.decrypt_with_private_key( keychain_uid=keychain_uid, cipher_algo=key_cipher_algo, cipherdict=cipherdict, ) frozen_datetime.tick(delta=timedelta( hours=24)) # We hardcode DECRYPTION_AUTHORIZATION_LIFESPAN_H here with pytest.raises( AuthorizationError, match="Decryption authorization is only valid from"): _attempt_decryption( ) # Too late, cipherdict is not even used so no ValueError keypair_obj.decryption_authorized_at = None keypair_obj.save() with pytest.raises(AuthorizationError, match="Decryption not authorized"): _attempt_decryption() # No more authorization at all
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