3434from dynamodb_encryption_sdk .encrypted .table import EncryptedTable
3535from dynamodb_encryption_sdk .identifiers import CryptoAction
3636from dynamodb_encryption_sdk .internal .identifiers import ReservedAttributes
37+ from dynamodb_encryption_sdk .material_providers import CryptographicMaterialsProvider
3738from dynamodb_encryption_sdk .material_providers .most_recent import MostRecentProvider
3839from dynamodb_encryption_sdk .material_providers .static import StaticCryptographicMaterialsProvider
3940from dynamodb_encryption_sdk .material_providers .store .meta import MetaStore
4041from dynamodb_encryption_sdk .material_providers .wrapped import WrappedCryptographicMaterialsProvider
42+ from dynamodb_encryption_sdk .materials import CryptographicMaterials
4143from dynamodb_encryption_sdk .materials .raw import RawDecryptionMaterials , RawEncryptionMaterials
42- from dynamodb_encryption_sdk .structures import AttributeActions
44+ from dynamodb_encryption_sdk .structures import AttributeActions , EncryptionContext
4345from dynamodb_encryption_sdk .transform import ddb_to_dict , dict_to_ddb
4446
4547RUNNING_IN_TRAVIS = "TRAVIS" in os .environ
@@ -164,6 +166,38 @@ def table_with_global_secondary_indexes():
164166 mock_dynamodb2 ().stop ()
165167
166168
169+ class PassThroughCryptographicMaterialsProviderThatRequiresAttributes (CryptographicMaterialsProvider ):
170+ """Cryptographic materials provider that passes through to another, but requires that attributes are set.
171+
172+ If the EncryptionContext passed to decryption_materials or encryption_materials
173+ ever does not have attributes set,
174+ a ValueError is raised.
175+ Otherwise, it passes through to the passthrough CMP normally.
176+ """
177+
178+ def __init__ (self , passthrough_cmp ):
179+ self ._passthrough_cmp = passthrough_cmp
180+
181+ def _assert_attributes_set (self , encryption_context ):
182+ # type: (EncryptionContext) -> None
183+ if not encryption_context .attributes :
184+ raise ValueError ("Encryption context attributes MUST be set!" )
185+
186+ def decryption_materials (self , encryption_context ):
187+ # type: (EncryptionContext) -> CryptographicMaterials
188+ self ._assert_attributes_set (encryption_context )
189+ return self ._passthrough_cmp .decryption_materials (encryption_context )
190+
191+ def encryption_materials (self , encryption_context ):
192+ # type: (EncryptionContext) -> CryptographicMaterials
193+ self ._assert_attributes_set (encryption_context )
194+ return self ._passthrough_cmp .encryption_materials (encryption_context )
195+
196+ def refresh (self ):
197+ # type: () -> None
198+ self ._passthrough_cmp .refresh ()
199+
200+
167201def _get_from_cache (dk_class , algorithm , key_length ):
168202 """Don't generate new keys every time. All we care about is that they are valid keys, not that they are unique."""
169203 try :
@@ -221,8 +255,15 @@ def _some_algorithm_pairs():
221255_cmp_builders = {"static" : build_static_jce_cmp , "wrapped" : _build_wrapped_jce_cmp }
222256
223257
224- def _all_possible_cmps (algorithm_generator ):
225- """Generate all possible cryptographic materials providers based on the supplied generator."""
258+ def _all_possible_cmps (algorithm_generator , require_attributes ):
259+ """Generate all possible cryptographic materials providers based on the supplied generator.
260+
261+ require_attributes determines whether the CMP will be wrapped in
262+ PassThroughCryptographicMaterialsProviderThatRequiresAttributes
263+ to require that attributes are set on every request.
264+ This should ONLY be disabled on the item encryptor tests.
265+ All high-level helper clients MUST set the attributes before passing the encryption context down.
266+ """
226267 # The AES combinations do the same thing, but this makes sure that the AESWrap name works as expected.
227268 yield _build_wrapped_jce_cmp ("AESWrap" , 256 , "HmacSHA256" , 256 )
228269
@@ -242,17 +283,31 @@ def _all_possible_cmps(algorithm_generator):
242283 sig_key_length = signing_key_length ,
243284 )
244285
286+ inner_cmp = builder_func (encryption_algorithm , encryption_key_length , signing_algorithm , signing_key_length )
287+
288+ if require_attributes :
289+ outer_cmp = PassThroughCryptographicMaterialsProviderThatRequiresAttributes (inner_cmp )
290+ else :
291+ outer_cmp = inner_cmp
292+
245293 yield pytest .param (
246- builder_func ( encryption_algorithm , encryption_key_length , signing_algorithm , signing_key_length ) ,
294+ outer_cmp ,
247295 id = id_string ,
248296 )
249297
250298
251- def set_parametrized_cmp (metafunc ):
252- """Set paramatrized values for cryptographic materials providers."""
299+ def set_parametrized_cmp (metafunc , require_attributes = True ):
300+ """Set paramatrized values for cryptographic materials providers.
301+
302+ require_attributes determines whether the CMP will be wrapped in
303+ PassThroughCryptographicMaterialsProviderThatRequiresAttributes
304+ to require that attributes are set on every request.
305+ This should ONLY be disabled on the item encryptor tests.
306+ All high-level helper clients MUST set the attributes before passing the encryption context down.
307+ """
253308 for name , algorithm_generator in (("all_the_cmps" , _all_algorithm_pairs ), ("some_cmps" , _some_algorithm_pairs )):
254309 if name in metafunc .fixturenames :
255- metafunc .parametrize (name , _all_possible_cmps (algorithm_generator ))
310+ metafunc .parametrize (name , _all_possible_cmps (algorithm_generator , require_attributes ))
256311
257312
258313_ACTIONS = {
0 commit comments