Skip to content

Commit fe58bdb

Browse files
authored
AWS KMS test vectors and related fixes (aws#24)
* * add direct AWS KMS encrypted item test vectors * fix issues with AWS KMS CMP building KMS encryption context * fix issues with handling decimals
1 parent 978ae55 commit fe58bdb

17 files changed

+456
-64
lines changed

src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ def _transform_number_value(value):
9090
:rtype: dynamodb_encryption_sdk.internal.dynamodb_types.STRING
9191
"""
9292
raw_value = codecs.decode(value, TEXT_ENCODING)
93-
decimal_value = Decimal(to_str(raw_value))
94-
return str(decimal_value.normalize())
93+
decimal_value = Decimal(to_str(raw_value)).normalize()
94+
return '{0:f}'.format(decimal_value)
9595

9696
def _deserialize_number(stream):
9797
# type: (io.BytesIO) -> Dict[str, dynamodb_types.STRING]

src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,8 @@ def _transform_number_value(value):
7878
# by dynamodb.TypeSerializer, so all numbers are str. However, TypeSerializer
7979
# leaves trailing zeros if they are defined in the Decimal call, but we need to
8080
# strip all trailing zeros.
81-
decimal_value = DYNAMODB_CONTEXT.create_decimal(value)
82-
raw_value = '{:f}'.format(decimal_value.normalize())
83-
return to_bytes(raw_value)
81+
decimal_value = DYNAMODB_CONTEXT.create_decimal(value).normalize()
82+
return '{0:f}'.format(decimal_value).encode('utf-8')
8483

8584
def _serialize_number(_attribute):
8685
# type: (str) -> bytes

src/dynamodb_encryption_sdk/material_providers/aws_kms.py

+40-15
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,29 @@ class KeyInfo(object):
8282
algorithm = attr.ib(validator=attr.validators.instance_of(six.string_types))
8383
length = attr.ib(validator=attr.validators.instance_of(six.integer_types))
8484

85+
@classmethod
86+
def from_description(cls, description, default_key_length=None):
87+
# type: (Text) -> KeyInfo
88+
"""Load key info from key info description.
89+
90+
:param str description: Key info description
91+
:param int default_key_length: Key length to use if not found in description
92+
"""
93+
description_parts = description.split('/', 1)
94+
algorithm = description_parts[0]
95+
try:
96+
key_length = int(description_parts[1])
97+
except IndexError:
98+
if default_key_length is None:
99+
raise ValueError(
100+
'Description "{}" does not contain key length and no default key length is provided'.format(
101+
description
102+
)
103+
)
104+
105+
key_length = default_key_length
106+
return cls(description, algorithm, key_length)
107+
85108
@classmethod
86109
def from_material_description(cls, material_description, description_key, default_algorithm, default_key_length):
87110
# type: (Dict[Text, Text], Text, Text, int) -> KeyInfo
@@ -90,18 +113,12 @@ def from_material_description(cls, material_description, description_key, defaul
90113
:param dict material_description: Material description to read
91114
:param str description_key: Material description key containing desired key info description
92115
:param str default_algorithm: Algorithm name to use if not found in material description
93-
:param int default_key_length: Key length to use if not found in material description
116+
:param int default_key_length: Key length to use if not found in key info description
94117
:returns: Key info loaded from material description, with defaults applied if necessary
95118
:rtype: dynamodb_encryption_sdk.material_providers.aws_kms.KeyInfo
96119
"""
97120
description = material_description.get(description_key, default_algorithm)
98-
description_parts = description.split('/', 1)
99-
algorithm = description_parts[0]
100-
try:
101-
key_length = int(description_parts[1])
102-
except IndexError:
103-
key_length = default_key_length
104-
return cls(description, algorithm, key_length)
121+
return cls.from_description(description, default_key_length)
105122

106123

107124
@attr.s
@@ -234,7 +251,7 @@ def _attribute_to_value(self, attribute):
234251
attribute_type, attribute_value = list(attribute.items())[0]
235252
if attribute_type == 'B':
236253
return base64.b64encode(attribute_value.value).decode(TEXT_ENCODING)
237-
if attribute_type == 'S':
254+
if attribute_type in ('S', 'N'):
238255
return attribute_value
239256
raise ValueError('Attribute of type "{}" cannot be used in KMS encryption context.'.format(attribute_type))
240257

@@ -255,14 +272,22 @@ def _kms_encryption_context(self, encryption_context, encryption_description, si
255272
}
256273

257274
if encryption_context.partition_key_name is not None:
258-
partition_key_attribute = encryption_context.attributes.get(encryption_context.partition_key_name)
259-
kms_encryption_context[encryption_context.partition_key_name] = self._attribute_to_value(
260-
partition_key_attribute
261-
)
275+
try:
276+
partition_key_attribute = encryption_context.attributes[encryption_context.partition_key_name]
277+
except KeyError:
278+
pass
279+
else:
280+
kms_encryption_context[encryption_context.partition_key_name] = self._attribute_to_value(
281+
partition_key_attribute
282+
)
262283

263284
if encryption_context.sort_key_name is not None:
264-
sort_key_attribute = encryption_context.attributes.get(encryption_context.sort_key_name)
265-
kms_encryption_context[encryption_context.sort_key_name] = self._attribute_to_value(sort_key_attribute)
285+
try:
286+
sort_key_attribute = encryption_context.attributes[encryption_context.sort_key_name]
287+
except KeyError:
288+
pass
289+
else:
290+
kms_encryption_context[encryption_context.sort_key_name] = self._attribute_to_value(sort_key_attribute)
266291

267292
if encryption_context.table_name is not None:
268293
kms_encryption_context[_TABLE_NAME_EC_KEY] = encryption_context.table_name

test/acceptance/acceptance_test_utils.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
2424
from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes, KeyEncodingType
25+
from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider
2526
from dynamodb_encryption_sdk.material_providers.static import StaticCryptographicMaterialsProvider
2627
from dynamodb_encryption_sdk.material_providers.wrapped import WrappedCryptographicMaterialsProvider
2728
from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials
@@ -171,9 +172,15 @@ def _build_wrapped_cmp(decrypt_key, verify_key):
171172
)
172173

173174

175+
def _build_aws_kms_cmp(decrypt_key, verify_key):
176+
key_id = decrypt_key['keyId']
177+
return AwsKmsCryptographicMaterialsProvider(key_id=key_id)
178+
179+
174180
_CMP_TYPE_MAP = {
175181
'STATIC': _build_static_cmp,
176-
'WRAPPED': _build_wrapped_cmp
182+
'WRAPPED': _build_wrapped_cmp,
183+
'AWSKMS': _build_aws_kms_cmp
177184
}
178185

179186

test/acceptance/encrypted/test_item.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from dynamodb_encryption_sdk.structures import EncryptionContext
1919
from ..acceptance_test_utils import load_scenarios
2020

21-
pytestmark = [pytest.mark.acceptance, pytest.mark.local]
21+
pytestmark = [pytest.mark.acceptance, pytest.mark.integ]
2222

2323

2424
@pytest.mark.parametrize(
@@ -36,7 +36,8 @@ def test_item_encryptor(
3636
encryption_context = EncryptionContext(
3737
table_name=table_name,
3838
partition_key_name=table_index['partition'],
39-
sort_key_name=table_index.get('sort', None)
39+
sort_key_name=table_index.get('sort', None),
40+
attributes=ciphertext_item
4041
)
4142
crypto_config = CryptoConfig(
4243
materials_provider=materials_provider,

test/functional/functional_test_vector_generators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def _decode_string(_value):
4747
return _value
4848

4949
def _decode_number(_value):
50-
return str(Decimal(_value).normalize())
50+
return '{0:f}'.format(Decimal(_value))
5151

5252
def _decode_binary(_value):
5353
raw = base64.b64decode(_value)

0 commit comments

Comments
 (0)