예제 #1
0
def test_load_asymmetric_key_from_pem_bytestring():

    for key_type in SUPPORTED_ASYMMETRIC_KEY_TYPES:

        keypair = wacryptolib.key_generation.generate_asymmetric_keypair(
            key_type=key_type)

        for field in ["private_key", "public_key"]:
            key = load_asymmetric_key_from_pem_bytestring(
                key_pem=keypair[field], key_type=key_type)
            assert key.export_key  # Method of Key object

        with pytest.raises(ValueError, match="Unknown key type"):
            load_asymmetric_key_from_pem_bytestring(
                key_pem=keypair["private_key"], key_type="ZHD")
def test_escrow_api_workflow():

    key_storage = DummyKeyStorage()
    escrow_api = EscrowApi(key_storage=key_storage)

    keychain_uid = uuid.uuid4()
    secret = get_random_bytes(101)

    public_key_pem = escrow_api.get_public_key(keychain_uid=keychain_uid,
                                               key_type="RSA")
    public_key = load_asymmetric_key_from_pem_bytestring(
        key_pem=public_key_pem, key_type="RSA")

    signature = escrow_api.get_message_signature(keychain_uid=keychain_uid,
                                                 message=secret,
                                                 key_type="RSA",
                                                 signature_algo="PSS")
    verify_message_signature(message=secret,
                             signature=signature,
                             key=public_key,
                             signature_algo="PSS")

    signature["digest"] += b"xyz"
    with pytest.raises(ValueError, match="Incorrect signature"):
        verify_message_signature(message=secret,
                                 signature=signature,
                                 key=public_key,
                                 signature_algo="PSS")

    cipherdict = _encrypt_via_rsa_oaep(plaintext=secret, key=public_key)

    decrypted = escrow_api.decrypt_with_private_key(
        keychain_uid=keychain_uid,
        key_type="RSA",
        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,
            key_type="RSA",
            encryption_algo="RSA_OAEP",
            cipherdict=cipherdict,
        )

    assert decrypted == secret
    def _verify_message_signatures(self, keychain_uid: uuid.UUID,
                                   message: bytes, conf: dict):
        signature_key_type = conf["signature_key_type"]
        signature_algo = conf["signature_algo"]
        encryption_proxy = _get_proxy_for_escrow(conf["signature_escrow"])
        public_key_pem = encryption_proxy.get_public_key(
            keychain_uid=keychain_uid, key_type=signature_key_type)
        public_key = load_asymmetric_key_from_pem_bytestring(
            key_pem=public_key_pem, key_type=signature_key_type)

        verify_message_signature(
            message=message,
            signature_algo=signature_algo,
            signature=conf["signature_value"],
            key=public_key,
        )  # Raises if troubles
예제 #4
0
 def _decrypt_private_key_pem_with_passphrases(
     self, *, private_key_pem: bytes, key_type: str, passphrases: Optional[list]
 ):
     """
     Attempt decryption of key with and without provided passphrases, and raise if all fail.
     """
     for passphrase in [None] + passphrases:
         try:
             key_obj = load_asymmetric_key_from_pem_bytestring(
                 key_pem=private_key_pem, key_type=key_type, passphrase=passphrase
             )
             return key_obj
         except KeyLoadingError:
             pass
     raise DecryptionError(
         "Could not decrypt private key of type %s (passphrases provided: %d)" % (key_type, len(passphrases))
     )
    def _encrypt_symmetric_key(self, keychain_uid: uuid.UUID,
                               symmetric_key_data: bytes, conf: dict) -> dict:
        assert isinstance(symmetric_key_data, bytes), symmetric_key_data
        escrow_key_type = conf["escrow_key_type"]
        key_encryption_algo = conf["key_encryption_algo"]
        encryption_proxy = _get_proxy_for_escrow(conf["key_escrow"])

        subkey_pem = encryption_proxy.get_public_key(keychain_uid=keychain_uid,
                                                     key_type=escrow_key_type)
        subkey = load_asymmetric_key_from_pem_bytestring(
            key_pem=subkey_pem, key_type=escrow_key_type)

        key_cipherdict = encrypt_bytestring(
            plaintext=symmetric_key_data,
            encryption_algo=key_encryption_algo,
            key=subkey,
        )
        return key_cipherdict
예제 #6
0
    def get_message_signature(
        self, *, keychain_uid: uuid.UUID, message: bytes, signature_algo: str  # FIXME name this "key_type" too?
    ) -> dict:
        """
        Return a signature structure corresponding to the provided key and signature types.
        """

        if len(message) > MAX_PAYLOAD_LENGTH_FOR_SIGNATURE:  # SECURITY
            raise ValueError("Message too big for signing, only a hash should be sent")

        self._ensure_keypair_exists(keychain_uid=keychain_uid, key_type=signature_algo)

        private_key_pem = self._key_storage.get_private_key(keychain_uid=keychain_uid, key_type=signature_algo)

        private_key = load_asymmetric_key_from_pem_bytestring(key_pem=private_key_pem, key_type=signature_algo)

        signature = sign_message(message=message, signature_algo=signature_algo, key=private_key)
        return signature
    def get_message_signature(
        self,
        *,
        keychain_uid: uuid.UUID,
        message: bytes,
        key_type: str,
        signature_algo: str,
    ) -> dict:
        """
        Return a signature structure corresponding to the provided key and signature types.
        """
        keypair_pem = self._fetch_keypair_with_caching(
            keychain_uid=keychain_uid, key_type=key_type)
        private_key = load_asymmetric_key_from_pem_bytestring(
            key_pem=keypair_pem["private_key"], key_type=key_type)

        signature = sign_message(message=message,
                                 signature_algo=signature_algo,
                                 key=private_key)
        return signature
예제 #8
0
    def decrypt_with_private_key(self, *, keychain_uid: uuid.UUID,
                                 encryption_algo: str,
                                 cipherdict: dict) -> bytes:
        """
        Return the message (probably a symmetric key) decrypted with the corresponding key,
        as bytestring.
        """
        assert (encryption_algo.upper() == "RSA_OAEP"
                )  # Only supported asymmetric cipher for now
        self._check_keypair_exists(keychain_uid=keychain_uid,
                                   key_type=encryption_algo)

        private_key_pem = self._key_storage.get_private_key(
            keychain_uid=keychain_uid, key_type=encryption_algo)

        private_key = load_asymmetric_key_from_pem_bytestring(
            key_pem=private_key_pem, key_type=encryption_algo)

        secret = _decrypt_via_rsa_oaep(cipherdict=cipherdict, key=private_key)
        return secret
    def decrypt_with_private_key(
        self,
        *,
        keychain_uid: uuid.UUID,
        key_type: str,
        encryption_algo: str,
        cipherdict: dict,
    ) -> bytes:
        """
        Return the message (probably a symmetric key) decrypted with the corresponding key,
        as bytestring.
        """
        assert key_type.upper() == "RSA"  # Only supported key for now
        assert (encryption_algo.upper() == "RSA_OAEP"
                )  # Only supported asymmetric cipher for now

        keypair_pem = self._fetch_keypair_with_caching(
            keychain_uid=keychain_uid, key_type=key_type)
        private_key = load_asymmetric_key_from_pem_bytestring(
            key_pem=keypair_pem["private_key"], key_type=key_type)

        secret = _decrypt_via_rsa_oaep(cipherdict=cipherdict, key=private_key)
        return secret
예제 #10
0
    def _encrypt_symmetric_key(self, keychain_uid: uuid.UUID,
                               symmetric_key_data: bytes, conf: dict) -> dict:
        assert isinstance(symmetric_key_data, bytes), symmetric_key_data
        key_encryption_algo = conf["key_encryption_algo"]
        encryption_proxy = self._get_proxy_for_escrow(conf["key_escrow"])

        logger.debug("Generating asymmetric key of type %r",
                     key_encryption_algo)
        subkey_pem = encryption_proxy.get_public_key(
            keychain_uid=keychain_uid, key_type=key_encryption_algo)

        logger.debug(
            "Encrypting symmetric key with asymmetric key of type %r",
            key_encryption_algo,
        )
        subkey = load_asymmetric_key_from_pem_bytestring(
            key_pem=subkey_pem, key_type=key_encryption_algo)

        key_cipherdict = encrypt_bytestring(
            plaintext=symmetric_key_data,
            encryption_algo=key_encryption_algo,
            key=subkey,
        )
        return key_cipherdict
예제 #11
0
def test_generate_and_load_passphrase_protected_asymmetric_key():

    # Both Unicode and Bytes are supported
    passphrases = ["Thïs is a passphrâse", b"aoh18726"]

    for passphrase in passphrases:

        for key_type in SUPPORTED_ASYMMETRIC_KEY_TYPES:

            keypair = wacryptolib.key_generation.generate_asymmetric_keypair(
                key_type=key_type, passphrase=passphrase)

            public_key = load_asymmetric_key_from_pem_bytestring(
                key_pem=keypair["public_key"],
                key_type=key_type  # NOT encrypted
            )
            assert public_key.export_key

            if isinstance(passphrase,
                          str):  # Different unicode représentations work fine
                passphrase = unicodedata.normalize("NFD", passphrase)

            private_key = load_asymmetric_key_from_pem_bytestring(
                key_pem=keypair["private_key"],
                key_type=key_type,
                passphrase=passphrase  # Encrypted
            )
            assert private_key.export_key

            error_matcher = "key format is not supported|Invalid DER encoding"

            with pytest.raises(KeyLoadingError, match=error_matcher):
                load_asymmetric_key_from_pem_bytestring(
                    key_pem=keypair["private_key"],
                    key_type=key_type,
                    passphrase=b"wrong passphrase")

            with pytest.raises(KeyLoadingError, match=error_matcher):
                load_asymmetric_key_from_pem_bytestring(
                    key_pem=keypair["private_key"],
                    key_type=key_type,
                    passphrase=None  # Missing passphrase
                )
예제 #12
0
def test_generate_and_load_passphrase_protected_asymmetric_key():

    passphrase = b"this is a passphrase"

    for key_type in SUPPORTED_ASYMMETRIC_KEY_TYPES:

        keypair = wacryptolib.key_generation.generate_asymmetric_keypair(
            key_type=key_type, passphrase=passphrase)

        public_key = load_asymmetric_key_from_pem_bytestring(
            key_pem=keypair["public_key"],
            key_type=key_type  # NOT encrypted
        )
        assert public_key.export_key

        private_key = load_asymmetric_key_from_pem_bytestring(
            key_pem=keypair["private_key"],
            key_type=key_type,
            passphrase=passphrase,  # Encrypted
        )
        assert private_key.export_key

        error_matcher = "key format is not supported|Invalid DER encoding"

        with pytest.raises(ValueError, match=error_matcher):
            load_asymmetric_key_from_pem_bytestring(
                key_pem=keypair["private_key"],
                key_type=key_type,
                passphrase=b"wrong passphrase",
            )

        with pytest.raises(ValueError, match=error_matcher):
            load_asymmetric_key_from_pem_bytestring(
                key_pem=keypair["private_key"],
                key_type=key_type,
                passphrase=None,  # Missing passphrase
            )
예제 #13
0
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
예제 #14
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
예제 #15
0
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_waescrow_escrow_api_workflow(db):

    escrow_proxy = TestingServiceProxy(client=Client(),
                                       service_url="/json/",
                                       version="2.0")

    keychain_uid = uuid.uuid4()
    key_type = "RSA"
    secret = get_random_bytes(101)

    public_key_pem = _get_jsonrpc_result(
        escrow_proxy.get_public_key(keychain_uid=keychain_uid, key_type="RSA"))
    public_key = load_asymmetric_key_from_pem_bytestring(
        key_pem=public_key_pem, key_type=key_type)

    signature = _get_jsonrpc_result(
        escrow_proxy.get_message_signature(keychain_uid=keychain_uid,
                                           message=secret,
                                           key_type=key_type,
                                           signature_algo="PSS"))
    verify_message_signature(message=secret,
                             signature=signature,
                             key=public_key,
                             signature_algo="PSS")

    signature["digest"] += b"xyz"
    with pytest.raises(ValueError, match="Incorrect signature"):
        verify_message_signature(message=secret,
                                 signature=signature,
                                 key=public_key,
                                 signature_algo="PSS")

    cipherdict = _encrypt_via_rsa_oaep(plaintext=secret, key=public_key)

    def _attempt_decryption():
        return escrow_proxy.decrypt_with_private_key(
            keychain_uid=keychain_uid,
            key_type=key_type,
            encryption_algo="RSA_OAEP",
            cipherdict=cipherdict)

    with freeze_time() as frozen_datetime:

        with pytest.raises(RuntimeError, match="Decryption not authorized"):
            _attempt_decryption()

        keypair_obj = EscrowKeypair.objects.get(keychain_uid=keychain_uid,
                                                key_type=key_type)
        keypair_obj.decryption_authorized_at = timezone.now() + timedelta(
            hours=2)
        keypair_obj.save()

        with pytest.raises(
                RuntimeError,
                match="Decryption authorization is not currently active"):
            _attempt_decryption()  # Too early

        frozen_datetime.tick(delta=timedelta(hours=3))

        decrypted = _get_jsonrpc_result(_attempt_decryption())
        assert decrypted == secret  # It works!

        cipherdict["digest_list"].append(b"aaabbbccc")
        with pytest.raises(ValueError,
                           match="Ciphertext with incorrect length"):
            # Django test client reraises signalled exception
            escrow_proxy.decrypt_with_private_key(keychain_uid=keychain_uid,
                                                  key_type=key_type,
                                                  encryption_algo="RSA_OAEP",
                                                  cipherdict=cipherdict)

        frozen_datetime.tick(delta=timedelta(
            hours=24))  # We hardcode DECRYPTION_AUTHORIZATION_LIFESPAN_H here

        with pytest.raises(
                RuntimeError,
                match="Decryption authorization is not currently active"):
            _attempt_decryption(
            )  # Too late, cipherdict is not even used so no ValueError

        keypair_obj.decryption_authorized_at = None
        keypair_obj.save()

        with pytest.raises(RuntimeError, match="Decryption not authorized"):
            _attempt_decryption()  # No more authorization at all