def test_client_no_region_name_without_default(self): test = KMSMasterKeyProvider() with six.assertRaisesRegex( self, UnknownRegionError, 'No default region found and no region determinable from key id: *' ): test._client('')
def test_client_no_region_name_with_default(self, mock_add_client): test = KMSMasterKeyProvider() test.default_region = sentinel.default_region test._regional_clients[sentinel.default_region] = sentinel.default_client client = test._client("") assert client is sentinel.default_client mock_add_client.assert_called_with(sentinel.default_region)
def test_client_no_region_name_without_default(self): test = KMSMasterKeyProvider() with pytest.raises(UnknownRegionError) as excinfo: test._client("") excinfo.match( "No default region found and no region determinable from key id: *" )
def test_add_regional_clients_from_list(self, mock_add_client): test = KMSMasterKeyProvider() test.add_regional_clients_from_list( [sentinel.region_a, sentinel.region_b, sentinel.region_c]) mock_add_client.assert_has_calls( (call(sentinel.region_a), call(sentinel.region_b), call(sentinel.region_c)))
def setup_kms_master_key_provider(): """Reads the test_values config file and builds the requested KMS Master Key Provider.""" config = read_test_config() cmk_arn = get_cmk_arn(config) botocore_session = setup_botocore_session(config) kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore_session) kms_master_key_provider.add_master_key(cmk_arn) return kms_master_key_provider
def test_new_master_key(self, mock_client): mock_client.return_value = self.mock_boto3_client_instance key_info = 'example key info asdf' test = KMSMasterKeyProvider() key = test._new_master_key(key_info) check_key = KMSMasterKey(key_id=key_info, client=self.mock_boto3_client_instance) assert key == check_key
def test_client_valid_region_name(self, mock_add_client): test = KMSMasterKeyProvider() test._regional_clients['us-east-1'] = self.mock_boto3_client_instance client = test._client( 'arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb' ) mock_add_client.assert_called_once_with('us-east-1') assert client is self.mock_boto3_client_instance
def test_init_with_default_region_not_found(self, mock_add_regional_client): test = KMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) assert test.default_region is None with patch.object(test.config.botocore_session, "get_config_variable", return_value=None) as mock_get_config: test._process_config() mock_get_config.assert_called_once_with("region") assert test.default_region is None assert not mock_add_regional_client.called
def test_new_master_key(self, mock_client): """v1.2.4 : master key equality is left to the Python object identity now""" mock_client.return_value = self.mock_boto3_client_instance key_info = "example key info asdf" test = KMSMasterKeyProvider() key = test._new_master_key(key_info) check_key = KMSMasterKey(key_id=key_info, client=self.mock_boto3_client_instance) assert key != check_key
def test_remove_bad_client(): test = KMSMasterKeyProvider() test.add_regional_client("us-fakey-12") with pytest.raises(BotoCoreError): test._regional_clients["us-fakey-12"].list_keys() assert not test._regional_clients
def test_add_regional_client_new(self): test = KMSMasterKeyProvider() test._regional_clients = {} test.add_regional_client('ex_region_name') self.mock_boto3_session.assert_called_once_with( region_name='ex_region_name', botocore_session=ANY) self.mock_boto3_session_instance.client.assert_called_once_with('kms') assert test._regional_clients[ 'ex_region_name'] is self.mock_boto3_client_instance
def test_remove_bad_client(): test = KMSMasterKeyProvider() fake_region = "us-fakey-12" test.add_regional_client(fake_region) with pytest.raises(BotoCoreError): test._regional_clients[fake_region].list_keys() assert fake_region not in test._regional_clients
def test_add_regional_client_new(self): test = KMSMasterKeyProvider() test._regional_clients = {} test.add_regional_client("ex_region_name") self.mock_boto3_session.assert_called_with( region_name="ex_region_name", botocore_session=ANY) self.mock_boto3_session_instance.client.assert_called_with( "kms", config=test._user_agent_adding_config) assert test._regional_clients[ "ex_region_name"] is self.mock_boto3_client_instance
def test_init_with_default_region_found(self, mock_add_regional_client): test = KMSMasterKeyProvider() assert test.default_region is None with patch.object( test.config.botocore_session, 'get_config_variable', return_value=sentinel.default_region) as mock_get_config: test._process_config() mock_get_config.assert_called_once_with('region') assert test.default_region is sentinel.default_region mock_add_regional_client.assert_called_once_with( sentinel.default_region)
def setup_kms_master_key_provider(cache=True): """Build an AWS KMS Master Key Provider.""" global _KMS_MKP # pylint: disable=global-statement if cache and _KMS_MKP is not None: return _KMS_MKP cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider() kms_master_key_provider.add_master_key(cmk_arn.encode("utf-8")) if cache: _KMS_MKP = kms_master_key_provider return kms_master_key_provider
def setup_kms_master_key_provider_with_botocore_session(cache=True): """Build an AWS KMS Master Key Provider with an explicit botocore_session.""" global _KMS_MKP_BOTO # pylint: disable=global-statement if cache and _KMS_MKP_BOTO is not None: return _KMS_MKP_BOTO cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore.session.Session()) kms_master_key_provider.add_master_key(cmk_arn.encode("utf-8")) if cache: _KMS_MKP_BOTO = kms_master_key_provider return kms_master_key_provider
def setup_kms_master_key_provider_with_botocore_session(cache=True): """Reads the test_values config file and builds the requested KMS Master Key Provider with botocore_session.""" global _KMS_MKP_BOTO # pylint: disable=global-statement if cache and _KMS_MKP_BOTO is not None: return _KMS_MKP_BOTO cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore.session.Session()) kms_master_key_provider.add_master_key(cmk_arn) if cache: _KMS_MKP_BOTO = kms_master_key_provider return kms_master_key_provider
def setup_kms_master_key_provider(cache=True): """Reads the test_values config file and builds the requested KMS Master Key Provider.""" global _KMS_MKP # pylint: disable=global-statement if cache and _KMS_MKP is not None: return _KMS_MKP cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider() kms_master_key_provider.add_master_key(cmk_arn) if cache: _KMS_MKP = kms_master_key_provider return kms_master_key_provider
def kms_master_key_provider(cache: Optional[bool] = True): """Build the expected KMS Master Key Provider based on environment variables.""" global _KMS_MKP # pylint: disable=global-statement if cache and _KMS_MKP is not None: return _KMS_MKP cmk_arn = get_cmk_arn() _kms_master_key_provider = KMSMasterKeyProvider() _kms_master_key_provider.add_master_key(cmk_arn) if cache: _KMS_MKP = _kms_master_key_provider return _kms_master_key_provider
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate configuring an AWS KMS master key provider for decryption. :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys :param bytes source_plaintext: Plaintext to encrypt """ # Prepare your encryption context. # Remember that your encryption context is NOT SECRET. # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context encryption_context = { "encryption": "context", "is not": "secret", "but adds": "useful metadata", "that can help you": "be confident that", "the data you are handling": "is what you think it is", } # Create the master key that determines how your data keys are protected. encrypt_master_key = KMSMasterKey(key_id=aws_kms_cmk) # Create an AWS KMS master key provider to use on decrypt. decrypt_master_key_provider = KMSMasterKeyProvider() # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, key_provider=encrypt_master_key) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the AWS KMS master key provider. # # You do not need to specify the encryption context on decrypt # because the header of the encrypted message includes the encryption context. decrypted, decrypt_header = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=decrypt_master_key_provider) # Demonstrate that the decrypted plaintext is identical to the original plaintext. assert decrypted == source_plaintext # Verify that the encryption context used in the decrypt operation includes # the encryption context that you specified when encrypting. # The AWS Encryption SDK can add pairs, so don't require an exact match. # # In production, always use a meaningful encryption context. assert set(encryption_context.items()) <= set( decrypt_header.encryption_context.items())
def configure_key_provider(graph, key_ids): """ Configure a key provider. During unit tests, use a static key provider (e.g. without AWS calls). """ if graph.metadata.testing: # use static provider provider = StaticMasterKeyProvider() provider.add_master_keys_from_list(key_ids) return provider # use AWS provider return KMSMasterKeyProvider(key_ids=key_ids)
def run(aws_kms_cmk, aws_kms_additional_cmks, source_plaintext): # type: (str, Sequence[str], bytes) -> None """Demonstrate how to create a keyring that behaves like an AWS KMS master key provider. :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys :param List[str] aws_kms_additional_cmks: Additional ARNs of secondary AWS KMS CMKs :param bytes source_plaintext: Plaintext to encrypt """ # Prepare your encryption context. # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context encryption_context = { "encryption": "context", "is not": "secret", "but adds": "useful metadata", "that can help you": "be confident that", "the data you are handling": "is what you think it is", } # This is the master key provider whose behavior we want to reproduce. # # When encrypting, this master key provider generates the data key using the first CMK in the list # and encrypts the data key using all specified CMKs. # However, when decrypting, this master key provider attempts to decrypt # any data keys that were encrypted under an AWS KMS CMK. master_key_provider_cmks = [aws_kms_cmk] + aws_kms_additional_cmks _master_key_provider_to_replicate = KMSMasterKeyProvider( # noqa: intentionally never used key_ids=master_key_provider_cmks, ) # Create a CMK keyring that encrypts and decrypts using the specified AWS KMS CMKs. # # This keyring reproduces the encryption behavior of the AWS KMS master key provider. # # The AWS KMS keyring requires that you explicitly identify the CMK # that you want the keyring to use to generate the data key. cmk_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk, key_ids=aws_kms_additional_cmks) # Create an AWS KMS discovery keyring that will attempt to decrypt # any data keys that were encrypted under an AWS KMS CMK. discovery_keyring = AwsKmsKeyring(is_discovery=True) # Combine the CMK and discovery keyrings # to create a keyring that behaves like an AWS KMS master key provider. # # The CMK keyring reproduces the encryption behavior # and the discovery keyring reproduces the decryption behavior. # This also means that it does not matter if the CMK keyring fails to decrypt. # For example, if you configured the CMK keyring with aliases, # it works on encrypt but fails to match any encrypted data keys on decrypt # because the serialized key name is the resulting CMK ARN rather than the alias name. # However, because the discovery keyring attempts to decrypt any AWS KMS-encrypted # data keys that it finds, the message still decrypts successfully. keyring = MultiKeyring(generator=cmk_keyring, children=[discovery_keyring]) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=keyring) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the same keyring you used on encrypt. # # You do not need to specify the encryption context on decrypt # because the header of the encrypted message includes the encryption context. decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) # Demonstrate that the decrypted plaintext is identical to the original plaintext. assert decrypted == source_plaintext # Verify that the encryption context used in the decrypt operation includes # the encryption context that you specified when encrypting. # The AWS Encryption SDK can add pairs, so don't require an exact match. # # In production, always use a meaningful encryption context. assert set(encryption_context.items()) <= set( decrypt_header.encryption_context.items())
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate configuring a master key provider to use an AWS KMS CMK and an RSA wrapping key. :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys :param bytes source_plaintext: Plaintext to encrypt """ # Prepare your encryption context. # Remember that your encryption context is NOT SECRET. # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context encryption_context = { "encryption": "context", "is not": "secret", "but adds": "useful metadata", "that can help you": "be confident that", "the data you are handling": "is what you think it is", } # Generate an RSA private key to use with your master key. # In practice, you should get this key from a secure key management system such as an HSM. # # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths # # Why did we use this public exponent? # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) # Serialize the RSA private key to PEM encoding. # This or DER encoding is likely to be what you get from your key management system in practice. private_key_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) # Collect the public key from the private key. public_key = private_key.public_key() # Serialize the RSA public key to PEM encoding. # This or DER encoding is likely to be what you get from your key management system in practice. public_key_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) # Create the encrypt master key that only has access to the public key. escrow_encrypt_master_key = RawMasterKey( # The provider ID and key ID are defined by you # and are used by the raw RSA master key # to determine whether it should attempt to decrypt # an encrypted data key. provider_id= "some managed raw keys", # provider ID corresponds to key namespace for keyrings key_id= b"my RSA wrapping key", # key ID corresponds to key name for keyrings wrapping_key=WrappingKey( wrapping_key=public_key_pem, wrapping_key_type=EncryptionKeyType.PUBLIC, # The wrapping algorithm tells the raw RSA master key # how to use your wrapping key to encrypt data keys. # # We recommend using RSA_OAEP_SHA256_MGF1. # You should not use RSA_PKCS1 unless you require it for backwards compatibility. wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, ), ) # Create the decrypt master key that has access to the private key. escrow_decrypt_master_key = RawMasterKey( # The key namespace and key name MUST match the encrypt master key. provider_id= "some managed raw keys", # provider ID corresponds to key namespace for keyrings key_id= b"my RSA wrapping key", # key ID corresponds to key name for keyrings wrapping_key=WrappingKey( wrapping_key=private_key_pem, wrapping_key_type=EncryptionKeyType.PRIVATE, # The wrapping algorithm MUST match the encrypt master key. wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, ), ) # Create the AWS KMS master key that you will use for decryption during normal operations. kms_master_key = KMSMasterKeyProvider(key_ids=[aws_kms_cmk]) # Add the escrow encrypt master key to the AWS KMS master key. kms_master_key.add_master_key_provider(escrow_encrypt_master_key) # Encrypt your plaintext data using the combined master keys. ciphertext, encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, key_provider=kms_master_key) # Verify that the header contains the expected number of encrypted data keys (EDKs). # It should contain one EDK for AWS KMS and one for the escrow key. assert len(encrypt_header.encrypted_data_keys) == 2 # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data separately using the AWS KMS master key and the escrow decrypt master key. # # You do not need to specify the encryption context on decrypt # because the header of the encrypted message includes the encryption context. decrypted_kms, decrypt_header_kms = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=kms_master_key) decrypted_escrow, decrypt_header_escrow = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=escrow_decrypt_master_key) # Demonstrate that the decrypted plaintext is identical to the original plaintext. assert decrypted_kms == source_plaintext assert decrypted_escrow == source_plaintext # Verify that the encryption context used in the decrypt operation includes # the encryption context that you specified when encrypting. # The AWS Encryption SDK can add pairs, so don't require an exact match. # # In production, always use a meaningful encryption context. assert set(encryption_context.items()) <= set( decrypt_header_kms.encryption_context.items()) assert set(encryption_context.items()) <= set( decrypt_header_escrow.encryption_context.items())
def test_init_with_region_names(self, mock_add_clients): region_names = (sentinel.region_name_1, sentinel.region_name_2) test = KMSMasterKeyProvider(region_names=region_names) mock_add_clients.assert_called_once_with(region_names) assert test.default_region is sentinel.region_name_1
def test_init_with_key_ids(self, mock_add_keys): mock_ids = (sentinel.id_1, sentinel.id_2) KMSMasterKeyProvider(key_ids=mock_ids) mock_add_keys.assert_called_once_with(mock_ids)
def test_init_bare(self, mock_process_config): KMSMasterKeyProvider() mock_process_config.assert_called_once_with()
def test_add_regional_client_exists(self): test = KMSMasterKeyProvider( botocore_session=self.botocore_no_region_session) test._regional_clients["ex_region_name"] = sentinel.existing_client test.add_regional_client("ex_region_name") assert not self.mock_boto3_session.called
def test_init_with_regionless_key_ids_and_region_names(): key_ids = ("alias/key_1", ) region_names = ("test-region-1", ) provider = KMSMasterKeyProvider(region_names=region_names, key_ids=key_ids) assert provider.master_key( "alias/key_1").config.client.meta.region_name == region_names[0]
def test_add_regional_client_exists(self): test = KMSMasterKeyProvider() test._regional_clients['ex_region_name'] = sentinel.existing_client test.add_regional_client('ex_region_name') assert not self.mock_boto3_session.called
def _master_key_provider() -> KMSMasterKeyProvider: """Build the V0 master key provider.""" master_key_provider = KMSMasterKeyProvider() master_key_provider.add_master_key_provider(NullMasterKey()) master_key_provider.add_master_key_provider(CountingMasterKey()) return master_key_provider