Example #1
0
def test_identity_keyring_as_generator_and_no_data_encryption_key(
        identity_keyring):
    test_multi_keyring = MultiKeyring(generator=identity_keyring)
    with pytest.raises(GenerateKeyError) as exc_info:
        test_multi_keyring.on_encrypt(
            encryption_materials=get_encryption_materials_without_data_key())
    assert exc_info.match("Unable to generate data encryption key.")
Example #2
0
def test_on_encrypt_edk_length_when_keyring_generates_but_does_not_encrypt_encryption_materials_with_data_key(
):
    test_multi_keyring = MultiKeyring(generator=OnlyGenerateKeyring())
    test = test_multi_keyring.on_encrypt(
        encryption_materials=get_encryption_materials_with_encrypted_data_key(
        ))
    assert len(test.encrypted_data_keys) == len(
        get_encryption_materials_with_encrypted_data_key().encrypted_data_keys)
Example #3
0
def test_on_encrypt_when_data_encryption_key_given(mock_generator,
                                                   mock_child_1, mock_child_2):
    test_multi_keyring = MultiKeyring(generator=mock_generator,
                                      children=[mock_child_1, mock_child_2])
    initial_materials = get_encryption_materials_with_data_key()
    new_materials = test_multi_keyring.on_encrypt(
        encryption_materials=initial_materials)

    assert new_materials is not initial_materials

    for keyring in test_multi_keyring._decryption_keyrings:
        keyring.on_encrypt.assert_called_once()
class AwsKmsKeyring(Keyring):
    """Keyring that uses AWS Key Management Service (KMS) Customer Master Keys (CMKs) to manage wrapping keys.

    Set ``generator_key_id`` to require that the keyring use that CMK to generate the data key.
    If you do not set ``generator_key_id``, the keyring will not generate a data key.

    Set ``key_ids`` to specify additional CMKs that the keyring will use to encrypt the data key.

    The keyring will attempt to use any CMKs
    identified by CMK ARN in either ``generator_key_id`` or ``key_ids`` on decrypt.

    You can identify CMKs by any `valid key ID`_ for the keyring to use on encrypt,
    but for the keyring to attempt to use them on decrypt
    you MUST specify the CMK ARN.

    If you specify ``is_discovery=True`` the keyring will be a KMS discovery keyring,
    doing nothing on encrypt and attempting to decrypt any AWS KMS-encrypted data key on decrypt.

    .. note::

        You must either set ``is_discovery=True`` or provide key IDs.

    You can use the :class:`ClientSupplier` to customize behavior further,
    such as to provide different credentials for different regions
    or to restrict which regions are allowed.

    See the `AWS KMS Keyring specification`_ for more details.

    .. _AWS KMS Keyring specification:
       https://github.com/awslabs/aws-encryption-sdk-specification/blob/master/framework/kms-keyring.md
    .. _valid key ID:
       https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKey.html#API_GenerateDataKey_RequestSyntax
    .. _discovery mode:
       https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#kms-keyring-discovery

    .. versionadded:: 1.5.0

    :param ClientSupplier client_supplier: Client supplier that provides AWS KMS clients (optional)
    :param bool is_discovery: Should this be a discovery keyring (optional)
    :param str generator_key_id: Key ID of AWS KMS CMK to use when generating data keys (optional)
    :param List[str] key_ids: Key IDs that will be used to encrypt and decrypt data keys (optional)
    :param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional)
    """

    _client_supplier = attr.ib(default=attr.Factory(DefaultClientSupplier), validator=is_callable())
    _is_discovery = attr.ib(default=False, validator=instance_of(bool))
    _generator_key_id = attr.ib(default=None, validator=optional(instance_of(six.string_types)))
    _key_ids = attr.ib(
        default=attr.Factory(tuple),
        validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string),
    )
    _grant_tokens = attr.ib(
        default=attr.Factory(tuple),
        validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string),
    )

    def __attrs_post_init__(self):
        """Configure internal keyring."""
        key_ids_provided = self._generator_key_id is not None or self._key_ids
        both = key_ids_provided and self._is_discovery
        neither = not key_ids_provided and not self._is_discovery

        if both:
            raise TypeError("is_discovery cannot be True if key IDs are provided")

        if neither:
            raise TypeError("is_discovery cannot be False if no key IDs are provided")

        if self._is_discovery:
            self._inner_keyring = _AwsKmsDiscoveryKeyring(
                client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
            )
            return

        if self._generator_key_id is None:
            generator_keyring = None
        else:
            generator_keyring = _AwsKmsSingleCmkKeyring(
                key_id=self._generator_key_id, client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
            )

        child_keyrings = [
            _AwsKmsSingleCmkKeyring(
                key_id=key_id, client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
            )
            for key_id in self._key_ids
        ]

        self._inner_keyring = MultiKeyring(generator=generator_keyring, children=child_keyrings)

    def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        """Generate a data key using generator keyring
        and encrypt it using any available wrapping key in any child keyring.

        :param EncryptionMaterials encryption_materials: Encryption materials for keyring to modify.
        :returns: Optionally modified encryption materials.
        :rtype: EncryptionMaterials
        :raises EncryptKeyError: if unable to encrypt data key.
        """
        return self._inner_keyring.on_encrypt(encryption_materials=encryption_materials)

    def on_decrypt(self, decryption_materials, encrypted_data_keys):
        # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
        """Attempt to decrypt the encrypted data keys.

        :param DecryptionMaterials decryption_materials: Decryption materials for keyring to modify.
        :param List[EncryptedDataKey] encrypted_data_keys: List of encrypted data keys.
        :returns: Optionally modified decryption materials.
        :rtype: DecryptionMaterials
        """
        return self._inner_keyring.on_decrypt(
            decryption_materials=decryption_materials, encrypted_data_keys=encrypted_data_keys
        )