From 4be5c222f7015585c5c73b1ba9bad3fa9cb1fd94 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 22 Apr 2020 18:08:25 -0700 Subject: [PATCH 1/3] feat: refactor raw RSA keyrings configuration per #257 --- examples/README.md | 4 +- .../{private_key_only.py => keypair.py} | 3 +- ...y_only_from_pem.py => keypair_from_pem.py} | 8 +- src/aws_encryption_sdk/keyrings/raw.py | 15 +- test/functional/keyrings/raw/test_raw_rsa.py | 180 +++++++++++++----- test/functional/keyrings/test_multi.py | 20 +- test/unit/keyrings/raw/test_raw_rsa.py | 29 ++- test/unit/unit_test_utils.py | 30 +-- 8 files changed, 185 insertions(+), 104 deletions(-) rename examples/src/keyring/raw_rsa/{private_key_only.py => keypair.py} (97%) rename examples/src/keyring/raw_rsa/{private_key_only_from_pem.py => keypair_from_pem.py} (94%) diff --git a/examples/README.md b/examples/README.md index 081e62fab..359a8a428 100644 --- a/examples/README.md +++ b/examples/README.md @@ -49,9 +49,9 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * [with keyrings](./src/keyring/raw_aes/raw_aes.py) * [with master key providers](./src/master_key_provider/raw_aes/raw_aes.py) * How to use a raw RSA wrapping key - * [with keyrings](./src/keyring/raw_rsa/private_key_only.py) + * [with keyrings](src/keyring/raw_rsa/keypair.py) * How to use a raw RSA wrapping key when the key is PEM or DER encoded - * [with keyrings](./src/keyring/raw_rsa/private_key_only_from_pem.py) + * [with keyrings](src/keyring/raw_rsa/keypair_from_pem.py) * [with master key providers](./src/master_key_provider/raw_rsa/private_key_only_from_pem.py) * How to encrypt with a raw RSA public key wrapping key without access to the private key * [with keyrings](./src/keyring/raw_rsa/public_private_key_separate.py) diff --git a/examples/src/keyring/raw_rsa/private_key_only.py b/examples/src/keyring/raw_rsa/keypair.py similarity index 97% rename from examples/src/keyring/raw_rsa/private_key_only.py rename to examples/src/keyring/raw_rsa/keypair.py index db7705735..c415a9d01 100644 --- a/examples/src/keyring/raw_rsa/private_key_only.py +++ b/examples/src/keyring/raw_rsa/keypair.py @@ -1,7 +1,7 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 """ -This examples shows how to configure and use a raw RSA keyring using a pre-loaded RSA private key. +This examples shows how to configure and use a raw RSA keyring using a pre-loaded RSA keypair. If your RSA key is in PEM or DER format, see the ``keyring/raw_rsa/private_key_only_from_pem`` example. @@ -55,6 +55,7 @@ def run(source_plaintext): key_namespace="some managed raw keys", key_name=b"my RSA wrapping key", private_wrapping_key=private_key, + public_wrapping_key=private_key.public_key(), # The wrapping algorithm tells the raw RSA keyring # how to use your wrapping key to encrypt data keys. # diff --git a/examples/src/keyring/raw_rsa/private_key_only_from_pem.py b/examples/src/keyring/raw_rsa/keypair_from_pem.py similarity index 94% rename from examples/src/keyring/raw_rsa/private_key_only_from_pem.py rename to examples/src/keyring/raw_rsa/keypair_from_pem.py index 397a79274..e586f65c6 100644 --- a/examples/src/keyring/raw_rsa/private_key_only_from_pem.py +++ b/examples/src/keyring/raw_rsa/keypair_from_pem.py @@ -3,7 +3,7 @@ """ When you store RSA keys, you have to serialize them somehow. -This example shows how to configure and use a raw RSA keyring using a PEM-encoded RSA private key. +This example shows how to configure and use a raw RSA keyring using a PEM-encoded RSA keypair. The most commonly used encodings for RSA keys tend to be PEM and DER. The raw RSA keyring supports loading both public and private keys from these encodings. @@ -47,13 +47,16 @@ def run(source_plaintext): # 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. + # Serialize the RSA keypair 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(), ) + public_key_pem = private_key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) # Create the keyring that determines how your data keys are protected. # @@ -68,6 +71,7 @@ def run(source_plaintext): key_namespace="some managed raw keys", key_name=b"my RSA wrapping key", private_encoded_key=private_key_pem, + public_encoded_key=public_key_pem, # The wrapping algorithm tells the raw RSA keyring # how to use your wrapping key to encrypt data keys. # diff --git a/src/aws_encryption_sdk/keyrings/raw.py b/src/aws_encryption_sdk/keyrings/raw.py index 450111ad7..72531fdbc 100644 --- a/src/aws_encryption_sdk/keyrings/raw.py +++ b/src/aws_encryption_sdk/keyrings/raw.py @@ -283,8 +283,12 @@ def __attrs_post_init__(self): if self._public_wrapping_key is None and self._private_wrapping_key is None: raise TypeError("At least one of public key or private key must be provided.") - if self._private_wrapping_key is not None and self._public_wrapping_key is None: - self._public_wrapping_key = self._private_wrapping_key.public_key() + if self._public_wrapping_key is not None and self._private_wrapping_key is not None: + derived_public_key = self._private_wrapping_key.public_key() + # We cannot compare the public key objects directly. + # Instead, extract their numbers and compare those. + if derived_public_key.public_numbers() != self._public_wrapping_key.public_numbers(): + raise ValueError("Private and public wrapping keys MUST be from the same keypair.") @classmethod def from_pem_encoding( @@ -375,13 +379,12 @@ def on_encrypt(self, encryption_materials): """ new_materials = encryption_materials + if self._public_wrapping_key is None: + return encryption_materials + if new_materials.data_encryption_key is None: new_materials = _generate_data_key(encryption_materials=new_materials, key_provider=self._key_provider) - if self._public_wrapping_key is None: - # This should be impossible, but just in case, give a useful error message. - raise EncryptKeyError("Raw RSA keyring unable to encrypt data key: no public key available") - try: # Encrypt data key encrypted_wrapped_key = EncryptedData( diff --git a/test/functional/keyrings/raw/test_raw_rsa.py b/test/functional/keyrings/raw/test_raw_rsa.py index b7a278d2b..ec785974d 100644 --- a/test/functional/keyrings/raw/test_raw_rsa.py +++ b/test/functional/keyrings/raw/test_raw_rsa.py @@ -42,79 +42,41 @@ _BACKEND = default_backend() _PRIVATE_WRAPPING_KEY = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) +_PUBLIC_WRAPPING_KEY = _PRIVATE_WRAPPING_KEY.public_key() -_PRIVATE_WRAPPING_KEY_PEM = ( - b"-----BEGIN RSA PRIVATE KEY-----\n" - b"MIIEowIBAAKCAQEAo8uCyhiO4JUGZV+rtNq5DBA9Lm4xkw5kTA3v6EPybs8bVXL2\n" - b"ZE6jkbo+xT4Jg/bKzUpnp1fE+T1ruGPtsPdoEmhY/P64LDNIs3sRq5U4QV9IETU1\n" - b"vIcbNNkgGhRjV8J87YNY0tV0H7tuWuZRpqnS+gjV6V9lUMkbvjMCc5IBqQc3heut\n" - b"/+fH4JwpGlGxOVXI8QAapnSy1XpCr3+PT29kydVJnIMuAoFrurojRpOQbOuVvhtA\n" - b"gARhst1Ji4nfROGYkj6eZhvkz2Bkud4/+3lGvVU5LO1vD8oY7WoGtpin3h50VcWe\n" - b"aBT4kejx4s9/G9C4R24lTH09J9HO2UUsuCqZYQIDAQABAoIBAQCfC90bCk+qaWqF\n" - b"gymC+qOWwCn4bM28gswHQb1D5r6AtKBRD8mKywVvWs7azguFVV3Fi8sspkBA2FBC\n" - b"At5p6ULoJOTL/TauzLl6djVJTCMM701WUDm2r+ZOIctXJ5bzP4n5Q4I7b0NMEL7u\n" - b"ixib4elYGr5D1vrVQAKtZHCr8gmkqyx8Mz7wkJepzBP9EeVzETCHsmiQDd5WYlO1\n" - b"C2IQYgw6MJzgM4entJ0V/GPytkodblGY95ORVK7ZhyNtda+r5BZ6/jeMW+hA3VoK\n" - b"tHSWjHt06ueVCCieZIATmYzBNt+zEz5UA2l7ksg3eWfVORJQS7a6Ef4VvbJLM9Ca\n" - b"m1kdsjelAoGBANKgvRf39i3bSuvm5VoyJuqinSb/23IH3Zo7XOZ5G164vh49E9Cq\n" - b"dOXXVxox74ppj/kbGUoOk+AvaB48zzfzNvac0a7lRHExykPH2kVrI/NwH/1OcT/x\n" - b"2e2DnFYocXcb4gbdZQ+m6X3zkxOYcONRzPVW1uMrFTWHcJveMUm4PGx7AoGBAMcU\n" - b"IRvrT6ye5se0s27gHnPweV+3xjsNtXZcK82N7duXyHmNjxrwOAv0SOhUmTkRXArM\n" - b"6aN5D8vyZBSWma2TgUKwpQYFTI+4Sp7sdkkyojGAEixJ+c5TZJNxZFrUe0FwAoic\n" - b"c2kb7ntaiEj5G+qHvykJJro5hy6uLnjiMVbAiJDTAoGAKb67241EmHAXGEwp9sdr\n" - b"2SMjnIAnQSF39UKAthkYqJxa6elXDQtLoeYdGE7/V+J2K3wIdhoPiuY6b4vD0iX9\n" - b"JcGM+WntN7YTjX2FsC588JmvbWfnoDHR7HYiPR1E58N597xXdFOzgUgORVr4PMWQ\n" - b"pqtwaZO3X2WZlvrhr+e46hMCgYBfdIdrm6jYXFjL6RkgUNZJQUTxYGzsY+ZemlNm\n" - b"fGdQo7a8kePMRuKY2MkcnXPaqTg49YgRmjq4z8CtHokRcWjJUWnPOTs8rmEZUshk\n" - b"0KJ0mbQdCFt/Uv0mtXgpFTkEZ3DPkDTGcV4oR4CRfOCl0/EU/A5VvL/U4i/mRo7h\n" - b"ye+xgQKBgD58b+9z+PR5LAJm1tZHIwb4tnyczP28PzwknxFd2qylR4ZNgvAUqGtU\n" - b"xvpUDpzMioz6zUH9YV43YNtt+5Xnzkqj+u9Mr27/H2v9XPwORGfwQ5XPwRJz/2oC\n" - b"EnPmP1SZoY9lXKUpQXHXSpDZ2rE2Klt3RHMUMHt8Zpy36E8Vwx8o\n" - b"-----END RSA PRIVATE KEY-----\n" -) - -_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( +_PRIVATE_WRAPPING_KEY_PEM = _PRIVATE_WRAPPING_KEY.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) +_PUBLIC_WRAPPING_KEY_PEM = _PUBLIC_WRAPPING_KEY.public_bytes( + encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo +) -_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( +_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD = _PRIVATE_WRAPPING_KEY_PEM + +_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD = _PRIVATE_WRAPPING_KEY.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), ) -_RAW_RSA_PUBLIC_KEY_PEM_ENCODED = ( - rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - .public_key() - .public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) -) +_RAW_RSA_PUBLIC_KEY_PEM_ENCODED = _PUBLIC_WRAPPING_KEY_PEM -_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( +_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD = _PRIVATE_WRAPPING_KEY.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) -_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( +_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD = _PRIVATE_WRAPPING_KEY.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), ) -_RAW_RSA_PUBLIC_KEY_DER_ENCODED = ( - rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - .public_key() - .public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) +_RAW_RSA_PUBLIC_KEY_DER_ENCODED = _PUBLIC_WRAPPING_KEY.public_bytes( + encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo ) @@ -148,18 +110,21 @@ def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): key_name=_KEY_ID, wrapping_algorithm=alg, private_wrapping_key=_PRIVATE_WRAPPING_KEY, + public_wrapping_key=_PUBLIC_WRAPPING_KEY, ) pem_and_der_encoded_raw_rsa_keyring = [ RawRSAKeyring.from_pem_encoding( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD, + public_encoded_key=_RAW_RSA_PUBLIC_KEY_PEM_ENCODED, wrapping_algorithm=_WRAPPING_ALGORITHM, ), RawRSAKeyring.from_pem_encoding( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD, + public_encoded_key=_RAW_RSA_PUBLIC_KEY_PEM_ENCODED, password=b"mypassword", wrapping_algorithm=_WRAPPING_ALGORITHM, ), @@ -173,12 +138,14 @@ def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD, + public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, wrapping_algorithm=_WRAPPING_ALGORITHM, ), RawRSAKeyring.from_der_encoding( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD, + public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, password=b"mypassword", wrapping_algorithm=_WRAPPING_ALGORITHM, ), @@ -186,7 +153,6 @@ def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): key_namespace=_PROVIDER_ID, key_name=_KEY_ID, public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, - password=b"mypassword", wrapping_algorithm=_WRAPPING_ALGORITHM, ), ] @@ -227,6 +193,7 @@ def test_raw_master_key_decrypts_what_raw_keyring_encrypts(encryption_materials_ key_name=_KEY_ID, wrapping_algorithm=_WRAPPING_ALGORITHM, private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, + public_encoded_key=_PUBLIC_WRAPPING_KEY_PEM, ) # Creating an instance of a raw master key @@ -272,6 +239,7 @@ def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_ key_name=_KEY_ID, wrapping_algorithm=_WRAPPING_ALGORITHM, private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, + public_encoded_key=_PUBLIC_WRAPPING_KEY_PEM, ) raw_mkp_generated_data_key = test_raw_master_key.generate_data_key( @@ -295,3 +263,111 @@ def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_ ) assert raw_mkp_generated_data_key.data_key == decryption_materials.data_encryption_key.data_key + + +def test_public_key_only_can_encrypt(): + test_keyring = RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + public_wrapping_key=_PUBLIC_WRAPPING_KEY, + ) + initial_materials = EncryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT + ) + + test_materials = test_keyring.on_encrypt(initial_materials) + + assert test_materials is not initial_materials + assert test_materials.data_encryption_key is not None + assert test_materials.encrypted_data_keys + + +def test_public_key_only_cannot_decrypt(): + test_keyring = RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + public_wrapping_key=_PUBLIC_WRAPPING_KEY, + ) + initial_materials = EncryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT + ) + + encryption_materials = test_keyring.on_encrypt(initial_materials) + + initial_decryption_materials = DecryptionMaterials( + algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT + ) + + test_materials = test_keyring.on_decrypt( + decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys + ) + + assert test_materials is initial_decryption_materials + + +def test_private_key_only_can_decrypt(): + complete_keyring = RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + private_wrapping_key=_PRIVATE_WRAPPING_KEY, + public_wrapping_key=_PUBLIC_WRAPPING_KEY, + ) + test_keyring = RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + private_wrapping_key=_PRIVATE_WRAPPING_KEY, + ) + initial_materials = EncryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT + ) + + encryption_materials = complete_keyring.on_encrypt(initial_materials) + + initial_decryption_materials = DecryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT + ) + + test_materials = test_keyring.on_decrypt( + decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys + ) + + assert test_materials is not initial_decryption_materials + assert test_materials.data_encryption_key is not None + + +def test_private_key_only_cannot_encrypt(): + test_keyring = RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + private_wrapping_key=_PRIVATE_WRAPPING_KEY, + ) + initial_materials = EncryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT + ) + + test_materials = test_keyring.on_encrypt(initial_materials) + + assert test_materials is initial_materials + assert test_materials.data_encryption_key is None + assert not test_materials.encrypted_data_keys + + +def test_keypair_must_match(): + wrapping_key_a = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + wrapping_key_b = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + + with pytest.raises(ValueError) as excinfo: + RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + private_wrapping_key=wrapping_key_a, + public_wrapping_key=wrapping_key_b.public_key(), + ) + + excinfo.match("Private and public wrapping keys MUST be from the same keypair.") diff --git a/test/functional/keyrings/test_multi.py b/test/functional/keyrings/test_multi.py index ff9eb2440..43833a41a 100644 --- a/test/functional/keyrings/test_multi.py +++ b/test/functional/keyrings/test_multi.py @@ -49,6 +49,8 @@ ], ) +_rsa_private_key_a = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) +_rsa_private_key_b = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) _MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN = MultiKeyring( generator=RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), children=[ @@ -56,17 +58,15 @@ key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), + private_wrapping_key=_rsa_private_key_a, + public_wrapping_key=_rsa_private_key_a.public_key(), ), RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), + private_wrapping_key=_rsa_private_key_b, + public_wrapping_key=_rsa_private_key_b.public_key(), ), ], ) @@ -76,7 +76,8 @@ key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()), + private_wrapping_key=_rsa_private_key_a, + public_wrapping_key=_rsa_private_key_a.public_key(), ) ) @@ -86,9 +87,8 @@ key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), + private_wrapping_key=_rsa_private_key_a, + public_wrapping_key=_rsa_private_key_a.public_key(), ), RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), ] diff --git a/test/unit/keyrings/raw/test_raw_rsa.py b/test/unit/keyrings/raw/test_raw_rsa.py index 5416ae24d..eb2082e4d 100644 --- a/test/unit/keyrings/raw/test_raw_rsa.py +++ b/test/unit/keyrings/raw/test_raw_rsa.py @@ -13,11 +13,12 @@ """Unit tests for Raw AES keyring.""" import pytest +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa import aws_encryption_sdk.key_providers.raw import aws_encryption_sdk.keyrings.raw -from aws_encryption_sdk.exceptions import EncryptKeyError from aws_encryption_sdk.identifiers import KeyringTraceFlag, WrappingAlgorithm from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.keyrings.base import Keyring @@ -45,11 +46,15 @@ @pytest.fixture def raw_rsa_keyring(): - return RawRSAKeyring.from_pem_encoding( + private_key = serialization.load_pem_private_key( + data=VALUES["private_rsa_key_bytes"][1], password=None, backend=default_backend() + ) + return RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_encoded_key=VALUES["private_rsa_key_bytes"][1], + private_wrapping_key=private_key, + public_wrapping_key=private_key.public_key(), ) @@ -145,10 +150,12 @@ def test_on_encrypt_when_data_encryption_key_given(raw_rsa_keyring, patch_genera def test_on_encrypt_no_public_key(raw_rsa_keyring): raw_rsa_keyring._public_wrapping_key = None - with pytest.raises(EncryptKeyError) as excinfo: - raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) + intial_materials = get_encryption_materials_without_data_encryption_key() + + test_materials = raw_rsa_keyring.on_encrypt(encryption_materials=intial_materials) - excinfo.match("Raw RSA keyring unable to encrypt data key: no public key available") + assert test_materials is intial_materials + assert intial_materials.data_encryption_key is None def test_on_encrypt_keyring_trace_when_data_encryption_key_given(raw_rsa_keyring): @@ -189,16 +196,6 @@ def test_on_encrypt_when_data_encryption_key_not_given(raw_rsa_keyring): assert len(test.encrypted_data_keys) == original_number_of_encrypted_data_keys + 1 -def test_on_encrypt_cannot_encrypt(raw_rsa_keyring, mocker): - encrypt_patch = mocker.patch.object(raw_rsa_keyring._public_wrapping_key, "encrypt") - encrypt_patch.side_effect = Exception("ENCRYPT FAIL") - - with pytest.raises(EncryptKeyError) as excinfo: - raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) - - excinfo.match("Raw RSA keyring unable to encrypt data key") - - def test_on_decrypt_when_data_key_given(raw_rsa_keyring, patch_decrypt_on_wrapping_key): test_raw_rsa_keyring = raw_rsa_keyring test_raw_rsa_keyring.on_decrypt( diff --git a/test/unit/unit_test_utils.py b/test/unit/unit_test_utils.py index d175f0b3e..bd6a9a82f 100644 --- a/test/unit/unit_test_utils.py +++ b/test/unit/unit_test_utils.py @@ -255,6 +255,7 @@ def get_decryption_materials_without_data_key(): def get_multi_keyring_with_generator_and_children(): + private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) return MultiKeyring( generator=RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), children=[ @@ -262,45 +263,43 @@ def get_multi_keyring_with_generator_and_children(): key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), + private_wrapping_key=private_key, + public_wrapping_key=private_key.public_key(), ), RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), + private_wrapping_key=private_key, + public_wrapping_key=private_key.public_key(), ), ], ) def get_multi_keyring_with_no_children(): + private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) return MultiKeyring( generator=RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), + private_wrapping_key=private_key, + public_wrapping_key=private_key.public_key(), ) ) def get_multi_keyring_with_no_generator(): + private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) return MultiKeyring( children=[ RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), + private_wrapping_key=private_key, + public_wrapping_key=private_key.public_key(), ), RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), ] @@ -410,12 +409,13 @@ def ephemeral_raw_rsa_master_key(size=4096): def ephemeral_raw_rsa_keyring(size=4096, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1): # type: (int, WrappingAlgorithm) -> RawRSAKeyring - key_bytes = _generate_rsa_key_bytes(size) - return RawRSAKeyring.from_pem_encoding( + private_key = rsa.generate_private_key(public_exponent=65537, key_size=size, backend=default_backend()) + return RawRSAKeyring( key_namespace="fake", key_name="rsa-{}".format(size).encode("utf-8"), wrapping_algorithm=wrapping_algorithm, - private_encoded_key=key_bytes, + private_wrapping_key=private_key, + public_wrapping_key=private_key.public_key(), ) From 57d95e77ba953565f08af7982d1504a0e64be413 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 23 Apr 2020 11:13:38 -0700 Subject: [PATCH 2/3] fix: raw RSA keyring must raise an error on encrypt if public key is not available --- src/aws_encryption_sdk/keyrings/raw.py | 2 +- test/functional/keyrings/raw/test_raw_rsa.py | 12 ++++++------ test/unit/keyrings/raw/test_raw_rsa.py | 17 ++++++++++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/aws_encryption_sdk/keyrings/raw.py b/src/aws_encryption_sdk/keyrings/raw.py index 72531fdbc..ddb07fb03 100644 --- a/src/aws_encryption_sdk/keyrings/raw.py +++ b/src/aws_encryption_sdk/keyrings/raw.py @@ -380,7 +380,7 @@ def on_encrypt(self, encryption_materials): new_materials = encryption_materials if self._public_wrapping_key is None: - return encryption_materials + raise EncryptKeyError("A public key is required to encrypt") if new_materials.data_encryption_key is None: new_materials = _generate_data_key(encryption_materials=new_materials, key_provider=self._key_provider) diff --git a/test/functional/keyrings/raw/test_raw_rsa.py b/test/functional/keyrings/raw/test_raw_rsa.py index ec785974d..f72ffee51 100644 --- a/test/functional/keyrings/raw/test_raw_rsa.py +++ b/test/functional/keyrings/raw/test_raw_rsa.py @@ -17,6 +17,7 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa +from aws_encryption_sdk.exceptions import EncryptKeyError from aws_encryption_sdk.identifiers import ( Algorithm, EncryptionKeyType, @@ -307,7 +308,7 @@ def test_public_key_only_cannot_decrypt(): assert test_materials is initial_decryption_materials -def test_private_key_only_can_decrypt(): +def test_private_key_can_decrypt(): complete_keyring = RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, @@ -339,7 +340,7 @@ def test_private_key_only_can_decrypt(): assert test_materials.data_encryption_key is not None -def test_private_key_only_cannot_encrypt(): +def test_private_key_cannot_encrypt(): test_keyring = RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, @@ -350,11 +351,10 @@ def test_private_key_only_cannot_encrypt(): algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT ) - test_materials = test_keyring.on_encrypt(initial_materials) + with pytest.raises(EncryptKeyError) as excinfo: + test_keyring.on_encrypt(initial_materials) - assert test_materials is initial_materials - assert test_materials.data_encryption_key is None - assert not test_materials.encrypted_data_keys + excinfo.match("A public key is required to encrypt") def test_keypair_must_match(): diff --git a/test/unit/keyrings/raw/test_raw_rsa.py b/test/unit/keyrings/raw/test_raw_rsa.py index eb2082e4d..55b91de92 100644 --- a/test/unit/keyrings/raw/test_raw_rsa.py +++ b/test/unit/keyrings/raw/test_raw_rsa.py @@ -19,6 +19,7 @@ import aws_encryption_sdk.key_providers.raw import aws_encryption_sdk.keyrings.raw +from aws_encryption_sdk.exceptions import EncryptKeyError from aws_encryption_sdk.identifiers import KeyringTraceFlag, WrappingAlgorithm from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.keyrings.base import Keyring @@ -148,14 +149,20 @@ def test_on_encrypt_when_data_encryption_key_given(raw_rsa_keyring, patch_genera def test_on_encrypt_no_public_key(raw_rsa_keyring): - raw_rsa_keyring._public_wrapping_key = None + private_key = raw_rsa_private_key() + test_keyring = RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=private_key, + ) - intial_materials = get_encryption_materials_without_data_encryption_key() + initial_materials = get_encryption_materials_without_data_encryption_key() - test_materials = raw_rsa_keyring.on_encrypt(encryption_materials=intial_materials) + with pytest.raises(EncryptKeyError) as excinfo: + test_keyring.on_encrypt(encryption_materials=initial_materials) - assert test_materials is intial_materials - assert intial_materials.data_encryption_key is None + excinfo.match("A public key is required to encrypt") def test_on_encrypt_keyring_trace_when_data_encryption_key_given(raw_rsa_keyring): From 0fdb3f25ee8e5b52c984cca44c46f47851793ee8 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 23 Apr 2020 12:50:50 -0700 Subject: [PATCH 3/3] docs: fix links in examples readme --- examples/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/README.md b/examples/README.md index 359a8a428..d592de09c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -49,9 +49,9 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * [with keyrings](./src/keyring/raw_aes/raw_aes.py) * [with master key providers](./src/master_key_provider/raw_aes/raw_aes.py) * How to use a raw RSA wrapping key - * [with keyrings](src/keyring/raw_rsa/keypair.py) + * [with keyrings](./src/keyring/raw_rsa/keypair.py) * How to use a raw RSA wrapping key when the key is PEM or DER encoded - * [with keyrings](src/keyring/raw_rsa/keypair_from_pem.py) + * [with keyrings](./src/keyring/raw_rsa/keypair_from_pem.py) * [with master key providers](./src/master_key_provider/raw_rsa/private_key_only_from_pem.py) * How to encrypt with a raw RSA public key wrapping key without access to the private key * [with keyrings](./src/keyring/raw_rsa/public_private_key_separate.py) @@ -62,9 +62,9 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * How to reuse data keys across multiple messages * [with the caching cryptographic materials manager](./src/crypto_materials_manager/caching/simple_cache.py) * How to restrict algorithm suites - * [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/algorithm_suite_enforcement.py) + * [with a custom cryptographic materials manager](./src/crypto_materials_manager/custom/algorithm_suite_enforcement.py) * How to require encryption context fields - * [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/requiring_encryption_context_fields.py) + * [with a custom cryptographic materials manager](./src/crypto_materials_manager/custom/requiring_encryption_context_fields.py) ### Keyrings