Skip to content

Commit f9999fc

Browse files
committed
feat: add CryptoResult and plumb in use
1 parent 9402826 commit f9999fc

File tree

5 files changed

+104
-17
lines changed

5 files changed

+104
-17
lines changed

CHANGELOG.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
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+
* Changed 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+
22+
523
1.4.1 -- 2019-09-20
624
===================
725

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/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: 44 additions & 0 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):
@@ -152,3 +158,41 @@ class MessageHeader(object):
152158
content_aad_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
153159
header_iv_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
154160
frame_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
161+
162+
163+
@attr.s
164+
class CryptoResult(object):
165+
"""Result container for one-shot cryptographic API results.
166+
167+
.. versionadded:: 1.5.0
168+
169+
.. note::
170+
171+
For backwards compatibility,
172+
this container also unpacks like a 2-member tuple.
173+
This allows for backwards compatibility with the previous outputs.
174+
175+
:param bytes result: Binary results of the cryptographic operation
176+
:param MessageHeader header: Encrypted message metadata
177+
:param Tuple[KeyringTrace] keyring_trace: Keyring trace entries
178+
"""
179+
180+
result = attr.ib(validator=instance_of(bytes))
181+
header = attr.ib(validator=instance_of(MessageHeader))
182+
keyring_trace = attr.ib(validator=deep_iterable(member_validator=instance_of(KeyringTrace)))
183+
184+
def __attrs_post_init__(self):
185+
"""Construct the inner tuple for backwards compatibility."""
186+
self._legacy_container = (self.result, self.header)
187+
188+
def __len__(self):
189+
"""Emulate the inner tuple."""
190+
return self._legacy_container.__len__()
191+
192+
def __iter__(self):
193+
"""Emulate the inner tuple."""
194+
return self._legacy_container.__iter__()
195+
196+
def __getitem__(self, key):
197+
"""Emulate the inner tuple."""
198+
return self._legacy_container.__getitem__(key)

test/unit/test_client.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
import aws_encryption_sdk
1818
import aws_encryption_sdk.internal.defaults
1919

20+
from .vectors import VALUES
21+
2022
pytestmark = [pytest.mark.unit, pytest.mark.local]
23+
_CIPHERTEXT = b"CIPHERTEXT"
24+
_PLAINTEXT = b"PLAINTEXT"
25+
_HEADER = VALUES["deserialized_header_frame"]
2126

2227

2328
class TestAwsEncryptionSdk(object):
@@ -27,16 +32,16 @@ def apply_fixtures(self):
2732
self.mock_stream_encryptor_patcher = patch("aws_encryption_sdk.StreamEncryptor")
2833
self.mock_stream_encryptor = self.mock_stream_encryptor_patcher.start()
2934
self.mock_stream_encryptor_instance = MagicMock()
30-
self.mock_stream_encryptor_instance.read.return_value = sentinel.ciphertext
31-
self.mock_stream_encryptor_instance.header = sentinel.header
35+
self.mock_stream_encryptor_instance.read.return_value = _CIPHERTEXT
36+
self.mock_stream_encryptor_instance.header = _HEADER
3237
self.mock_stream_encryptor.return_value = self.mock_stream_encryptor_instance
3338
self.mock_stream_encryptor_instance.__enter__.return_value = self.mock_stream_encryptor_instance
3439
# Set up StreamDecryptor patch
3540
self.mock_stream_decryptor_patcher = patch("aws_encryption_sdk.StreamDecryptor")
3641
self.mock_stream_decryptor = self.mock_stream_decryptor_patcher.start()
3742
self.mock_stream_decryptor_instance = MagicMock()
38-
self.mock_stream_decryptor_instance.read.return_value = sentinel.plaintext
39-
self.mock_stream_decryptor_instance.header = sentinel.header
43+
self.mock_stream_decryptor_instance.read.return_value = _PLAINTEXT
44+
self.mock_stream_decryptor_instance.header = _HEADER
4045
self.mock_stream_decryptor.return_value = self.mock_stream_decryptor_instance
4146
self.mock_stream_decryptor_instance.__enter__.return_value = self.mock_stream_decryptor_instance
4247
yield
@@ -47,14 +52,14 @@ def apply_fixtures(self):
4752
def test_encrypt(self):
4853
test_ciphertext, test_header = aws_encryption_sdk.encrypt(a=sentinel.a, b=sentinel.b, c=sentinel.b)
4954
self.mock_stream_encryptor.called_once_with(a=sentinel.a, b=sentinel.b, c=sentinel.b)
50-
assert test_ciphertext is sentinel.ciphertext
51-
assert test_header is sentinel.header
55+
assert test_ciphertext is _CIPHERTEXT
56+
assert test_header is _HEADER
5257

5358
def test_decrypt(self):
5459
test_plaintext, test_header = aws_encryption_sdk.decrypt(a=sentinel.a, b=sentinel.b, c=sentinel.b)
5560
self.mock_stream_encryptor.called_once_with(a=sentinel.a, b=sentinel.b, c=sentinel.b)
56-
assert test_plaintext is sentinel.plaintext
57-
assert test_header is sentinel.header
61+
assert test_plaintext is _PLAINTEXT
62+
assert test_header is _HEADER
5863

5964
def test_stream_encryptor_e(self):
6065
test = aws_encryption_sdk.stream(mode="e", a=sentinel.a, b=sentinel.b, c=sentinel.b)

0 commit comments

Comments
 (0)