Skip to content

Commit 810eff2

Browse files
committed
Merge branch 'keyring' into moar-examples
2 parents 8e44484 + 31e4e49 commit 810eff2

File tree

19 files changed

+329
-160
lines changed

19 files changed

+329
-160
lines changed

CHANGELOG.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22
Changelog
33
*********
44

5+
1.5.0 -- 2020-xx-xx
6+
===================
7+
8+
Major Features
9+
--------------
10+
11+
* Add `keyrings`_.
12+
* Change one-step APIs to return a :class:`CryptoResult` rather than a tuple.
13+
* Modified APIs: ``aws_encryption_sdk.encrypt`` and ``aws_encryption_sdk.decrypt``.
14+
15+
.. note::
16+
17+
For backwards compatibility,
18+
:class:`CryptoResult` also unpacks like a 2-member tuple.
19+
This allows for backwards compatibility with the previous outputs
20+
so this change should not break any existing consumers
21+
unless you are specifically relying on the output being an instance of :class:`tuple`.
22+
23+
524
1.4.1 -- 2019-09-20
625
===================
726

@@ -193,3 +212,4 @@ Minor
193212
.. _pylint: https://www.pylint.org/
194213
.. _flake8: http://flake8.pycqa.org/en/latest/
195214
.. _doc8: https://launchpad.net/doc8
215+
.. _keyrings: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html

src/aws_encryption_sdk/__init__.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
StreamDecryptor,
1515
StreamEncryptor,
1616
)
17+
from aws_encryption_sdk.structures import CryptoResult
18+
19+
__all__ = ("encrypt", "decrypt", "stream")
1720

1821

1922
def encrypt(**kwargs):
@@ -26,6 +29,13 @@ def encrypt(**kwargs):
2629
.. versionadded:: 1.5.0
2730
The *keyring* parameter.
2831
32+
.. versionadded:: 1.5.0
33+
34+
For backwards compatibility,
35+
the new :class:`CryptoResult` return value also unpacks like a 2-member tuple.
36+
This allows for backwards compatibility with the previous outputs
37+
so this change should not break any existing consumers.
38+
2939
.. code:: python
3040
3141
>>> import aws_encryption_sdk
@@ -67,12 +77,13 @@ def encrypt(**kwargs):
6777
:param algorithm: Algorithm to use for encryption
6878
:type algorithm: aws_encryption_sdk.identifiers.Algorithm
6979
:param int frame_length: Frame length in bytes
70-
:returns: Tuple containing the encrypted ciphertext and the message header object
71-
:rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader`
80+
:returns: Encrypted message, message metadata (header), and keyring trace
81+
:rtype: CryptoResult
7282
"""
7383
with StreamEncryptor(**kwargs) as encryptor:
7484
ciphertext = encryptor.read()
75-
return ciphertext, encryptor.header
85+
86+
return CryptoResult(result=ciphertext, header=encryptor.header, keyring_trace=encryptor.keyring_trace)
7687

7788

7889
def decrypt(**kwargs):
@@ -85,6 +96,13 @@ def decrypt(**kwargs):
8596
.. versionadded:: 1.5.0
8697
The *keyring* parameter.
8798
99+
.. versionadded:: 1.5.0
100+
101+
For backwards compatibility,
102+
the new :class:`CryptoResult` return value also unpacks like a 2-member tuple.
103+
This allows for backwards compatibility with the previous outputs
104+
so this change should not break any existing consumers.
105+
88106
.. code:: python
89107
90108
>>> import aws_encryption_sdk
@@ -117,12 +135,13 @@ def decrypt(**kwargs):
117135
118136
:param int max_body_length: Maximum frame size (or content length for non-framed messages)
119137
in bytes to read from ciphertext message.
120-
:returns: Tuple containing the decrypted plaintext and the message header object
121-
:rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader`
138+
:returns: Decrypted plaintext, message metadata (header), and keyring trace
139+
:rtype: CryptoResult
122140
"""
123141
with StreamDecryptor(**kwargs) as decryptor:
124142
plaintext = decryptor.read()
125-
return plaintext, decryptor.header
143+
144+
return CryptoResult(result=plaintext, header=decryptor.header, keyring_trace=decryptor.keyring_trace)
126145

127146

128147
def stream(**kwargs):
@@ -182,6 +201,3 @@ def stream(**kwargs):
182201
return _stream_map[mode.lower()](**kwargs)
183202
except KeyError:
184203
raise ValueError("Unsupported mode: {}".format(mode))
185-
186-
187-
__all__ = ("encrypt", "decrypt", "stream")

src/aws_encryption_sdk/identifiers.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import struct
1515
from enum import Enum
1616

17+
import attr
1718
from cryptography.hazmat.primitives import hashes
1819
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
1920
from cryptography.hazmat.primitives.ciphers import algorithms, modes
@@ -333,8 +334,22 @@ class ContentAADString(Enum):
333334
class KeyringTraceFlag(Enum):
334335
"""KeyRing Trace actions."""
335336

336-
WRAPPING_KEY_GENERATED_DATA_KEY = 1
337-
WRAPPING_KEY_ENCRYPTED_DATA_KEY = 1 << 1
338-
WRAPPING_KEY_DECRYPTED_DATA_KEY = 1 << 2
339-
WRAPPING_KEY_SIGNED_ENC_CTX = 1 << 3
340-
WRAPPING_KEY_VERIFIED_ENC_CTX = 1 << 4
337+
@attr.s
338+
class KeyringTraceFlagValue(object):
339+
"""Keyring trace flags do not have defined serializable values."""
340+
341+
name = attr.ib()
342+
343+
#: A flag to represent that a keyring has generated a plaintext data key.
344+
GENERATED_DATA_KEY = KeyringTraceFlagValue("GENERATED_DATA_KEY")
345+
#: A flag to represent that a keyring has created an encrypted data key.
346+
ENCRYPTED_DATA_KEY = KeyringTraceFlagValue("ENCRYPTED_DATA_KEY")
347+
#: A flag to represent that a keyring has obtained
348+
#: the corresponding plaintext data key from an encrypted data key.
349+
DECRYPTED_DATA_KEY = KeyringTraceFlagValue("DECRYPTED_DATA_KEY")
350+
#: A flag to represent that the keyring has cryptographically
351+
#: bound the encryption context to a newly created encrypted data key.
352+
SIGNED_ENCRYPTION_CONTEXT = KeyringTraceFlagValue("SIGNED_ENCRYPTION_CONTEXT")
353+
#: A flag to represent that the keyring has verified that an encrypted
354+
#: data key was originally created with a particular encryption context.
355+
VERIFIED_ENCRYPTION_CONTEXT = KeyringTraceFlagValue("VERIFIED_ENCRYPTION_CONTEXT")

src/aws_encryption_sdk/keyrings/aws_kms/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@
3434

3535
_LOGGER = logging.getLogger(__name__)
3636
_PROVIDER_ID = "aws-kms"
37-
_GENERATE_FLAGS = {KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY}
38-
_ENCRYPT_FLAGS = {KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY, KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX}
39-
_DECRYPT_FLAGS = {KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY, KeyringTraceFlag.WRAPPING_KEY_VERIFIED_ENC_CTX}
37+
_GENERATE_FLAGS = {KeyringTraceFlag.GENERATED_DATA_KEY}
38+
_ENCRYPT_FLAGS = {KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT}
39+
_DECRYPT_FLAGS = {KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT}
4040

4141

4242
@attr.s

src/aws_encryption_sdk/keyrings/raw.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def _generate_data_key(encryption_materials, key_provider):
5252
raise GenerateKeyError("Unable to generate data encryption key.")
5353

5454
# Create a keyring trace
55-
keyring_trace = KeyringTrace(wrapping_key=key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY})
55+
keyring_trace = KeyringTrace(wrapping_key=key_provider, flags={KeyringTraceFlag.GENERATED_DATA_KEY})
5656

5757
# plaintext_data_key to RawDataKey
5858
data_encryption_key = RawDataKey(key_provider=key_provider, data_key=plaintext_data_key)
@@ -147,7 +147,7 @@ def on_encrypt(self, encryption_materials):
147147

148148
# Update Keyring Trace
149149
keyring_trace = KeyringTrace(
150-
wrapping_key=encrypted_data_key.key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY}
150+
wrapping_key=encrypted_data_key.key_provider, flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY}
151151
)
152152

153153
# Add encrypted data key to encryption_materials
@@ -198,9 +198,7 @@ def on_decrypt(self, decryption_materials, encrypted_data_keys):
198198
return decryption_materials
199199

200200
# Create a keyring trace
201-
keyring_trace = KeyringTrace(
202-
wrapping_key=self._key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY}
203-
)
201+
keyring_trace = KeyringTrace(wrapping_key=self._key_provider, flags={KeyringTraceFlag.DECRYPTED_DATA_KEY})
204202

205203
# Update decryption materials
206204
data_encryption_key = RawDataKey(key_provider=self._key_provider, data_key=plaintext_data_key)
@@ -365,7 +363,7 @@ def on_encrypt(self, encryption_materials):
365363

366364
# Update Keyring Trace
367365
keyring_trace = KeyringTrace(
368-
wrapping_key=encrypted_data_key.key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY}
366+
wrapping_key=encrypted_data_key.key_provider, flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY}
369367
)
370368

371369
# Add encrypted data key to encryption_materials
@@ -406,9 +404,7 @@ def on_decrypt(self, decryption_materials, encrypted_data_keys):
406404
continue
407405

408406
# Create a keyring trace
409-
keyring_trace = KeyringTrace(
410-
wrapping_key=self._key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY}
411-
)
407+
keyring_trace = KeyringTrace(wrapping_key=self._key_provider, flags={KeyringTraceFlag.DECRYPTED_DATA_KEY})
412408

413409
# Update decryption materials
414410
data_encryption_key = RawDataKey(key_provider=self._key_provider, data_key=plaintext_data_key)

src/aws_encryption_sdk/materials_managers/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, KeyringTrace, RawDataKey
2626

2727
try: # Python 3.5.0 and 3.5.1 have incompatible typing modules
28-
from typing import Any, FrozenSet, Iterable, Tuple, Union # noqa pylint: disable=unused-import
28+
from typing import Any, Iterable, Tuple, Union # noqa pylint: disable=unused-import
2929
except ImportError: # pragma: no cover
3030
# We only actually need these imports when running the mypy checks
3131
pass
@@ -238,10 +238,10 @@ def __init__(
238238

239239
@property
240240
def encrypted_data_keys(self):
241-
# type: () -> FrozenSet[EncryptedDataKey]
241+
# type: () -> Tuple[EncryptedDataKey]
242242
"""Return a read-only version of the encrypted data keys.
243243
244-
:rtype: frozenset
244+
:rtype: Tuple[EncryptedDataKey]
245245
"""
246246
return tuple(self._encrypted_data_keys)
247247

@@ -280,7 +280,7 @@ def add_data_encryption_key(self, data_encryption_key, keyring_trace):
280280
self._add_data_encryption_key(
281281
data_encryption_key=data_encryption_key,
282282
keyring_trace=keyring_trace,
283-
required_flags={KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY},
283+
required_flags={KeyringTraceFlag.GENERATED_DATA_KEY},
284284
)
285285

286286
def add_encrypted_data_key(self, encrypted_data_key, keyring_trace):
@@ -299,7 +299,7 @@ def add_encrypted_data_key(self, encrypted_data_key, keyring_trace):
299299
if self.data_encryption_key is None:
300300
raise AttributeError("Data encryption key is not set.")
301301

302-
if KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY not in keyring_trace.flags:
302+
if KeyringTraceFlag.ENCRYPTED_DATA_KEY not in keyring_trace.flags:
303303
raise InvalidKeyringTraceError("Keyring flags do not match action.")
304304

305305
if keyring_trace.wrapping_key != encrypted_data_key.key_provider:
@@ -445,7 +445,7 @@ def add_data_encryption_key(self, data_encryption_key, keyring_trace):
445445
self._add_data_encryption_key(
446446
data_encryption_key=data_encryption_key,
447447
keyring_trace=keyring_trace,
448-
required_flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY},
448+
required_flags={KeyringTraceFlag.DECRYPTED_DATA_KEY},
449449
)
450450

451451
def add_verification_key(self, verification_key):

src/aws_encryption_sdk/streaming_client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class _EncryptionStream(io.IOBase):
137137
_message_prepped = None # type: bool
138138
source_stream = None
139139
_stream_length = None # type: int
140+
keyring_trace = ()
140141

141142
def __new__(cls, **kwargs):
142143
"""Perform necessary handling for _EncryptionStream instances that should be
@@ -443,6 +444,7 @@ def _prep_message(self):
443444
self._encryption_materials = self.config.materials_manager.get_encryption_materials(
444445
request=encryption_materials_request
445446
)
447+
self.keyring_trace = self._encryption_materials.keyring_trace
446448

447449
if self.config.algorithm is not None and self._encryption_materials.algorithm != self.config.algorithm:
448450
raise ActionNotAllowedError(
@@ -779,6 +781,8 @@ def _read_header(self):
779781
encryption_context=header.encryption_context,
780782
)
781783
decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request)
784+
self.keyring_trace = decryption_materials.keyring_trace
785+
782786
if decryption_materials.verification_key is None:
783787
self.verifier = None
784788
else:

src/aws_encryption_sdk/structures.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
from aws_encryption_sdk.identifiers import Algorithm, ContentType, KeyringTraceFlag, ObjectType, SerializationVersion
2121
from aws_encryption_sdk.internal.str_ops import to_bytes, to_str
2222

23+
try: # Python 3.5.0 and 3.5.1 have incompatible typing modules
24+
from typing import Tuple # noqa pylint: disable=unused-import
25+
except ImportError: # pragma: no cover
26+
# We only actually need these imports when running the mypy checks
27+
pass
28+
2329

2430
@attr.s(hash=True)
2531
class MasterKeyInfo(object):
@@ -107,8 +113,7 @@ class KeyringTrace(object):
107113
.. versionadded:: 1.5.0
108114
109115
:param MasterKeyInfo wrapping_key: Wrapping key used
110-
:param flags: Actions performed
111-
:type flags: set of :class:`KeyringTraceFlag`
116+
:param Set[KeyringTraceFlag] flags: Actions performed
112117
"""
113118

114119
wrapping_key = attr.ib(validator=instance_of(MasterKeyInfo))
@@ -120,19 +125,14 @@ class MessageHeader(object):
120125
# pylint: disable=too-many-instance-attributes
121126
"""Deserialized message header object.
122127
123-
:param version: Message format version, per spec
124-
:type version: SerializationVersion
125-
:param type: Message content type, per spec
126-
:type type: ObjectType
127-
:param algorithm: Algorithm to use for encryption
128-
:type algorithm: Algorithm
128+
:param SerializationVersion version: Message format version, per spec
129+
:param ObjectType type: Message content type, per spec
130+
:param AlgorithmSuite algorithm: Algorithm to use for encryption
129131
:param bytes message_id: Message ID
130-
:param dict encryption_context: Dictionary defining encryption context
131-
:param encrypted_data_keys: Encrypted data keys
132-
:type encrypted_data_keys: set of :class:`aws_encryption_sdk.structures.EncryptedDataKey`
133-
:param content_type: Message content framing type (framed/non-framed)
134-
:type content_type: ContentType
135-
:param bytes content_aad_length: empty
132+
:param Dict[str,str] encryption_context: Dictionary defining encryption context
133+
:param Sequence[EncryptedDataKey] encrypted_data_keys: Encrypted data keys
134+
:param ContentType content_type: Message content framing type (framed/non-framed)
135+
:param int content_aad_length: empty
136136
:param int header_iv_length: Bytes in Initialization Vector value found in header
137137
:param int frame_length: Length of message frame in bytes
138138
"""
@@ -152,3 +152,41 @@ class MessageHeader(object):
152152
content_aad_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
153153
header_iv_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
154154
frame_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
155+
156+
157+
@attr.s
158+
class CryptoResult(object):
159+
"""Result container for one-shot cryptographic API results.
160+
161+
.. versionadded:: 1.5.0
162+
163+
.. note::
164+
165+
For backwards compatibility,
166+
this container also unpacks like a 2-member tuple.
167+
This allows for backwards compatibility with the previous outputs.
168+
169+
:param bytes result: Binary results of the cryptographic operation
170+
:param MessageHeader header: Encrypted message metadata
171+
:param Tuple[KeyringTrace] keyring_trace: Keyring trace entries
172+
"""
173+
174+
result = attr.ib(validator=instance_of(bytes))
175+
header = attr.ib(validator=instance_of(MessageHeader))
176+
keyring_trace = attr.ib(validator=deep_iterable(member_validator=instance_of(KeyringTrace)))
177+
178+
def __attrs_post_init__(self):
179+
"""Construct the inner tuple for backwards compatibility."""
180+
self._legacy_container = (self.result, self.header)
181+
182+
def __len__(self):
183+
"""Emulate the inner tuple."""
184+
return self._legacy_container.__len__()
185+
186+
def __iter__(self):
187+
"""Emulate the inner tuple."""
188+
return self._legacy_container.__iter__()
189+
190+
def __getitem__(self, key):
191+
"""Emulate the inner tuple."""
192+
return self._legacy_container.__getitem__(key)

0 commit comments

Comments
 (0)