Skip to content

Commit daaacba

Browse files
committed
add keyring trace and integrate into updated encrytion/decryption materials
1 parent a3d947d commit daaacba

File tree

5 files changed

+217
-41
lines changed

5 files changed

+217
-41
lines changed

src/aws_encryption_sdk/identifiers.py

+10
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,13 @@ class ContentAADString(Enum):
328328
FRAME_STRING_ID = b"AWSKMSEncryptionClient Frame"
329329
FINAL_FRAME_STRING_ID = b"AWSKMSEncryptionClient Final Frame"
330330
NON_FRAMED_STRING_ID = b"AWSKMSEncryptionClient Single Block"
331+
332+
333+
class KeyRingTraceFlag(Enum):
334+
"""KeyRing Trace actions."""
335+
336+
WRAPPING_KEY_GENERATED_DATA_KEY = 1
337+
WRAPPING_KEY_ENCRYPTED_DATA_KEY = 2
338+
WRAPPING_KEY_DECRYPTED_DATA_KEY = 3
339+
WRAPPING_KEY_SIGNED_ENC_CTX = 4
340+
WRAPPING_KEY_VERIFIED_ENC_CTX = 5

src/aws_encryption_sdk/materials_managers/__init__.py

+129-21
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616
"""
1717
import attr
1818
import six
19+
from attr.validators import deep_iterable, deep_mapping, instance_of, optional
1920

20-
from ..identifiers import Algorithm
21+
from ..identifiers import Algorithm, KeyRingTraceFlag
2122
from ..internal.utils.streams import ROStream
22-
from ..structures import DataKey
23+
from ..structures import DataKey, EncryptedDataKey, KeyRingTrace
2324

2425

2526
@attr.s(hash=False)
@@ -51,28 +52,87 @@ class EncryptionMaterialsRequest(object):
5152
)
5253

5354

54-
@attr.s(hash=False)
55-
class EncryptionMaterials(object):
55+
@attr.s
56+
class CryptographicMaterials(object):
57+
"""Cryptographic materials core.
58+
59+
.. versionadded:: 1.5.0
60+
61+
:param Algorithm algorithm: Algorithm to use for encrypting message
62+
:param dict encryption_context: Encryption context tied to `encrypted_data_keys`
63+
:param DataKey data_encryption_key: Plaintext data key to use for encrypting message
64+
:param encrypted_data_keys: List of encrypted data keys
65+
:type encrypted_data_keys: list of :class:`EncryptedDataKey`
66+
:param keyring_trace: Any KeyRing trace entries
67+
:type keyring_trace: list of :class:`KeyRingTrace`
68+
"""
69+
70+
algorithm = attr.ib(validator=optional(instance_of(Algorithm)))
71+
encryption_context = attr.ib(
72+
validator=optional(
73+
deep_mapping(key_validator=instance_of(six.string_types), value_validator=instance_of(six.string_types))
74+
)
75+
)
76+
data_encryption_key = attr.ib(default=None, validator=optional(instance_of(DataKey)))
77+
encrypted_data_keys = attr.ib(
78+
default=attr.Factory(list), validator=optional(deep_iterable(member_validator=instance_of(EncryptedDataKey)))
79+
)
80+
keyring_trace = attr.ib(
81+
default=attr.Factory(list), validator=optional(deep_iterable(member_validator=instance_of(KeyRingTrace)))
82+
)
83+
84+
85+
@attr.s(hash=False, init=False)
86+
class EncryptionMaterials(CryptographicMaterials):
5687
"""Encryption materials returned by a crypto material manager's `get_encryption_materials` method.
5788
5889
.. versionadded:: 1.3.0
5990
60-
:param algorithm: Algorithm to use for encrypting message
61-
:type algorithm: aws_encryption_sdk.identifiers.Algorithm
62-
:param data_encryption_key: Plaintext data key to use for encrypting message
63-
:type data_encryption_key: aws_encryption_sdk.structures.DataKey
64-
:param encrypted_data_keys: List of encrypted data keys
65-
:type encrypted_data_keys: list of `aws_encryption_sdk.structures.EncryptedDataKey`
91+
.. versionadded:: 1.5.0
92+
93+
The **keyring_trace** parameter.
94+
95+
.. versionadded:: 1.5.0
96+
97+
Most parameters are now optional.
98+
99+
:param Algorithm algorithm: Algorithm to use for encrypting message
100+
:param DataKey data_encryption_key: Plaintext data key to use for encrypting message (optional)
101+
:param encrypted_data_keys: List of encrypted data keys (optional)
102+
:type encrypted_data_keys: list of :class:`EncryptedDataKey`
66103
:param dict encryption_context: Encryption context tied to `encrypted_data_keys`
67-
:param bytes signing_key: Encoded signing key
104+
:param bytes signing_key: Encoded signing key (optional)
105+
:param keyring_trace: Any KeyRing trace entries (optional)
106+
:type keyring_trace: list of :class:`KeyRingTrace`
68107
"""
69108

70-
algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm))
71-
data_encryption_key = attr.ib(validator=attr.validators.instance_of(DataKey))
72-
encrypted_data_keys = attr.ib(validator=attr.validators.instance_of(set))
73-
encryption_context = attr.ib(validator=attr.validators.instance_of(dict))
74109
signing_key = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(bytes)))
75110

111+
def __init__(
112+
self,
113+
algorithm=None,
114+
data_encryption_key=None,
115+
encrypted_data_keys=None,
116+
encryption_context=None,
117+
signing_key=None,
118+
**kwargs
119+
):
120+
if algorithm is None:
121+
raise TypeError("algorithm must not be None")
122+
123+
if encryption_context is None:
124+
raise TypeError("encryption_context must not be None")
125+
126+
super(EncryptionMaterials, self).__init__(
127+
algorithm=algorithm,
128+
encryption_context=encryption_context,
129+
data_encryption_key=data_encryption_key,
130+
encrypted_data_keys=encrypted_data_keys,
131+
**kwargs
132+
)
133+
self.signing_key = signing_key
134+
attr.validate(self)
135+
76136

77137
@attr.s(hash=False)
78138
class DecryptionMaterialsRequest(object):
@@ -92,16 +152,64 @@ class DecryptionMaterialsRequest(object):
92152
encryption_context = attr.ib(validator=attr.validators.instance_of(dict))
93153

94154

95-
@attr.s(hash=False)
96-
class DecryptionMaterials(object):
155+
_DEFAULT_SENTINEL = object()
156+
157+
158+
@attr.s(hash=False, init=False)
159+
class DecryptionMaterials(CryptographicMaterials):
97160
"""Decryption materials returned by a crypto material manager's `decrypt_materials` method.
98161
99162
.. versionadded:: 1.3.0
100163
101-
:param data_key: Plaintext data key to use with message decryption
102-
:type data_key: aws_encryption_sdk.structures.DataKey
103-
:param bytes verification_key: Raw signature verification key
164+
.. versionadded:: 1.5.0
165+
166+
The **algorithm**, **data_encryption_key**, **encrypted_data_keys**,
167+
**encryption_context**, and **keyring_trace** parameters.
168+
169+
.. versionadded:: 1.5.0
170+
171+
All parameters are now optional.
172+
173+
:param Algorithm algorithm: Algorithm to use for encrypting message (optional)
174+
:param DataKey data_encryption_key: Plaintext data key to use for encrypting message (optional)
175+
:param encrypted_data_keys: List of encrypted data keys (optional)
176+
:type encrypted_data_keys: list of :class:`EncryptedDataKey`
177+
:param dict encryption_context: Encryption context tied to `encrypted_data_keys` (optional)
178+
:param bytes verification_key: Raw signature verification key (optional)
179+
:param keyring_trace: Any KeyRing trace entries (optional)
180+
:type keyring_trace: list of :class:`KeyRingTrace`
104181
"""
105182

106-
data_key = attr.ib(validator=attr.validators.instance_of(DataKey))
107183
verification_key = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(bytes)))
184+
185+
def __init__(self, data_key=_DEFAULT_SENTINEL, verification_key=None, **kwargs):
186+
if any(
187+
(
188+
data_key is _DEFAULT_SENTINEL and "data_encryption_key" not in kwargs,
189+
data_key is not _DEFAULT_SENTINEL and "data_encryption_key" in kwargs,
190+
)
191+
):
192+
raise TypeError("Exactly one of data_key or data_encryption_key must be set")
193+
194+
if data_key is not _DEFAULT_SENTINEL and "data_encryption_key" not in kwargs:
195+
kwargs["data_encryption_key"] = data_key
196+
197+
for legacy_missing in ("algorithm", "encryption_context"):
198+
if legacy_missing not in kwargs:
199+
kwargs[legacy_missing] = None
200+
201+
super(DecryptionMaterials, self).__init__(**kwargs)
202+
203+
self.verification_key = verification_key
204+
attr.validate(self)
205+
206+
@property
207+
def data_key(self):
208+
"""Backwards-compatible shim."""
209+
return self.data_encryption_key
210+
211+
@data_key.setter
212+
def data_key(self, value):
213+
# type: (DataKey) -> None
214+
"""Backwards-compatible shim."""
215+
self.data_encryption_key = value

src/aws_encryption_sdk/structures.py

+16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""Public data structures for aws_encryption_sdk."""
1414
import attr
1515
import six
16+
from attr.validators import deep_iterable, instance_of
1617

1718
import aws_encryption_sdk.identifiers
1819
from aws_encryption_sdk.internal.str_ops import to_bytes, to_str
@@ -104,3 +105,18 @@ class EncryptedDataKey(object):
104105

105106
key_provider = attr.ib(hash=True, validator=attr.validators.instance_of(MasterKeyInfo))
106107
encrypted_data_key = attr.ib(hash=True, validator=attr.validators.instance_of(bytes))
108+
109+
110+
@attr.s
111+
class KeyRingTrace(object):
112+
"""Record of all actions that a KeyRing performed with a wrapping key.
113+
114+
:param MasterKeyInfo wrapping_key: Wrapping key used
115+
:param flags: Actions performed
116+
:type flags: set of :class:`KeyRingTraceFlag`
117+
"""
118+
119+
wrapping_key = attr.ib(validator=instance_of(MasterKeyInfo))
120+
flags = attr.ib(
121+
validator=deep_iterable(member_validator=instance_of(aws_encryption_sdk.identifiers.KeyRingTraceFlag))
122+
)

test/unit/test_material_managers.py

+59-17
Original file line numberDiff line numberDiff line change
@@ -12,66 +12,93 @@
1212
# language governing permissions and limitations under the License.
1313
"""Test suite for aws_encryption_sdk.materials_managers"""
1414
import pytest
15-
from mock import MagicMock
15+
from mock import MagicMock, sentinel
1616
from pytest_mock import mocker # noqa pylint: disable=unused-import
1717

18-
from aws_encryption_sdk.identifiers import Algorithm
18+
from aws_encryption_sdk.identifiers import KeyRingTraceFlag
19+
from aws_encryption_sdk.internal.defaults import ALGORITHM
1920
from aws_encryption_sdk.internal.utils.streams import ROStream
2021
from aws_encryption_sdk.materials_managers import (
22+
CryptographicMaterials,
2123
DecryptionMaterials,
2224
DecryptionMaterialsRequest,
2325
EncryptionMaterials,
2426
EncryptionMaterialsRequest,
2527
)
26-
from aws_encryption_sdk.structures import DataKey
28+
from aws_encryption_sdk.structures import DataKey, KeyRingTrace, MasterKeyInfo
2729

2830
pytestmark = [pytest.mark.unit, pytest.mark.local]
2931

32+
_DATA_KEY = DataKey(
33+
key_provider=MasterKeyInfo(provider_id="Provider", key_info=b"Info"),
34+
data_key=b"1234567890123456789012",
35+
encrypted_data_key=b"asdf",
36+
)
3037

3138
_VALID_KWARGS = {
39+
"CryptographicMaterials": dict(
40+
algorithm=ALGORITHM,
41+
encryption_context={"additional": "data"},
42+
data_encryption_key=_DATA_KEY,
43+
encrypted_data_keys=[],
44+
keyring_trace=[
45+
KeyRingTrace(
46+
wrapping_key=MasterKeyInfo(provider_id="Provider", key_info=b"Info"),
47+
flags={KeyRingTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY},
48+
)
49+
],
50+
),
3251
"EncryptionMaterialsRequest": dict(
3352
encryption_context={},
3453
plaintext_rostream=MagicMock(__class__=ROStream),
3554
frame_length=5,
36-
algorithm=MagicMock(__class__=Algorithm),
55+
algorithm=ALGORITHM,
3756
plaintext_length=5,
3857
),
3958
"EncryptionMaterials": dict(
40-
algorithm=MagicMock(__class__=Algorithm),
41-
data_encryption_key=MagicMock(__class__=DataKey),
59+
algorithm=ALGORITHM,
60+
data_encryption_key=_DATA_KEY,
4261
encrypted_data_keys=set([]),
4362
encryption_context={},
4463
signing_key=b"",
4564
),
46-
"DecryptionMaterialsRequest": dict(
47-
algorithm=MagicMock(__class__=Algorithm), encrypted_data_keys=set([]), encryption_context={}
65+
"DecryptionMaterialsRequest": dict(algorithm=ALGORITHM, encrypted_data_keys=set([]), encryption_context={}),
66+
"DecryptionMaterials": dict(
67+
data_key=_DATA_KEY, verification_key=b"ex_verification_key", algorithm=ALGORITHM, encryption_context={}
4868
),
49-
"DecryptionMaterials": dict(data_key=MagicMock(__class__=DataKey), verification_key=b"ex_verification_key"),
5069
}
70+
_REMOVE = object()
5171

5272

5373
@pytest.mark.parametrize(
5474
"attr_class, invalid_kwargs",
5575
(
76+
(CryptographicMaterials, dict(algorithm=1234)),
77+
(CryptographicMaterials, dict(encryption_context=1234)),
78+
(CryptographicMaterials, dict(data_encryption_key=1234)),
79+
(CryptographicMaterials, dict(encrypted_data_keys=1234)),
80+
(CryptographicMaterials, dict(keyring_trace=1234)),
5681
(EncryptionMaterialsRequest, dict(encryption_context=None)),
5782
(EncryptionMaterialsRequest, dict(frame_length="not an int")),
5883
(EncryptionMaterialsRequest, dict(algorithm="not an Algorithm or None")),
5984
(EncryptionMaterialsRequest, dict(plaintext_length="not an int or None")),
6085
(EncryptionMaterials, dict(algorithm=None)),
61-
(EncryptionMaterials, dict(data_encryption_key=None)),
62-
(EncryptionMaterials, dict(encrypted_data_keys=None)),
6386
(EncryptionMaterials, dict(encryption_context=None)),
6487
(EncryptionMaterials, dict(signing_key=u"not bytes or None")),
6588
(DecryptionMaterialsRequest, dict(algorithm=None)),
6689
(DecryptionMaterialsRequest, dict(encrypted_data_keys=None)),
6790
(DecryptionMaterialsRequest, dict(encryption_context=None)),
68-
(DecryptionMaterials, dict(data_key=None)),
6991
(DecryptionMaterials, dict(verification_key=5555)),
92+
(DecryptionMaterials, dict(data_key=_DATA_KEY, data_encryption_key=_DATA_KEY)),
93+
(DecryptionMaterials, dict(data_key=_REMOVE, data_encryption_key=_REMOVE)),
7094
),
7195
)
7296
def test_attributes_fails(attr_class, invalid_kwargs):
7397
kwargs = _VALID_KWARGS[attr_class.__name__].copy()
7498
kwargs.update(invalid_kwargs)
99+
purge_keys = [key for key, val in kwargs.items() if val is _REMOVE]
100+
for key in purge_keys:
101+
del kwargs[key]
75102
with pytest.raises(TypeError):
76103
attr_class(**kwargs)
77104

@@ -85,14 +112,29 @@ def test_encryption_materials_request_attributes_defaults():
85112

86113
def test_encryption_materials_defaults():
87114
test = EncryptionMaterials(
88-
algorithm=MagicMock(__class__=Algorithm),
89-
data_encryption_key=MagicMock(__class__=DataKey),
90-
encrypted_data_keys=set([]),
91-
encryption_context={},
115+
algorithm=ALGORITHM, data_encryption_key=_DATA_KEY, encrypted_data_keys=set([]), encryption_context={}
92116
)
93117
assert test.signing_key is None
94118

95119

96120
def test_decryption_materials_defaults():
97-
test = DecryptionMaterials(data_key=MagicMock(__class__=DataKey))
121+
test = DecryptionMaterials(data_key=_DATA_KEY)
98122
assert test.verification_key is None
123+
assert test.algorithm is None
124+
assert test.encryption_context is None
125+
126+
127+
def test_decryption_materials_legacy_data_key_get():
128+
test = DecryptionMaterials(data_encryption_key=_DATA_KEY)
129+
130+
assert test.data_encryption_key is _DATA_KEY
131+
assert test.data_key is _DATA_KEY
132+
133+
134+
def test_decryption_materials_legacy_data_key_set():
135+
test = DecryptionMaterials(data_encryption_key=_DATA_KEY)
136+
137+
test.data_key = sentinel.data_key
138+
139+
assert test.data_encryption_key is sentinel.data_key
140+
assert test.data_key is sentinel.data_key

test/unit/test_material_managers_default.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from aws_encryption_sdk.key_providers.base import MasterKeyProvider
2323
from aws_encryption_sdk.materials_managers import EncryptionMaterials
2424
from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager
25-
from aws_encryption_sdk.structures import DataKey
25+
from aws_encryption_sdk.structures import DataKey, EncryptedDataKey
2626

2727
pytestmark = [pytest.mark.unit, pytest.mark.local]
2828

@@ -34,7 +34,7 @@ def patch_for_dcmm_encrypt(mocker):
3434
DefaultCryptoMaterialsManager._generate_signing_key_and_update_encryption_context.return_value = mock_signing_key
3535
mocker.patch.object(aws_encryption_sdk.materials_managers.default, "prepare_data_keys")
3636
mock_data_encryption_key = MagicMock(__class__=DataKey)
37-
mock_encrypted_data_keys = set([mock_data_encryption_key])
37+
mock_encrypted_data_keys = set([MagicMock(__class__=EncryptedDataKey)])
3838
result_pair = mock_data_encryption_key, mock_encrypted_data_keys
3939
aws_encryption_sdk.materials_managers.default.prepare_data_keys.return_value = result_pair
4040
yield result_pair, mock_signing_key
@@ -128,7 +128,7 @@ def test_get_encryption_materials(patch_for_dcmm_encrypt):
128128
assert isinstance(test, EncryptionMaterials)
129129
assert test.algorithm is cmm.algorithm
130130
assert test.data_encryption_key is patch_for_dcmm_encrypt[0][0]
131-
assert test.encrypted_data_keys is patch_for_dcmm_encrypt[0][1]
131+
assert test.encrypted_data_keys == patch_for_dcmm_encrypt[0][1]
132132
assert test.encryption_context == encryption_context
133133
assert test.signing_key == patch_for_dcmm_encrypt[1]
134134

0 commit comments

Comments
 (0)