Skip to content

Commit 8a27aea

Browse files
committed
add DefaultCryptographicMaterialsProvider tests for keyrings that return incomplete or broken materials
1 parent 87fed8a commit 8a27aea

File tree

2 files changed

+215
-16
lines changed

2 files changed

+215
-16
lines changed

test/unit/materials_managers/test_default.py

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,24 @@
1515
from mock import MagicMock, sentinel
1616

1717
import aws_encryption_sdk.materials_managers.default
18-
from aws_encryption_sdk.exceptions import MasterKeyProviderError, SerializationError
19-
from aws_encryption_sdk.identifiers import Algorithm
18+
from aws_encryption_sdk.exceptions import InvalidCryptographicMaterialsError, MasterKeyProviderError, SerializationError
19+
from aws_encryption_sdk.identifiers import Algorithm, WrappingAlgorithm
2020
from aws_encryption_sdk.internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY
2121
from aws_encryption_sdk.key_providers.base import MasterKeyProvider
22-
from aws_encryption_sdk.materials_managers import EncryptionMaterials
22+
from aws_encryption_sdk.materials_managers import (
23+
DecryptionMaterialsRequest,
24+
EncryptionMaterials,
25+
EncryptionMaterialsRequest,
26+
)
2327
from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager
2428
from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey
2529

26-
from ..unit_test_utils import ephemeral_raw_aes_keyring, ephemeral_raw_aes_master_key
30+
from ..unit_test_utils import (
31+
BrokenKeyring,
32+
OnlyGenerateKeyring,
33+
ephemeral_raw_aes_keyring,
34+
ephemeral_raw_aes_master_key,
35+
)
2736

2837
pytestmark = [pytest.mark.unit, pytest.mark.local]
2938

@@ -253,3 +262,80 @@ def test_decrypt_materials(mocker, patch_for_dcmm_decrypt):
253262
)
254263
assert test.data_key == RawDataKey.from_data_key(cmm.master_key_provider.decrypt_data_key_from_list.return_value)
255264
assert test.verification_key == patch_for_dcmm_decrypt
265+
266+
267+
def test_encrypt_with_keyring_materials_incomplete():
268+
raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING)
269+
270+
encrypt_cmm = DefaultCryptoMaterialsManager(keyring=OnlyGenerateKeyring(inner_keyring=raw_aes256_keyring))
271+
272+
encryption_materials_request = EncryptionMaterialsRequest(encryption_context={}, frame_length=1024)
273+
274+
with pytest.raises(InvalidCryptographicMaterialsError) as excinfo:
275+
encrypt_cmm.get_encryption_materials(encryption_materials_request)
276+
277+
excinfo.match("Encryption materials are incomplete!")
278+
279+
280+
def _broken_materials_scenarios():
281+
yield pytest.param(dict(break_algorithm=True), id="broken algorithm")
282+
yield pytest.param(dict(break_encryption_context=True), id="broken encryption context")
283+
yield pytest.param(dict(break_signing=True), id="broken signing/verification key")
284+
285+
286+
@pytest.mark.parametrize("kwargs", _broken_materials_scenarios())
287+
def test_encrypt_with_keyring_materials_do_not_match_request(kwargs):
288+
raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING)
289+
290+
encrypt_cmm = DefaultCryptoMaterialsManager(keyring=BrokenKeyring(inner_keyring=raw_aes256_keyring, **kwargs))
291+
292+
encryption_materials_request = EncryptionMaterialsRequest(encryption_context={}, frame_length=1024)
293+
294+
with pytest.raises(InvalidCryptographicMaterialsError) as excinfo:
295+
encrypt_cmm.get_encryption_materials(encryption_materials_request)
296+
297+
excinfo.match("Encryption materials do not match request!")
298+
299+
300+
def test_decrypt_with_keyring_materials_incomplete():
301+
raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING)
302+
raw_aes128_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING)
303+
304+
encrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes256_keyring)
305+
decrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes128_keyring)
306+
307+
encryption_materials_request = EncryptionMaterialsRequest(encryption_context={}, frame_length=1024)
308+
encryption_materials = encrypt_cmm.get_encryption_materials(encryption_materials_request)
309+
310+
decryption_materials_request = DecryptionMaterialsRequest(
311+
algorithm=encryption_materials.algorithm,
312+
encrypted_data_keys=encryption_materials.encrypted_data_keys,
313+
encryption_context=encryption_materials.encryption_context,
314+
)
315+
316+
with pytest.raises(InvalidCryptographicMaterialsError) as excinfo:
317+
decrypt_cmm.decrypt_materials(decryption_materials_request)
318+
319+
excinfo.match("Decryption materials are incomplete!")
320+
321+
322+
@pytest.mark.parametrize("kwargs", _broken_materials_scenarios())
323+
def test_decrypt_with_keyring_materials_do_not_match_request(kwargs):
324+
raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING)
325+
326+
encrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes256_keyring)
327+
decrypt_cmm = DefaultCryptoMaterialsManager(keyring=BrokenKeyring(inner_keyring=raw_aes256_keyring, **kwargs))
328+
329+
encryption_materials_request = EncryptionMaterialsRequest(encryption_context={}, frame_length=1024)
330+
encryption_materials = encrypt_cmm.get_encryption_materials(encryption_materials_request)
331+
332+
decryption_materials_request = DecryptionMaterialsRequest(
333+
algorithm=encryption_materials.algorithm,
334+
encrypted_data_keys=encryption_materials.encrypted_data_keys,
335+
encryption_context=encryption_materials.encryption_context,
336+
)
337+
338+
with pytest.raises(InvalidCryptographicMaterialsError) as excinfo:
339+
decrypt_cmm.decrypt_materials(decryption_materials_request)
340+
341+
excinfo.match("Decryption materials do not match request!")

test/unit/unit_test_utils.py

Lines changed: 125 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""Utility functions to handle common test framework functions."""
14+
import base64
1415
import copy
1516
import io
1617
import itertools
1718
import os
1819

20+
import attr
21+
from attr.validators import instance_of
1922
from cryptography.hazmat.backends import default_backend
2023
from cryptography.hazmat.primitives import serialization
2124
from cryptography.hazmat.primitives.asymmetric import rsa
2225

2326
from aws_encryption_sdk.exceptions import DecryptKeyError
24-
from aws_encryption_sdk.identifiers import Algorithm, EncryptionKeyType, KeyringTraceFlag, WrappingAlgorithm
27+
from aws_encryption_sdk.identifiers import AlgorithmSuite, EncryptionKeyType, KeyringTraceFlag, WrappingAlgorithm
2528
from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey
2629
from aws_encryption_sdk.internal.utils.streams import InsistentReaderBytesIO
2730
from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig
@@ -33,7 +36,7 @@
3336
from aws_encryption_sdk.structures import EncryptedDataKey, KeyringTrace, MasterKeyInfo, RawDataKey
3437

3538
try: # Python 3.5.0 and 3.5.1 have incompatible typing modules
36-
from typing import Iterable, Optional # noqa pylint: disable=unused-import
39+
from typing import Dict, Iterable, Optional # noqa pylint: disable=unused-import
3740
except ImportError: # pragma: no cover
3841
# We only actually need these imports when running the mypy checks
3942
pass
@@ -120,7 +123,7 @@ def on_decrypt(self, decryption_materials, encrypted_data_keys):
120123

121124
def get_encryption_materials_with_data_key():
122125
return EncryptionMaterials(
123-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
126+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
124127
data_encryption_key=RawDataKey(
125128
key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID),
126129
data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(',
@@ -138,7 +141,7 @@ def get_encryption_materials_with_data_key():
138141

139142
def get_encryption_materials_with_data_encryption_key():
140143
return EncryptionMaterials(
141-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
144+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
142145
data_encryption_key=RawDataKey(
143146
key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=b"5430b043-5843-4629-869c-64794af77ada"),
144147
data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(',
@@ -156,15 +159,15 @@ def get_encryption_materials_with_data_encryption_key():
156159

157160
def get_encryption_materials_without_data_key():
158161
return EncryptionMaterials(
159-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
162+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
160163
encryption_context=_ENCRYPTION_CONTEXT,
161164
signing_key=_SIGNING_KEY,
162165
)
163166

164167

165168
def get_encryption_materials_with_encrypted_data_key():
166169
return EncryptionMaterials(
167-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
170+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
168171
data_encryption_key=RawDataKey(
169172
key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID),
170173
data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(',
@@ -192,7 +195,7 @@ def get_encryption_materials_with_encrypted_data_key():
192195

193196
def get_encryption_materials_with_encrypted_data_key_aes():
194197
return EncryptionMaterials(
195-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
198+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
196199
data_encryption_key=RawDataKey(
197200
key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID),
198201
data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(',
@@ -214,23 +217,23 @@ def get_encryption_materials_with_encrypted_data_key_aes():
214217

215218
def get_encryption_materials_without_data_encryption_key():
216219
return EncryptionMaterials(
217-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
220+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
218221
encryption_context=_ENCRYPTION_CONTEXT,
219222
signing_key=_SIGNING_KEY,
220223
)
221224

222225

223226
def get_decryption_materials_without_data_encryption_key():
224227
return DecryptionMaterials(
225-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
228+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
226229
verification_key=b"ex_verification_key",
227230
encryption_context=_ENCRYPTION_CONTEXT,
228231
)
229232

230233

231234
def get_decryption_materials_with_data_key():
232235
return DecryptionMaterials(
233-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
236+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
234237
data_encryption_key=RawDataKey(
235238
key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID),
236239
data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(',
@@ -248,7 +251,7 @@ def get_decryption_materials_with_data_key():
248251

249252
def get_decryption_materials_with_data_encryption_key():
250253
return DecryptionMaterials(
251-
algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
254+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
252255
data_encryption_key=RawDataKey(
253256
key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=b"5430b043-5843-4629-869c-64794af77ada"),
254257
data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(',
@@ -509,7 +512,10 @@ class EphemeralRawMasterKeyProvider(RawMasterKeyProvider):
509512
provider_id = "fake"
510513

511514
def __init__(self):
512-
self.__keys = {b"aes-256": ephemeral_raw_aes_master_key(256), b"rsa-4096": ephemeral_raw_rsa_master_key(4096)}
515+
self.__keys = {
516+
b"aes-256": ephemeral_raw_aes_master_key(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING),
517+
b"rsa-4096": ephemeral_raw_rsa_master_key(4096),
518+
}
513519

514520
def _get_raw_key(self, key_id):
515521
return self.__keys[key_id].config.wrapping_key
@@ -548,3 +554,110 @@ class FailingDecryptMasterKeyProvider(EphemeralRawMasterKeyProvider):
548554

549555
def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context):
550556
raise DecryptKeyError("FailingDecryptMasterKeyProvider cannot decrypt!")
557+
558+
559+
@attr.s
560+
class BrokenKeyring(Keyring):
561+
"""Keyring that wraps another keyring and selectively breaks the returned values."""
562+
563+
_inner_keyring = attr.ib(validator=instance_of(Keyring))
564+
_break_algorithm = attr.ib(default=False, validator=instance_of(bool))
565+
_break_encryption_context = attr.ib(default=False, validator=instance_of(bool))
566+
_break_signing = attr.ib(default=False, validator=instance_of(bool))
567+
568+
@staticmethod
569+
def _random_string(bytes_len):
570+
# type: (int) -> str
571+
return base64.b64encode(os.urandom(bytes_len)).decode("utf-8")
572+
573+
def _broken_algorithm(self, algorithm):
574+
# type: (AlgorithmSuite) -> AlgorithmSuite
575+
if not self._break_algorithm:
576+
return algorithm
577+
578+
# We want to make sure that we return something different,
579+
# so find this suite in all suites and grab the next one,
580+
# whatever that is.
581+
all_suites = list(AlgorithmSuite)
582+
suite_index = all_suites.index(algorithm)
583+
next_index = (suite_index + 1) % (len(all_suites) - 1)
584+
585+
return all_suites[next_index]
586+
587+
def _broken_encryption_context(self, encryption_context):
588+
# type: (Dict[str, str]) -> Dict[str, str]
589+
broken_ec = encryption_context.copy()
590+
591+
if not self._break_encryption_context:
592+
return broken_ec
593+
594+
# Remove a random value
595+
try:
596+
broken_ec.popitem()
597+
except KeyError:
598+
pass
599+
600+
# add a random value
601+
broken_ec[self._random_string(5)] = self._random_string(10)
602+
603+
return broken_ec
604+
605+
def _broken_key(self, key):
606+
# type: (bytes) -> bytes
607+
if not self._break_signing:
608+
return key
609+
610+
return self._random_string(32).encode("utf-8")
611+
612+
def _break_encryption_materials(self, encryption_materials):
613+
# type: (EncryptionMaterials) -> EncryptionMaterials
614+
return EncryptionMaterials(
615+
algorithm=self._broken_algorithm(encryption_materials.algorithm),
616+
data_encryption_key=encryption_materials.data_encryption_key,
617+
encrypted_data_keys=encryption_materials.encrypted_data_keys,
618+
encryption_context=self._broken_encryption_context(encryption_materials.encryption_context),
619+
signing_key=self._broken_key(encryption_materials.signing_key),
620+
keyring_trace=encryption_materials.keyring_trace,
621+
)
622+
623+
def _break_decryption_materials(self, decryption_materials):
624+
# type: (DecryptionMaterials) -> DecryptionMaterials
625+
return DecryptionMaterials(
626+
algorithm=self._broken_algorithm(decryption_materials.algorithm),
627+
data_encryption_key=decryption_materials.data_encryption_key,
628+
encryption_context=self._broken_encryption_context(decryption_materials.encryption_context),
629+
verification_key=self._broken_key(decryption_materials.verification_key),
630+
keyring_trace=decryption_materials.keyring_trace,
631+
)
632+
633+
def on_encrypt(self, encryption_materials):
634+
# type: (EncryptionMaterials) -> EncryptionMaterials
635+
return self._break_encryption_materials(self._inner_keyring.on_encrypt(encryption_materials))
636+
637+
def on_decrypt(self, decryption_materials, encrypted_data_keys):
638+
# type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
639+
return self._break_decryption_materials(
640+
self._inner_keyring.on_decrypt(decryption_materials, encrypted_data_keys)
641+
)
642+
643+
644+
@attr.s
645+
class OnlyGenerateKeyring(Keyring):
646+
"""Keyring that wraps another keyring and removes any encrypted data keys."""
647+
648+
_inner_keyring = attr.ib(validator=instance_of(Keyring))
649+
650+
def on_encrypt(self, encryption_materials):
651+
# type: (EncryptionMaterials) -> EncryptionMaterials
652+
materials = self._inner_keyring.on_encrypt(encryption_materials)
653+
return EncryptionMaterials(
654+
algorithm=materials.algorithm,
655+
data_encryption_key=materials.data_encryption_key,
656+
encryption_context=materials.encryption_context,
657+
signing_key=materials.signing_key,
658+
keyring_trace=materials.keyring_trace,
659+
)
660+
661+
def on_decrypt(self, decryption_materials, encrypted_data_keys):
662+
# type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
663+
return self._inner_keyring.on_decrypt(decryption_materials, encrypted_data_keys)

0 commit comments

Comments
 (0)