class RequireApprovedAlgorithmSuitesCryptoMaterialsManager( CryptoMaterialsManager): """Only allow encryption requests for approved algorithm suites.""" def __init__(self, keyring): # type: (Keyring) -> None """Set up the inner cryptographic materials manager using the provided keyring. :param Keyring keyring: Keyring to use in the inner cryptographic materials manager """ self._allowed_algorithm_suites = { None, # no algorithm suite in the request AlgorithmSuite. AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, # the default algorithm suite AlgorithmSuite. AES_256_GCM_IV12_TAG16_HKDF_SHA256, # the recommended unsigned algorithm suite } # Wrap the provided keyring in the default cryptographic materials manager (CMM). # # This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, # do if you provide a keyring instead of a CMM. self._cmm = DefaultCryptoMaterialsManager(keyring=keyring) def get_encryption_materials(self, request): # type: (EncryptionMaterialsRequest) -> EncryptionMaterials """Block any requests that include an unapproved algorithm suite.""" if request.algorithm not in self._allowed_algorithm_suites: raise UnapprovedAlgorithmSuite( "Unapproved algorithm suite requested!") return self._cmm.get_encryption_materials(request) def decrypt_materials(self, request): # type: (DecryptionMaterialsRequest) -> DecryptionMaterials """Be more permissive on decrypt and just pass through.""" return self._cmm.decrypt_materials(request)
class ClassificationRequiringCryptoMaterialsManager(CryptoMaterialsManager): """Only allow requests when the encryption context contains a classification identifier.""" def __init__(self, keyring): # type: (Keyring) -> None """Set up the inner cryptographic materials manager using the provided keyring. :param Keyring keyring: Keyring to use in the inner cryptographic materials manager """ self._classification_field = "classification" self._classification_error = MissingClassificationError("Encryption context does not contain classification!") # Wrap the provided keyring in the default cryptographic materials manager (CMM). # # This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, # do if you provide a keyring instead of a CMM. self._cmm = DefaultCryptoMaterialsManager(keyring=keyring) def get_encryption_materials(self, request): # type: (EncryptionMaterialsRequest) -> EncryptionMaterials """Block any requests that do not contain a classification identifier in the encryption context.""" if self._classification_field not in request.encryption_context: raise self._classification_error return self._cmm.get_encryption_materials(request) def decrypt_materials(self, request): # type: (DecryptionMaterialsRequest) -> DecryptionMaterials """Block any requests that do not contain a classification identifier in the encryption context.""" if self._classification_field not in request.encryption_context: raise self._classification_error return self._cmm.decrypt_materials(request)
def __init__(self, keyring): # type: (Keyring) -> None """Set up the inner cryptographic materials manager using the provided keyring. :param Keyring keyring: Keyring to use in the inner cryptographic materials manager """ self._classification_field = "classification" self._classification_error = MissingClassificationError("Encryption context does not contain classification!") # Wrap the provided keyring in the default cryptographic materials manager (CMM). # # This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, # do if you provide a keyring instead of a CMM. self._cmm = DefaultCryptoMaterialsManager(keyring=keyring)
def test_get_encryption_materials_uncommitting_algorithm_policy_forbid( patch_for_dcmm_encrypt): """Tests that a Default Crypto Materials Manager request with policy FORBID_ENCRYPT_ALLOW_DECRYPT can successfully encrypt using an algorithm that does not provide commitment.""" mock_alg = MagicMock(__class__=Algorithm) mock_alg.is_committing.return_value = False mock_request = MagicMock(algorithm=mock_alg, encryption_context={}) mock_request.commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT cmm = DefaultCryptoMaterialsManager(master_key_provider=build_mkp()) test = cmm.get_encryption_materials(request=mock_request) assert test.algorithm is mock_request.algorithm
def test_get_encryption_materials_committing_algorithm_require_encrypt_require_decrypt( patch_for_dcmm_encrypt): """Tests that a Default Crypto Materials Manager request with policy REQUIRE_ENCRYPT_REQUIRE_DECRYPT can successfully encrypt using an algorithm that provides commitment.""" mock_alg = MagicMock(__class__=Algorithm) mock_alg.is_committing.return_value = True mock_request = MagicMock(algorithm=mock_alg, encryption_context={}) mock_request.commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT cmm = DefaultCryptoMaterialsManager(master_key_provider=build_mkp()) test = cmm.get_encryption_materials(request=mock_request) assert test.algorithm is mock_request.algorithm
def __attrs_post_init__(self): """Normalize inputs to crypto material manager.""" options_provided = [option is not None for option in (self.materials_manager, self.keyring, self.key_provider)] provided_count = len([is_set for is_set in options_provided if is_set]) if provided_count != 1: raise TypeError("Exactly one of 'materials_manager', 'keyring', or 'key_provider' must be provided") if self.materials_manager is None: if self.key_provider is not None: self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) else: self.materials_manager = DefaultCryptoMaterialsManager(keyring=self.keyring)
def test_encrypt_with_keyring_materials_incomplete(algorithm_suite): raw_aes256_keyring = ephemeral_raw_aes_keyring( WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING) encrypt_cmm = DefaultCryptoMaterialsManager( keyring=NoEncryptedDataKeysKeyring(inner_keyring=raw_aes256_keyring)) encryption_materials_request = EncryptionMaterialsRequest( encryption_context={}, frame_length=1024, algorithm=algorithm_suite) with pytest.raises(InvalidCryptographicMaterialsError) as excinfo: encrypt_cmm.get_encryption_materials(encryption_materials_request) excinfo.match("Encryption materials are incomplete!")
def test_decrypt_materials_uncommitting_alg_require_policy( patch_for_dcmm_decrypt): """Tests that a configuration which requires commitment does not allow decryption of un-committed messages.""" mock_alg = MagicMock(__class__=Algorithm) mock_alg.is_committing.return_value = False mock_request = MagicMock(algorithm=mock_alg, encryption_context={}) mock_request.commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT cmm = DefaultCryptoMaterialsManager(master_key_provider=build_mkp()) with pytest.raises(ActionNotAllowedError) as excinfo: cmm.decrypt_materials(request=mock_request) excinfo.match( "Configuration conflict. Cannot decrypt due to .* requiring only committed messages." )
def test_encrypt_with_keyring_materials_do_not_match_request( kwargs, algorithm_suite): raw_aes256_keyring = ephemeral_raw_aes_keyring( WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING) encrypt_cmm = DefaultCryptoMaterialsManager( keyring=BrokenKeyring(inner_keyring=raw_aes256_keyring, **kwargs)) encryption_materials_request = EncryptionMaterialsRequest( encryption_context={}, frame_length=1024, algorithm=algorithm_suite) with pytest.raises(InvalidCryptographicMaterialsError) as excinfo: encrypt_cmm.get_encryption_materials(encryption_materials_request) excinfo.match("Encryption materials do not match request!")
def test_get_encryption_materials_uncommitting_algorithm_require_encrypt_allow_decrypt( patch_for_dcmm_encrypt): """Tests that a Default Crypto Materials Manager request with policy REQUIRE_ENCRYPT_ALLOW_DECRYPT cannot encrypt using an algorithm that does not provide commitment.""" mock_alg = MagicMock(__class__=Algorithm) mock_alg.is_committing.return_value = False mock_request = MagicMock(algorithm=mock_alg, encryption_context={}) mock_request.commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT cmm = DefaultCryptoMaterialsManager(master_key_provider=build_mkp()) with pytest.raises(ActionNotAllowedError) as excinfo: cmm.get_encryption_materials(request=mock_request) excinfo.match( "Configuration conflict. Cannot encrypt due to .* requiring only committed messages" )
def __init__(self, keyring): # type: (Keyring) -> None """Set up the inner cryptographic materials manager using the provided keyring. :param Keyring keyring: Keyring to use in the inner cryptographic materials manager """ self._allowed_algorithm_suites = { None, # no algorithm suite in the request AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, # the default algorithm suite AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256, # the recommended unsigned algorithm suite } # Wrap the provided keyring in the default cryptographic materials manager (CMM). # # This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, # do if you provide a keyring instead of a CMM. self._cmm = DefaultCryptoMaterialsManager(keyring=keyring)
def build_cmm(): mock_mkp = MagicMock(__class__=MasterKeyProvider) mock_mkp.decrypt_data_key_from_list.return_value = _DATA_KEY mock_mkp.master_keys_for_encryption.return_value = ( sentinel.primary_mk, {sentinel.primary_mk, sentinel.mk_a, sentinel.mk_b}, ) return DefaultCryptoMaterialsManager(master_key_provider=mock_mkp)
def __attrs_post_init__(self): """Normalize inputs to crypto material manager.""" both_cmm_and_mkp_defined = self.materials_manager is not None and self.key_provider is not None neither_cmm_nor_mkp_defined = self.materials_manager is None and self.key_provider is None if both_cmm_and_mkp_defined or neither_cmm_nor_mkp_defined: raise TypeError("Exactly one of materials_manager or key_provider must be provided") if self.materials_manager is None: self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider)
class HalfSigningCryptoMaterialsManager(CryptoMaterialsManager): """ Custom CMM that generates materials for an unsigned algorithm suite that includes the "aws-crypto-public-key" encryption context. THIS IS ONLY USED TO CREATE INVALID MESSAGES and should never be used in production! It is imitating what a malicious decryptor without encryption permissions might do, to attempt to forge an unsigned message from a decrypted signed message, and therefore this is an important case for ESDKs to reject. """ wrapped_default_cmm = attr.ib(validator=attr.validators.instance_of(CryptoMaterialsManager)) def __init__(self, master_key_provider): """ Create a new CMM that wraps a new DefaultCryptoMaterialsManager based on the given master key provider. """ self.wrapped_default_cmm = DefaultCryptoMaterialsManager(master_key_provider) def get_encryption_materials(self, request): """ Generate half-signing materials by requesting signing materials from the wrapped default CMM, and then changing the algorithm suite and removing the signing key from teh result. """ if request.algorithm == AlgorithmSuite.AES_256_GCM_HKDF_SHA512_COMMIT_KEY: signing_request = copy(request) signing_request.algorithm = AlgorithmSuite.AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384 result = self.wrapped_default_cmm.get_encryption_materials(signing_request) result.algorithm = request.algorithm result.signing_key = None return result raise NotImplementedError( "The half-sign tampering method is only supported on the " "AES_256_GCM_HKDF_SHA512_COMMIT_KEY algorithm suite." ) def decrypt_materials(self, request): """Thunks to the wrapped default CMM""" return self.wrapped_default_cmm.decrypt_materials(request)
def test_decrypt_materials_committing_alg(patch_for_dcmm_decrypt, policy): """Tests that all configurations of CommitmentPolicy are able to decrypt when the algorithm provides commitment.""" mock_alg = MagicMock(__class__=Algorithm) mock_alg.is_committing.return_value = True mock_request = MagicMock(algorithm=mock_alg, encryption_context={}) mock_request.commitment_policy = policy cmm = DefaultCryptoMaterialsManager(master_key_provider=build_mkp()) test = cmm.decrypt_materials(request=mock_request) cmm.master_key_provider.decrypt_data_key_from_list.assert_called_once_with( encrypted_data_keys=mock_request.encrypted_data_keys, algorithm=mock_request.algorithm, encryption_context=mock_request.encryption_context, ) cmm._load_verification_key_from_encryption_context.assert_called_once_with( algorithm=mock_request.algorithm, encryption_context=mock_request.encryption_context) assert test.data_key is cmm.master_key_provider.decrypt_data_key_from_list.return_value assert test.verification_key == patch_for_dcmm_decrypt
def run_scenario_with_tampering(self, ciphertext_writer, generation_scenario, plaintext_uri): """ Run a given scenario, tampering with the input or the result. return: a list of (ciphertext, result) pairs """ materials_manager = DefaultCryptoMaterialsManager( generation_scenario.encryption_scenario.master_key_provider_fn() ) ciphertext_to_decrypt = generation_scenario.encryption_scenario.run(materials_manager) if generation_scenario.result: expected_result = generation_scenario.result else: expected_result = MessageDecryptionTestResult.expect_output( plaintext_uri=plaintext_uri, plaintext=generation_scenario.encryption_scenario.plaintext ) return [ generation_scenario.decryption_test_scenario_pair(ciphertext_writer, ciphertext_to_decrypt, expected_result) ]
def test_decrypt_with_keyring_materials_incomplete(algorithm_suite): raw_aes256_keyring = ephemeral_raw_aes_keyring( WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING) raw_aes128_keyring = ephemeral_raw_aes_keyring( WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING) encrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes256_keyring) decrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes128_keyring) encryption_materials_request = EncryptionMaterialsRequest( encryption_context={}, frame_length=1024, algorithm=algorithm_suite) encryption_materials = encrypt_cmm.get_encryption_materials( encryption_materials_request) decryption_materials_request = DecryptionMaterialsRequest( algorithm=encryption_materials.algorithm, encrypted_data_keys=encryption_materials.encrypted_data_keys, encryption_context=encryption_materials.encryption_context, ) with pytest.raises(InvalidCryptographicMaterialsError) as excinfo: decrypt_cmm.decrypt_materials(decryption_materials_request) excinfo.match("Decryption materials are incomplete!")
def test_attributes_default(): cmm = DefaultCryptoMaterialsManager(master_key_provider=MagicMock( __class__=MasterKeyProvider)) assert cmm.algorithm is ALGORITHM
def test_attributes_fail(kwargs): with pytest.raises(TypeError): DefaultCryptoMaterialsManager(**kwargs)
def build_cmm(): mock_mkp = build_mkp() return DefaultCryptoMaterialsManager(master_key_provider=mock_mkp)
def test_attributes_fail(): with pytest.raises(TypeError): DefaultCryptoMaterialsManager(master_key_provider=None)
def __init__(self, master_key_provider): """ Create a new CMM that wraps a new DefaultCryptoMaterialsManager based on the given master key provider. """ self.wrapped_default_cmm = DefaultCryptoMaterialsManager(master_key_provider)