Skip to content

Commit 98d761b

Browse files
committed
properly handle items when only signing and no en/decryption keys are available
1 parent abcc934 commit 98d761b

File tree

3 files changed

+137
-27
lines changed

3 files changed

+137
-27
lines changed

src/dynamodb_encryption_sdk/encrypted/item.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -60,28 +60,38 @@ def encrypt_dynamodb_item(item, crypto_config):
6060
crypto_config.materials_provider.refresh()
6161
encryption_materials = crypto_config.encryption_materials()
6262

63-
# Add the attribute encryption mode to the inner material description
64-
# TODO: This is awkward...see if we can break this out any
65-
encryption_mode = MaterialDescriptionValues.CBC_PKCS5_ATTRIBUTE_ENCRYPTION.value
6663
inner_material_description = encryption_materials.material_description.copy()
67-
inner_material_description[
68-
MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value
69-
] = encryption_mode
64+
try:
65+
encryption_materials.encryption_key
66+
except AttributeError:
67+
if crypto_config.attribute_actions.contains_action(CryptoAction.ENCRYPT_AND_SIGN):
68+
raise EncryptionError(
69+
'Attribute actions ask for some attributes to be encrypted but no encryption key is available'
70+
)
71+
72+
encrypted_item = item.copy()
73+
else:
74+
# Add the attribute encryption mode to the inner material description
75+
# TODO: This is awkward...see if we can break this out any
76+
encryption_mode = MaterialDescriptionValues.CBC_PKCS5_ATTRIBUTE_ENCRYPTION.value
77+
inner_material_description[
78+
MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value
79+
] = encryption_mode
7080

71-
algorithm_descriptor = encryption_materials.encryption_key.algorithm + encryption_mode
81+
algorithm_descriptor = encryption_materials.encryption_key.algorithm + encryption_mode
7282

73-
encrypted_item = {}
74-
for name, attribute in item.items():
75-
if crypto_config.attribute_actions.action(name) is not CryptoAction.ENCRYPT_AND_SIGN:
76-
encrypted_item[name] = attribute.copy()
77-
continue
83+
encrypted_item = {}
84+
for name, attribute in item.items():
85+
if crypto_config.attribute_actions.action(name) is not CryptoAction.ENCRYPT_AND_SIGN:
86+
encrypted_item[name] = attribute.copy()
87+
continue
7888

79-
encrypted_item[name] = encrypt_attribute(
80-
attribute_name=name,
81-
attribute=attribute,
82-
encryption_key=encryption_materials.encryption_key,
83-
algorithm=algorithm_descriptor
84-
)
89+
encrypted_item[name] = encrypt_attribute(
90+
attribute_name=name,
91+
attribute=attribute,
92+
encryption_key=encryption_materials.encryption_key,
93+
algorithm=algorithm_descriptor
94+
)
8595

8696
signature_attribute = sign_item(encrypted_item, encryption_materials.signing_key, crypto_config)
8797
encrypted_item[ReservedAttributes.SIGNATURE.value] = signature_attribute
@@ -162,12 +172,22 @@ def decrypt_dynamodb_item(item, crypto_config):
162172

163173
decryption_materials = inner_crypto_config.decryption_materials()
164174

175+
verify_item_signature(signature_attribute, item, decryption_materials.verification_key, inner_crypto_config)
176+
177+
try:
178+
decryption_key = decryption_materials.decryption_key
179+
except AttributeError:
180+
if inner_crypto_config.attribute_actions.contains_action(CryptoAction.ENCRYPT_AND_SIGN):
181+
raise DecryptionError(
182+
'Attribute actions ask for some attributes to be decrypted but no decryption key is available'
183+
)
184+
185+
return item.copy()
186+
165187
decryption_mode = inner_crypto_config.encryption_context.material_description.get(
166188
MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value
167189
)
168-
algorithm_descriptor = decryption_materials.decryption_key.algorithm + decryption_mode
169-
170-
verify_item_signature(signature_attribute, item, decryption_materials.verification_key, inner_crypto_config)
190+
algorithm_descriptor = decryption_key.algorithm + decryption_mode
171191

172192
# Once the signature has been verified, actually decrypt the item attributes.
173193
decrypted_item = {}
@@ -179,7 +199,7 @@ def decrypt_dynamodb_item(item, crypto_config):
179199
decrypted_item[name] = decrypt_attribute(
180200
attribute_name=name,
181201
attribute=attribute,
182-
decryption_key=decryption_materials.decryption_key,
202+
decryption_key=decryption_key,
183203
algorithm=algorithm_descriptor
184204
)
185205
return decrypted_item

src/dynamodb_encryption_sdk/structures.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,19 @@ def __attrs_post_init__(self):
109109

110110
def action(self, attribute_name):
111111
# (text) -> CryptoAction
112-
"""Determines the correct CryptoAction to apply to a supplied attribute based on this config."""
112+
"""Determine the correct CryptoAction to apply to a supplied attribute based on this config."""
113113
return self.attribute_actions.get(attribute_name, self.default_action)
114114

115115
def copy(self):
116116
# () -> AttributeActions
117-
"""Returns a new copy of this object."""
117+
"""Return a new copy of this object."""
118118
return AttributeActions(
119119
default_action=self.default_action,
120120
attribute_actions=self.attribute_actions.copy()
121121
)
122122

123123
def set_index_keys(self, *keys):
124-
"""Sets the appropriate action for the specified indexed attribute names.
124+
"""Set the appropriate action for the specified indexed attribute names.
125125
126126
.. warning::
127127
@@ -145,9 +145,18 @@ def set_index_keys(self, *keys):
145145
except KeyError:
146146
self.attribute_actions[key] = index_action
147147

148+
def contains_action(self, action):
149+
# (CryptoAction) -> bool
150+
"""Determine if the specified action is a possible action from this configuration.
151+
152+
:param action: Action to look for
153+
:type action: dynamodb_encryption_sdk.identifiers.CryptoAction
154+
"""
155+
return action is self.default_action or action in self.attribute_actions.values()
156+
148157
def __add__(self, other):
149158
# (AttributeActions) -> AttributeActions
150-
"""Merges two AttributeActions objects into a new instance, applying the dominant
159+
"""Merge two AttributeActions objects into a new instance, applying the dominant
151160
action in each discovered case.
152161
"""
153162
default_action = self.default_action + other.default_action

test/functional/encrypted/test_item.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
import hypothesis
1515
import pytest
1616

17+
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
1718
from dynamodb_encryption_sdk.encrypted import CryptoConfig
1819
from dynamodb_encryption_sdk.encrypted.item import decrypt_python_item, encrypt_python_item
1920
from dynamodb_encryption_sdk.exceptions import DecryptionError, EncryptionError
20-
from dynamodb_encryption_sdk.internal.identifiers import ReservedAttributes
21+
from dynamodb_encryption_sdk.identifiers import CryptoAction
22+
from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys, ReservedAttributes
23+
from dynamodb_encryption_sdk.material_providers.static import StaticCryptographicMaterialsProvider
24+
from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials, RawEncryptionMaterials
2125
from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext
2226
from ..functional_test_utils import (
2327
build_static_jce_cmp, cycle_item_check, set_parametrized_actions, set_parametrized_cmp, set_parametrized_item
@@ -62,6 +66,83 @@ def test_reserved_attributes_on_encrypt(static_cmp_crypto_config, item):
6266
exc_info.match(r'Reserved attribute name *')
6367

6468

69+
def test_only_sign_item(parametrized_item):
70+
signing_key = JceNameLocalDelegatedKey.generate('HmacSHA256', 256)
71+
cmp = StaticCryptographicMaterialsProvider(
72+
encryption_materials=RawEncryptionMaterials(signing_key=signing_key),
73+
decryption_materials=RawDecryptionMaterials(verification_key=signing_key)
74+
)
75+
actions = AttributeActions(default_action=CryptoAction.SIGN_ONLY)
76+
crypto_config = CryptoConfig(
77+
materials_provider=cmp,
78+
encryption_context=EncryptionContext(),
79+
attribute_actions=actions
80+
)
81+
82+
signed_item = encrypt_python_item(parametrized_item, crypto_config)
83+
material_description = signed_item[ReservedAttributes.MATERIAL_DESCRIPTION.value].value
84+
assert MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value.encode('utf-8') not in material_description
85+
86+
decrypt_python_item(signed_item, crypto_config)
87+
88+
89+
@pytest.mark.parametrize('actions', (
90+
AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN),
91+
AttributeActions(default_action=CryptoAction.SIGN_ONLY, attribute_actions={'test': CryptoAction.ENCRYPT_AND_SIGN}),
92+
))
93+
def test_no_encryption_key_but_encryption_requested(actions, parametrized_item):
94+
signing_key = JceNameLocalDelegatedKey.generate('HmacSHA256', 256)
95+
cmp = StaticCryptographicMaterialsProvider(
96+
encryption_materials=RawEncryptionMaterials(signing_key=signing_key)
97+
)
98+
crypto_config = CryptoConfig(
99+
materials_provider=cmp,
100+
encryption_context=EncryptionContext(),
101+
attribute_actions=actions
102+
)
103+
104+
with pytest.raises(EncryptionError) as excinfo:
105+
encrypt_python_item(parametrized_item, crypto_config)
106+
107+
excinfo.match('Attribute actions ask for some attributes to be encrypted but no encryption key is available')
108+
109+
110+
@pytest.mark.parametrize('actions', (
111+
AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN),
112+
AttributeActions(default_action=CryptoAction.SIGN_ONLY, attribute_actions={'test': CryptoAction.ENCRYPT_AND_SIGN}),
113+
))
114+
def test_no_decryption_key_but_decryption_requested(actions, parametrized_item):
115+
encryption_key = JceNameLocalDelegatedKey.generate('AES', 256)
116+
signing_key = JceNameLocalDelegatedKey.generate('HmacSHA256', 256)
117+
encrypting_cmp = StaticCryptographicMaterialsProvider(
118+
encryption_materials=RawEncryptionMaterials(encryption_key=encryption_key, signing_key=signing_key)
119+
)
120+
decrypting_cmp = StaticCryptographicMaterialsProvider(
121+
decryption_materials=RawDecryptionMaterials(verification_key=signing_key)
122+
)
123+
124+
encrypted_item = encrypt_python_item(
125+
parametrized_item,
126+
CryptoConfig(
127+
materials_provider=encrypting_cmp,
128+
encryption_context=EncryptionContext(),
129+
attribute_actions=actions
130+
)
131+
)
132+
133+
with pytest.raises(DecryptionError) as excinfo:
134+
decrypt_python_item(
135+
encrypted_item,
136+
CryptoConfig(
137+
materials_provider=decrypting_cmp,
138+
encryption_context=EncryptionContext(),
139+
attribute_actions=actions
140+
)
141+
)
142+
143+
excinfo.match('Attribute actions ask for some attributes to be decrypted but no decryption key is available')
144+
145+
65146
def _item_cycle_check(materials_provider, attribute_actions, item):
66147
crypto_config = CryptoConfig(
67148
materials_provider=materials_provider,

0 commit comments

Comments
 (0)