def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context=None): """Decrypts an encrypted data key and returns the plaintext. :param data_key: Encrypted data key :type data_key: aws_encryption_sdk.structures.EncryptedDataKey :type algorithm: `aws_encryption_sdk.identifiers.Algorithm` (not used for KMS) :param dict encryption_context: Encryption context to use in decryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if Master Key is unable to decrypt data key """ edk_key_id = to_str(encrypted_data_key.key_provider.key_info) if edk_key_id != self._key_id: raise DecryptKeyError( "Cannot decrypt EDK wrapped by key_id={}, because it does not match this " "provider's key_id={}".format(edk_key_id, self._key_id)) kms_params = { "CiphertextBlob": encrypted_data_key.encrypted_data_key, "KeyId": edk_key_id } if encryption_context: kms_params["EncryptionContext"] = encryption_context if self.config.grant_tokens: kms_params["GrantTokens"] = self.config.grant_tokens # Catch any boto3 errors and normalize to expected DecryptKeyError try: response = self.config.client.decrypt(**kms_params) returned_key_id = response["KeyId"] if returned_key_id != edk_key_id: error_message = "AWS KMS returned unexpected key_id {returned} (expected {key_id})".format( returned=returned_key_id, key_id=edk_key_id) _LOGGER.exception(error_message) raise DecryptKeyError(error_message) plaintext = response["Plaintext"] except (ClientError, KeyError): error_message = "Master Key {key_id} unable to decrypt data key".format( key_id=self._key_id) _LOGGER.exception(error_message) raise DecryptKeyError(error_message) return DataKey( key_provider=self.key_provider, data_key=plaintext, encrypted_data_key=encrypted_data_key.encrypted_data_key)
def decrypt_data_key_from_list(self, encrypted_data_keys, algorithm, encryption_context): """Receives a list of encrypted data keys and returns the first one which this provider is able to decrypt. :param encrypted_data_keys: List of encrypted data keys :type encrypted_data_keys: list of :class:`aws_encryption_sdk.structures.EncryptedDataKey` :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param dict encryption_context: Encryption context to use in encryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if unable to decrypt any of the supplied encrypted data keys """ data_key = None for encrypted_data_key in encrypted_data_keys: try: data_key = self.decrypt_data_key(encrypted_data_key, algorithm, encryption_context) # MasterKeyProvider.decrypt_data_key throws DecryptKeyError # but MasterKey.decrypt_data_key throws IncorrectMasterKeyError except (DecryptKeyError, IncorrectMasterKeyError): continue else: break if not data_key: raise DecryptKeyError("Unable to decrypt any data key") return data_key
def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context=None): """Decrypts an encrypted data key and returns the plaintext. :param data_key: Encrypted data key :type data_key: aws_encryption_sdk.structures.EncryptedDataKey :type algorithm: `aws_encryption_sdk.identifiers.Algorithm` (not used for KMS) :param dict encryption_context: Encryption context to use in decryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if Master Key is unable to decrypt data key """ kms_params = {"CiphertextBlob": encrypted_data_key.encrypted_data_key} if encryption_context: kms_params["EncryptionContext"] = encryption_context if self.config.grant_tokens: kms_params["GrantTokens"] = self.config.grant_tokens # Catch any boto3 errors and normalize to expected DecryptKeyError try: response = self.config.client.decrypt(**kms_params) plaintext = response["Plaintext"] except (ClientError, KeyError): error_message = "Master Key {key_id} unable to decrypt data key".format( key_id=self._key_id) _LOGGER.exception(error_message) raise DecryptKeyError(error_message) return DataKey( key_provider=self.key_provider, data_key=plaintext, encrypted_data_key=encrypted_data_key.encrypted_data_key)
def _do_aws_kms_decrypt(client_supplier, key_name, encrypted_data_key, encryption_context, grant_tokens): # type: (ClientSupplierType, str, EncryptedDataKey, Dict[str, str], Iterable[str]) -> RawDataKey """Attempt to call ``kms:Decrypt`` and return the resulting plaintext data key. Any errors encountered are passed up the chain without comment. .. versionadded:: 1.5.0 """ region = _region_from_key_id(encrypted_data_key.key_provider.key_info.decode("utf-8")) client = client_supplier(region) response = client.decrypt( CiphertextBlob=encrypted_data_key.encrypted_data_key, EncryptionContext=encryption_context, GrantTokens=grant_tokens, ) response_key_id = response["KeyId"] if response_key_id != key_name: raise DecryptKeyError( "Decryption results from AWS KMS are for an unexpected key ID!" " actual '{actual}' != expected '{expected}'".format(actual=response_key_id, expected=key_name) ) return RawDataKey( key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=response_key_id), data_key=response["Plaintext"] )
def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): """Iterates through all currently added Master Keys and Master Key Providers to attempt to decrypt data key. :param encrypted_data_key: Encrypted data key to decrypt :type encrypted_data_key: aws_encryption_sdk.structures.EncryptedDataKey :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param dict encryption_context: Encryption context to use in encryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if unable to decrypt encrypted data key """ _LOGGER.debug("starting decrypt data key attempt") for master_key in self.master_keys_for_data_key(encrypted_data_key): try: _LOGGER.debug( "attempting to decrypt data key with provider %s", encrypted_data_key.key_provider.key_info) return master_key.decrypt_data_key(encrypted_data_key, algorithm, encryption_context) # MasterKeyProvider.decrypt_data_key throws DecryptKeyError # but MasterKey.decrypt_data_key throws IncorrectMasterKeyError except (IncorrectMasterKeyError, DecryptKeyError) as error: _LOGGER.debug( "%s raised when attempting to decrypt data key with master key %s", repr(error), master_key.key_provider, ) continue raise DecryptKeyError("Unable to decrypt data key")
def _validate_allowed_to_decrypt(self, edk_key_id): """Checks that this provider is allowed to decrypt with the given key id. Compared to the default KMS provider, this checks for MRK equality between the edk and the configured key id rather than strict string equality. """ if not _check_mrk_arns_equal(edk_key_id, self._key_id): raise DecryptKeyError( "Cannot decrypt EDK wrapped by key_id={}, because it does not match this " "provider's key_id={}".format(edk_key_id, self._key_id))
def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): """Iterates through all currently added Master Keys and Master Key Providers to attempt to decrypt data key. :param encrypted_data_key: Encrypted data key to decrypt :type encrypted_data_key: aws_encryption_sdk.structures.EncryptedDataKey :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param dict encryption_context: Encryption context to use in encryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if unable to decrypt encrypted data key """ data_key = None master_key = None _LOGGER.debug("starting decrypt data key attempt") for member in [self] + self._members: if member.provider_id == encrypted_data_key.key_provider.provider_id: _LOGGER.debug( "attempting to locate master key from key provider: %s", member.provider_id) if isinstance(member, MasterKey): _LOGGER.debug("using existing master key") master_key = member elif self.vend_masterkey_on_decrypt: try: _LOGGER.debug("attempting to add master key: %s", encrypted_data_key.key_provider.key_info) master_key = member.master_key_for_decrypt( encrypted_data_key.key_provider.key_info) except InvalidKeyIdError: _LOGGER.debug( "master key %s not available in provider", encrypted_data_key.key_provider.key_info) continue else: continue try: _LOGGER.debug( "attempting to decrypt data key with provider %s", encrypted_data_key.key_provider.key_info) data_key = master_key.decrypt_data_key( encrypted_data_key, algorithm, encryption_context) except (IncorrectMasterKeyError, DecryptKeyError) as error: _LOGGER.debug( "%s raised when attempting to decrypt data key with master key %s", repr(error), master_key.key_provider, ) continue break # If this point is reached without throwing any errors, the data key has been decrypted if not data_key: raise DecryptKeyError("Unable to decrypt data key") return data_key
def test_decrypt_data_key_unsuccessful_no_matching_members(self): mock_member = MagicMock() mock_member.provider_id = sentinel.another_provider_id mock_encrypted_data_key = MagicMock() mock_encrypted_data_key.key_provider.provider_id = sentinel.provider_id mock_encrypted_data_key.key_provider.key_info = sentinel.key_info mock_master_key = MagicMock() mock_master_key.decrypt_data_key.side_effect = DecryptKeyError() mock_master_key_provider = MockMasterKeyProvider( provider_id=sentinel.provider_id, mock_new_master_key=mock_master_key) mock_master_key_provider._members = [mock_member] with six.assertRaisesRegex(self, DecryptKeyError, 'Unable to decrypt data key'): mock_master_key_provider.decrypt_data_key( encrypted_data_key=mock_encrypted_data_key, algorithm=sentinel.algorithm, encryption_context=sentinel.encryption_context)
def test_decrypt_data_key_unsuccessful_no_matching_members(self): mock_member = MagicMock() mock_member.provider_id = sentinel.another_provider_id mock_encrypted_data_key = MagicMock() mock_encrypted_data_key.key_provider.provider_id = sentinel.provider_id mock_encrypted_data_key.key_provider.key_info = sentinel.key_info mock_master_key = MagicMock() mock_master_key.decrypt_data_key.side_effect = DecryptKeyError() mock_master_key_provider = MockMasterKeyProvider( provider_id=sentinel.provider_id, mock_new_master_key=mock_master_key ) mock_master_key_provider._members = [mock_member] with pytest.raises(DecryptKeyError) as excinfo: mock_master_key_provider.decrypt_data_key( encrypted_data_key=mock_encrypted_data_key, algorithm=sentinel.algorithm, encryption_context=sentinel.encryption_context, ) excinfo.match("Unable to decrypt data key")
def _decrypt_data_key(self, encrypted_data_key: EncryptedDataKey, algorithm: AlgorithmSuite, encryption_context: Dict[Text, Text]) -> DataKey: """Decrypt an encrypted data key and return the plaintext. :param data_key: Encrypted data key :type data_key: aws_encryption_sdk.structures.EncryptedDataKey :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param dict encryption_context: Encryption context to use in decryption :returns: Data key containing decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if Master Key is unable to decrypt data key """ if encrypted_data_key.encrypted_data_key != self._encrypted_data_key: raise DecryptKeyError( 'Master Key "{provider}" unable to decrypt data key'.format( provider=self.key_provider)) return self._generate_data_key(algorithm, encryption_context)
def test_decrypt_data_key_unsuccessful_master_key_decryt_error(self): mock_member = MagicMock() mock_member.provider_id = sentinel.provider_id mock_master_key = MagicMock() mock_master_key.decrypt_data_key.side_effect = (DecryptKeyError, ) mock_member.master_key_for_decrypt.return_value = mock_master_key mock_encrypted_data_key = MagicMock() mock_encrypted_data_key.key_provider.provider_id = sentinel.provider_id mock_encrypted_data_key.key_provider.key_info = sentinel.key_info mock_master_key = MagicMock() mock_master_key.decrypt_data_key.side_effect = DecryptKeyError() mock_master_key_provider = MockMasterKeyProvider( provider_id=sentinel.provider_id, mock_new_master_key=mock_master_key) mock_master_key_provider._members = [mock_member] with six.assertRaisesRegex(self, DecryptKeyError, "Unable to decrypt data key"): mock_master_key_provider.decrypt_data_key( encrypted_data_key=mock_encrypted_data_key, algorithm=sentinel.algorithm, encryption_context=sentinel.encryption_context, )
def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): raise DecryptKeyError( "FailingDecryptMasterKeyProvider cannot decrypt!")
def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): """Iterates through all currently added Master Keys and Master Key Providers to attempt to decrypt data key. :param encrypted_data_key: Encrypted data key to decrypt :type encrypted_data_key: aws_encryption_sdk.structures.EncryptedDataKey :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param dict encryption_context: Encryption context to use in encryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if unable to decrypt encrypted data key """ data_key = None master_key = None _LOGGER.debug("starting decrypt data key attempt") for member in [self] + self._members: # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9 # //# To match the encrypted data key's # //# provider ID MUST exactly match the value "aws-kms". if member.provider_id == encrypted_data_key.key_provider.provider_id: _LOGGER.debug( "attempting to locate master key from key provider: %s", member.provider_id) if isinstance(member, MasterKey): _LOGGER.debug("using existing master key") master_key = member elif self.vend_masterkey_on_decrypt: # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9 # //# For each encrypted data key in the filtered set, one at a time, the # //# master key provider MUST call Get Master Key (aws-kms-mrk-aware- # //# master-key-provider.md#get-master-key) with the encrypted data key's # //# provider info as the AWS KMS key ARN. # We attempt to decrypt with pre-populated self._members for strict MKPs/MKs # and vend new MKs for Discovery MPKs/MKs. try: _LOGGER.debug("attempting to add master key: %s", encrypted_data_key.key_provider.key_info) master_key = member.master_key_for_decrypt( encrypted_data_key.key_provider.key_info) except InvalidKeyIdError: _LOGGER.debug( "master key %s not available in provider", encrypted_data_key.key_provider.key_info) continue else: continue try: _LOGGER.debug( "attempting to decrypt data key with provider %s", encrypted_data_key.key_provider.key_info) # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9 # //# It MUST call Decrypt Data Key # //# (aws-kms-mrk-aware-master-key.md#decrypt-data-key) on this master key # //# with the input algorithm, this single encrypted data key, and the # //# input encryption context. data_key = master_key.decrypt_data_key( encrypted_data_key, algorithm, encryption_context) except (IncorrectMasterKeyError, DecryptKeyError) as error: _LOGGER.debug( "%s raised when attempting to decrypt data key with master key %s", repr(error), master_key.key_provider, ) continue # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9 # //# If the AWS KMS response satisfies the requirements then it MUST be # //# use and this function MUST return and not attempt to decrypt any more # //# encrypted data keys. # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9 # //# If the decrypt data key call is # //# successful, then this function MUST return this result and not # //# attempt to decrypt any more encrypted data keys. break # If this point is reached without throwing any errors, the data key has been decrypted if not data_key: # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9 # //# If all the input encrypted data keys have been processed then this # //# function MUST yield an error that includes all the collected errors. # Note the latter half of "includes all collected errors" is not satisfied # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9 # //# If all the input encrypted data keys have been processed then this # //# function MUST yield an error that includes all the collected errors. # Note the latter half of "includes all collected errors" is not satisfied raise DecryptKeyError("Unable to decrypt data key") return data_key
def _validate_allowed_to_decrypt(self, edk_key_id): """Checks that this provider is allowed to decrypt with the given key id.""" if edk_key_id != self._key_id: raise DecryptKeyError( "Cannot decrypt EDK wrapped by key_id={}, because it does not match this " "provider's key_id={}".format(edk_key_id, self._key_id))
def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context=None): """Decrypts an encrypted data key and returns the plaintext. :param data_key: Encrypted data key :type data_key: aws_encryption_sdk.structures.EncryptedDataKey :type algorithm: `aws_encryption_sdk.identifiers.Algorithm` (not used for KMS) :param dict encryption_context: Encryption context to use in decryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if Master Key is unable to decrypt data key """ # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9 # //# Additionally each provider info MUST be a valid AWS KMS ARN # //# (aws-kms-key-arn.md#a-valid-aws-kms-arn) with a resource type of # //# "key". edk_key_id = to_str(encrypted_data_key.key_provider.key_info) edk_arn = arn_from_str(edk_key_id) if not edk_arn.resource_type == "key": error_message = "AWS KMS Provider EDK contains unexpected key_id: {key_id}".format( key_id=edk_key_id) _LOGGER.exception(error_message) raise DecryptKeyError(error_message) self._validate_allowed_to_decrypt(edk_key_id) kms_params = self._build_decrypt_request(encrypted_data_key, encryption_context) # Catch any boto3 errors and normalize to expected DecryptKeyError try: response = self.config.client.decrypt(**kms_params) # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9 # //# If the call succeeds then the response's "KeyId" MUST be equal to the # //# configured AWS KMS key identifier otherwise the function MUST collect # //# an error. # Note that Python logs but does not collect errors returned_key_id = response["KeyId"] if returned_key_id != self._key_id: error_message = "AWS KMS returned unexpected key_id {returned} (expected {key_id})".format( returned=returned_key_id, key_id=self._key_id) _LOGGER.exception(error_message) raise DecryptKeyError(error_message) # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9 # //# The response's "Plaintext"'s length MUST equal the length # //# required by the requested algorithm suite otherwise the function MUST # //# collect an error. # Note that Python logs but does not collect errors plaintext = response["Plaintext"] if len(plaintext) != algorithm.data_key_len: error_message = "Plaintext length ({len1}) does not match algorithm's expected length ({len2})".format( len1=len(plaintext), len2=algorithm.data_key_len) raise DecryptKeyError(error_message) except (ClientError, KeyError): error_message = "Master Key {key_id} unable to decrypt data key".format( key_id=self._key_id) _LOGGER.exception(error_message) raise DecryptKeyError(error_message) return DataKey( key_provider=self.key_provider, data_key=plaintext, encrypted_data_key=encrypted_data_key.encrypted_data_key)