Skip to content

Commit 6515bbf

Browse files
authored
Merge pull request #35 from mattsb42-aws/auth-only-cmp
Auth only CMP
2 parents 94998f9 + 9b41362 commit 6515bbf

File tree

6 files changed

+215
-42
lines changed

6 files changed

+215
-42
lines changed

src/dynamodb_encryption_sdk/encrypted/item.py

+52-33
Original file line numberDiff line numberDiff line change
@@ -60,28 +60,37 @@ 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
70-
71-
algorithm_descriptor = encryption_materials.encryption_key.algorithm + encryption_mode
72-
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
78-
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-
)
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
80+
81+
algorithm_descriptor = encryption_materials.encryption_key.algorithm + encryption_mode
82+
83+
encrypted_item = {}
84+
for name, attribute in item.items():
85+
if crypto_config.attribute_actions.action(name) is CryptoAction.ENCRYPT_AND_SIGN:
86+
encrypted_item[name] = encrypt_attribute(
87+
attribute_name=name,
88+
attribute=attribute,
89+
encryption_key=encryption_materials.encryption_key,
90+
algorithm=algorithm_descriptor
91+
)
92+
else:
93+
encrypted_item[name] = attribute.copy()
8594

8695
signature_attribute = sign_item(encrypted_item, encryption_materials.signing_key, crypto_config)
8796
encrypted_item[ReservedAttributes.SIGNATURE.value] = signature_attribute
@@ -162,26 +171,36 @@ def decrypt_dynamodb_item(item, crypto_config):
162171

163172
decryption_materials = inner_crypto_config.decryption_materials()
164173

174+
verify_item_signature(signature_attribute, item, decryption_materials.verification_key, inner_crypto_config)
175+
176+
try:
177+
decryption_key = decryption_materials.decryption_key
178+
except AttributeError:
179+
if inner_crypto_config.attribute_actions.contains_action(CryptoAction.ENCRYPT_AND_SIGN):
180+
raise DecryptionError(
181+
'Attribute actions ask for some attributes to be decrypted but no decryption key is available'
182+
)
183+
184+
return item.copy()
185+
165186
decryption_mode = inner_crypto_config.encryption_context.material_description.get(
166187
MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value
167188
)
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)
189+
algorithm_descriptor = decryption_key.algorithm + decryption_mode
171190

172191
# Once the signature has been verified, actually decrypt the item attributes.
173192
decrypted_item = {}
174193
for name, attribute in item.items():
175-
if inner_crypto_config.attribute_actions.action(name) is not CryptoAction.ENCRYPT_AND_SIGN:
194+
if inner_crypto_config.attribute_actions.action(name) is CryptoAction.ENCRYPT_AND_SIGN:
195+
decrypted_item[name] = decrypt_attribute(
196+
attribute_name=name,
197+
attribute=attribute,
198+
decryption_key=decryption_key,
199+
algorithm=algorithm_descriptor
200+
)
201+
else:
176202
decrypted_item[name] = attribute.copy()
177-
continue
178-
179-
decrypted_item[name] = decrypt_attribute(
180-
attribute_name=name,
181-
attribute=attribute,
182-
decryption_key=decryption_materials.decryption_key,
183-
algorithm=algorithm_descriptor
184-
)
203+
185204
return decrypted_item
186205

187206

src/dynamodb_encryption_sdk/materials/raw.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ class RawEncryptionMaterials(EncryptionMaterials):
5151
"""
5252

5353
_signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
54-
_encryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
54+
_encryption_key = attr.ib(
55+
validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)),
56+
default=None
57+
)
5558
_material_description = attr.ib(
5659
validator=dictionary_validator(six.string_types, six.string_types),
5760
converter=copy.deepcopy,
@@ -60,7 +63,7 @@ class RawEncryptionMaterials(EncryptionMaterials):
6063

6164
def __attrs_post_init__(self):
6265
"""Verify that the encryption key is allowed be used for raw materials."""
63-
if not self._encryption_key.allowed_for_raw_materials:
66+
if self._encryption_key is not None and not self._encryption_key.allowed_for_raw_materials:
6467
raise ValueError('Encryption key type "{}" does not allow use with RawEncryptionMaterials'.format(
6568
type(self._encryption_key)
6669
))
@@ -93,6 +96,9 @@ def encryption_key(self):
9396
:returns: Encryption key
9497
:rtype: dynamodb_encryption_sdk.delegated_keys.DelegatedKey
9598
"""
99+
if self._encryption_key is None:
100+
raise AttributeError('No encryption key available')
101+
96102
return self._encryption_key
97103

98104

@@ -113,7 +119,10 @@ class RawDecryptionMaterials(DecryptionMaterials):
113119
"""
114120

115121
_verification_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
116-
_decryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
122+
_decryption_key = attr.ib(
123+
validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)),
124+
default=None
125+
)
117126
_material_description = attr.ib(
118127
validator=dictionary_validator(six.string_types, six.string_types),
119128
converter=copy.deepcopy,
@@ -122,7 +131,7 @@ class RawDecryptionMaterials(DecryptionMaterials):
122131

123132
def __attrs_post_init__(self):
124133
"""Verify that the encryption key is allowed be used for raw materials."""
125-
if not self._decryption_key.allowed_for_raw_materials:
134+
if self._decryption_key is not None and not self._decryption_key.allowed_for_raw_materials:
126135
raise ValueError('Decryption key type "{}" does not allow use with RawDecryptionMaterials'.format(
127136
type(self._decryption_key)
128137
))
@@ -155,4 +164,7 @@ def decryption_key(self):
155164
:returns: Decryption key
156165
:rtype: dynamodb_encryption_sdk.delegated_keys.DelegatedKey
157166
"""
167+
if self._decryption_key is None:
168+
raise AttributeError('No decryption key available')
169+
158170
return self._decryption_key

src/dynamodb_encryption_sdk/structures.py

+13-4
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

+82-1
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,

test/functional/materials/__init__.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Dummy stub to make linters work better."""

test/functional/materials/test_raw.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Functional test suite for ``dynamodb_encryption_sdk.materials.raw``."""
14+
import pytest
15+
16+
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
17+
from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials, RawEncryptionMaterials
18+
19+
pytestmark = [pytest.mark.functional, pytest.mark.local]
20+
21+
22+
def test_no_encryption_key():
23+
signing_key = JceNameLocalDelegatedKey.generate('HmacSHA512', 256)
24+
encryption_materials = RawEncryptionMaterials(signing_key=signing_key)
25+
26+
with pytest.raises(AttributeError) as excinfo:
27+
encryption_materials.encryption_key
28+
29+
excinfo.match('No encryption key available')
30+
31+
32+
def test_no_decryption_key():
33+
verification_key = JceNameLocalDelegatedKey.generate('HmacSHA512', 256)
34+
decryption_materials = RawDecryptionMaterials(verification_key=verification_key)
35+
36+
with pytest.raises(AttributeError) as excinfo:
37+
decryption_materials.decryption_key
38+
39+
excinfo.match('No decryption key available')

0 commit comments

Comments
 (0)