Skip to content

Commit e90d9a4

Browse files
committed
pre-release update
1 parent 096d85e commit e90d9a4

30 files changed

+1209
-425
lines changed

aws_encryption_sdk/__init__.py

+30
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
def encrypt(**kwargs):
99
"""Encrypts and serializes provided plaintext.
1010
11+
.. note::
12+
When using this function, the entire ciphertext message is encrypted into memory before returning
13+
any data. If streaming is desired, see :class:`aws_encryption_sdk.stream`.
14+
15+
.. code:: python
16+
1117
>>> import aws_encryption_sdk
1218
>>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[
1319
... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222',
@@ -49,6 +55,12 @@ def encrypt(**kwargs):
4955
def decrypt(**kwargs):
5056
"""Deserializes and decrypts provided ciphertext.
5157
58+
.. note::
59+
When using this function, the entire ciphertext message is decrypted into memory before returning
60+
any data. If streaming is desired, see :class:`aws_encryption_sdk.stream`.
61+
62+
.. code:: python
63+
5264
>>> import aws_encryption_sdk
5365
>>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[
5466
... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222',
@@ -75,6 +87,8 @@ def decrypt(**kwargs):
7587
.. note::
7688
The concept of "lines" is used to match Python file-like-object terminology. In this
7789
context it defines the number of bytes returned by readline().
90+
:param int max_body_length: Maximum frame size (or content length for non-framed messages)
91+
in bytes to read from ciphertext message.
7892
:returns: Tuple containing the decrypted plaintext and the message header object
7993
:rtype: tuple of str and :class:`aws_encryption_sdk.structure.MessageHeader`
8094
"""
@@ -86,6 +100,22 @@ def decrypt(**kwargs):
86100
def stream(**kwargs):
87101
"""Provides an :py:func:`open`-like interface to the streaming encryptor/decryptor classes.
88102
103+
.. note::
104+
Take care when decrypting framed messages with large frame length and large non-framed
105+
messages. In order to protect the authenticity of the encrypted data, no plaintext
106+
is returned until it has been authenticated. Because of this, potentially large amounts
107+
of data may be read into memory. In the case of framed messages, the entire contents
108+
of each frame are read into memory and authenticated before returning any plaintext.
109+
In the case of non-framed messages, the entire message is read into memory and
110+
authenticated before returning any plaintext. The authenticated plaintext is held in
111+
memory until it is requested.
112+
113+
.. note::
114+
Consequently, keep the above decrypting consideration in mind when encrypting messages
115+
to ensure that issues are not encountered when decrypting those messages.
116+
117+
.. code:: python
118+
89119
>>> import aws_encryption_sdk
90120
>>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[
91121
... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222',

aws_encryption_sdk/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ class SerializationError(AWSEncryptionSDKClientError):
99
"""Exception class for serialization/deserialization errors."""
1010

1111

12+
class CustomMaximumValueExceeded(SerializationError):
13+
"""Exception class for use when values are found which exceed user-defined custom maximum values."""
14+
15+
1216
class UnknownIdentityError(AWSEncryptionSDKClientError):
1317
"""Exception class for unknown identity errors."""
1418

aws_encryption_sdk/identifiers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,4 @@ class ContentAADString(Enum):
197197
"""Body Additional Authenticated Data values for building the AAD for a message body."""
198198
FRAME_STRING_ID = b'AWSKMSEncryptionClient Frame'
199199
FINAL_FRAME_STRING_ID = b'AWSKMSEncryptionClient Final Frame'
200-
SINGLE_BLOCK_STRING_ID = b'AWSKMSEncryptionClient Single Block'
200+
NON_FRAMED_STRING_ID = b'AWSKMSEncryptionClient Single Block'

aws_encryption_sdk/internal/defaults.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
MAX_FRAME_COUNT = 4294967295 # 2 ** 32 - 1
2727
#: Maximum bytes allowed in a single frame as defined in specification
2828
MAX_FRAME_SIZE = 2147483647 # 2 ** 31 - 1
29-
#: Maximum bytes allowed in a single block message ciphertext as defined in specification
30-
MAX_SINGLE_BLOCK_SIZE = 68719476704 # 2 ** 36 - 32
29+
#: Maximum bytes allowed in a non-framed message ciphertext as defined in specification
30+
MAX_NON_FRAMED_SIZE = 68719476704 # 2 ** 36 - 32
3131

3232
#: Maximum number of AAD bytes allowed as defined in specification
3333
MAX_BYTE_ARRAY_SIZE = 65535 # 2 ** 16 - 1

aws_encryption_sdk/internal/formatting/deserialize.py

+31-15
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ def deserialize_header(stream):
175175
header['header_iv_length'] = iv_length
176176

177177
(frame_length,) = unpack_values('>I', stream)
178+
if content_type == ContentType.FRAMED_DATA and frame_length > aws_encryption_sdk.internal.defaults.MAX_FRAME_SIZE:
179+
raise SerializationError('Specified frame length larger than allowed maximum: {found} > {max}'.format(
180+
found=frame_length,
181+
max=aws_encryption_sdk.internal.defaults.MAX_FRAME_SIZE
182+
))
183+
elif content_type == ContentType.NO_FRAMING and frame_length != 0:
184+
raise SerializationError('Non-zero frame length found for non-framed message')
178185
header['frame_length'] = frame_length
179186

180187
return MessageHeader(**header)
@@ -200,8 +207,8 @@ def deserialize_header_auth(stream, algorithm, verifier=None):
200207
return MessageHeaderAuthentication(*unpack_values(format_string, stream, verifier))
201208

202209

203-
def deserialize_single_block_values(stream, header, verifier=None):
204-
"""Deserializes the IV and Tag from a single block stream.
210+
def deserialize_non_framed_values(stream, header, verifier=None):
211+
"""Deserializes the IV and Tag from a non-framed stream.
205212
206213
:param stream: Source data stream
207214
:type stream: io.BytesIO
@@ -212,7 +219,7 @@ def deserialize_single_block_values(stream, header, verifier=None):
212219
:returns: IV, Tag, and Data Length values for body
213220
:rtype: tuple of str, str, and int
214221
"""
215-
_LOGGER.debug('Starting single block body iv/tag deserialization')
222+
_LOGGER.debug('Starting non-framed body iv/tag deserialization')
216223
(data_iv, data_length) = unpack_values(
217224
'>{}sQ'.format(header.algorithm.iv_len),
218225
stream,
@@ -233,7 +240,7 @@ def update_verifier_with_tag(stream, header, verifier):
233240
"""Updates verifier with data for authentication tag.
234241
235242
.. note::
236-
This is meant to be used in conjunction with deserialize_single_block_values
243+
This is meant to be used in conjunction with deserialize_non_framed_values
237244
to update the verifier over information which has already been retrieved.
238245
239246
:param stream: Source data stream
@@ -284,6 +291,11 @@ def deserialize_frame(stream, header, verifier=None):
284291
frame_data['iv'] = frame_iv
285292
if final_frame is True:
286293
(content_length,) = unpack_values('>I', stream, verifier)
294+
if content_length >= header.frame_length:
295+
raise SerializationError('Invalid final frame length: {final} >= {normal}'.format(
296+
final=content_length,
297+
normal=header.frame_length
298+
))
287299
else:
288300
content_length = header.frame_length
289301
(frame_content, frame_tag) = unpack_values(
@@ -312,19 +324,19 @@ def deserialize_footer(stream, verifier=None):
312324
"""
313325
_LOGGER.debug('Starting footer deserialization')
314326
signature = b''
327+
if verifier is None:
328+
return MessageFooter(signature=signature)
315329
try:
316330
(sig_len,) = unpack_values('>H', stream)
317331
(signature,) = unpack_values(
318332
'>{sig_len}s'.format(sig_len=sig_len),
319333
stream
320334
)
321-
if verifier:
322-
verifier.set_signature(signature)
323-
verifier.verify()
324-
except struct.error:
325-
# If there is a struct error, assume that there is no footer to read.
326-
if verifier:
327-
raise SerializationError('No signature found in message')
335+
except SerializationError:
336+
raise SerializationError('No signature found in message')
337+
if verifier:
338+
verifier.set_signature(signature)
339+
verifier.verify()
328340
return MessageFooter(signature=signature)
329341

330342

@@ -340,10 +352,14 @@ def unpack_values(format_string, stream, verifier=None):
340352
:returns: Unpacked values
341353
:rtype: tuple
342354
"""
343-
message_bytes = stream.read(struct.calcsize(format_string))
344-
if verifier:
345-
verifier.update(message_bytes)
346-
return struct.unpack(format_string, message_bytes)
355+
try:
356+
message_bytes = stream.read(struct.calcsize(format_string))
357+
if verifier:
358+
verifier.update(message_bytes)
359+
values = struct.unpack(format_string, message_bytes)
360+
except struct.error as e:
361+
raise SerializationError('Unexpected deserialization error', type(e), e.args)
362+
return values
347363

348364

349365
def deserialize_wrapped_key(wrapping_algorithm, wrapping_key_id, wrapped_encrypted_key):

aws_encryption_sdk/internal/formatting/serialize.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def serialize_header_auth(algorithm, header, message_id, encryption_data_key, si
131131
return output
132132

133133

134-
def serialize_single_block_open(algorithm, iv, plaintext_length, signer=None):
134+
def serialize_non_framed_open(algorithm, iv, plaintext_length, signer=None):
135135
"""Serializes the opening block for a non-framed message body.
136136
137137
:param algorithm: Algorithm to use for encryption
@@ -160,7 +160,7 @@ def serialize_single_block_open(algorithm, iv, plaintext_length, signer=None):
160160
return body_start
161161

162162

163-
def serialize_single_block_close(tag, signer=None):
163+
def serialize_non_framed_close(tag, signer=None):
164164
"""Serializes the closing block for a non-framed message body.
165165
166166
:param bytes tag: Auth tag value from body encryptor

aws_encryption_sdk/internal/utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def get_aad_content_string(content_type, is_final_frame):
7272
:raises UnknownIdentityError: if unknown content type
7373
"""
7474
if content_type == ContentType.NO_FRAMING:
75-
aad_content_string = ContentAADString.SINGLE_BLOCK_STRING_ID
75+
aad_content_string = ContentAADString.NON_FRAMED_STRING_ID
7676
elif content_type == ContentType.FRAMED_DATA:
7777
if is_final_frame:
7878
aad_content_string = ContentAADString.FINAL_FRAME_STRING_ID

aws_encryption_sdk/key_providers/kms.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from aws_encryption_sdk.key_providers.base import (
1414
MasterKeyProvider, MasterKeyProviderConfig, MasterKey, MasterKeyConfig
1515
)
16-
from aws_encryption_sdk.structures import DataKey, EncryptedDataKey
16+
from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo
1717

1818
_LOGGER = logging.getLogger(__name__)
1919

@@ -203,10 +203,14 @@ def _generate_data_key(self, algorithm, encryption_context=None):
203203
response = self.config.client.generate_data_key(**kms_params)
204204
plaintext = response['Plaintext']
205205
ciphertext = response['CiphertextBlob']
206+
key_id = response['KeyId']
206207
except (ClientError, KeyError):
207208
raise GenerateKeyError('Master Key {key_id} unable to generate data key'.format(key_id=self._key_id))
208209
return DataKey(
209-
key_provider=self.key_provider,
210+
key_provider=MasterKeyInfo(
211+
provider_id=self.provider_id,
212+
key_info=key_id
213+
),
210214
data_key=plaintext,
211215
encrypted_data_key=ciphertext
212216
)
@@ -235,10 +239,14 @@ def _encrypt_data_key(self, data_key, algorithm, encryption_context=None):
235239
try:
236240
response = self.config.client.encrypt(**kms_params)
237241
ciphertext = response['CiphertextBlob']
242+
key_id = response['KeyId']
238243
except (ClientError, KeyError):
239244
raise EncryptKeyError('Master Key {key_id} unable to encrypt data key'.format(key_id=self._key_id))
240245
return EncryptedDataKey(
241-
key_provider=self.key_provider,
246+
key_provider=MasterKeyInfo(
247+
provider_id=self.provider_id,
248+
key_info=key_id
249+
),
242250
encrypted_data_key=ciphertext
243251
)
244252

0 commit comments

Comments
 (0)