Skip to content

Expose keyring trace in results #224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@
Changelog
*********

1.5.0 -- 2020-xx-xx
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this will be updated once it's shipped.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. That's out standard placeholder for "this release hasn't shipped yet".

===================

Major Features
--------------

* Add `keyrings`_.
* Change one-step APIs to return a :class:`CryptoResult` rather than a tuple.
* Modified APIs: ``aws_encryption_sdk.encrypt`` and ``aws_encryption_sdk.decrypt``.

.. note::

For backwards compatibility,
:class:`CryptoResult` also unpacks like a 2-member tuple.
This allows for backwards compatibility with the previous outputs
so this change should not break any existing consumers
unless you are specifically relying on the output being an instance of :class:`tuple`.


1.4.1 -- 2019-09-20
===================

Expand Down Expand Up @@ -193,3 +212,4 @@ Minor
.. _pylint: https://www.pylint.org/
.. _flake8: http://flake8.pycqa.org/en/latest/
.. _doc8: https://launchpad.net/doc8
.. _keyrings: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html
34 changes: 25 additions & 9 deletions src/aws_encryption_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
StreamDecryptor,
StreamEncryptor,
)
from aws_encryption_sdk.structures import CryptoResult

__all__ = ("encrypt", "decrypt", "stream")


def encrypt(**kwargs):
Expand All @@ -26,6 +29,13 @@ def encrypt(**kwargs):
.. versionadded:: 1.5.0
The *keyring* parameter.

.. versionadded:: 1.5.0

For backwards compatibility,
the new :class:`CryptoResult` return value also unpacks like a 2-member tuple.
This allows for backwards compatibility with the previous outputs
so this change should not break any existing consumers.

.. code:: python

>>> import aws_encryption_sdk
Expand Down Expand Up @@ -67,12 +77,13 @@ def encrypt(**kwargs):
:param algorithm: Algorithm to use for encryption
:type algorithm: aws_encryption_sdk.identifiers.Algorithm
:param int frame_length: Frame length in bytes
:returns: Tuple containing the encrypted ciphertext and the message header object
:rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader`
:returns: Encrypted message, message metadata (header), and keyring trace
:rtype: CryptoResult
"""
with StreamEncryptor(**kwargs) as encryptor:
ciphertext = encryptor.read()
return ciphertext, encryptor.header

return CryptoResult(result=ciphertext, header=encryptor.header, keyring_trace=encryptor.keyring_trace)


def decrypt(**kwargs):
Expand All @@ -85,6 +96,13 @@ def decrypt(**kwargs):
.. versionadded:: 1.5.0
The *keyring* parameter.

.. versionadded:: 1.5.0

For backwards compatibility,
the new :class:`CryptoResult` return value also unpacks like a 2-member tuple.
This allows for backwards compatibility with the previous outputs
so this change should not break any existing consumers.

.. code:: python

>>> import aws_encryption_sdk
Expand Down Expand Up @@ -117,12 +135,13 @@ def decrypt(**kwargs):

:param int max_body_length: Maximum frame size (or content length for non-framed messages)
in bytes to read from ciphertext message.
:returns: Tuple containing the decrypted plaintext and the message header object
:rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader`
:returns: Decrypted plaintext, message metadata (header), and keyring trace
:rtype: CryptoResult
"""
with StreamDecryptor(**kwargs) as decryptor:
plaintext = decryptor.read()
return plaintext, decryptor.header

return CryptoResult(result=plaintext, header=decryptor.header, keyring_trace=decryptor.keyring_trace)


def stream(**kwargs):
Expand Down Expand Up @@ -182,6 +201,3 @@ def stream(**kwargs):
return _stream_map[mode.lower()](**kwargs)
except KeyError:
raise ValueError("Unsupported mode: {}".format(mode))


__all__ = ("encrypt", "decrypt", "stream")
6 changes: 3 additions & 3 deletions src/aws_encryption_sdk/materials_managers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, KeyringTrace, RawDataKey

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

@property
def encrypted_data_keys(self):
# type: () -> FrozenSet[EncryptedDataKey]
# type: () -> Tuple[EncryptedDataKey]
"""Return a read-only version of the encrypted data keys.

:rtype: frozenset
:rtype: Tuple[EncryptedDataKey]
"""
return tuple(self._encrypted_data_keys)

Expand Down
4 changes: 4 additions & 0 deletions src/aws_encryption_sdk/streaming_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class _EncryptionStream(io.IOBase):
_message_prepped = None # type: bool
source_stream = None
_stream_length = None # type: int
keyring_trace = ()

def __new__(cls, **kwargs):
"""Perform necessary handling for _EncryptionStream instances that should be
Expand Down Expand Up @@ -443,6 +444,7 @@ def _prep_message(self):
self._encryption_materials = self.config.materials_manager.get_encryption_materials(
request=encryption_materials_request
)
self.keyring_trace = self._encryption_materials.keyring_trace

if self.config.algorithm is not None and self._encryption_materials.algorithm != self.config.algorithm:
raise ActionNotAllowedError(
Expand Down Expand Up @@ -779,6 +781,8 @@ def _read_header(self):
encryption_context=header.encryption_context,
)
decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request)
self.keyring_trace = decryption_materials.keyring_trace

if decryption_materials.verification_key is None:
self.verifier = None
else:
Expand Down
66 changes: 52 additions & 14 deletions src/aws_encryption_sdk/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
from aws_encryption_sdk.identifiers import Algorithm, ContentType, KeyringTraceFlag, ObjectType, SerializationVersion
from aws_encryption_sdk.internal.str_ops import to_bytes, to_str

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


@attr.s(hash=True)
class MasterKeyInfo(object):
Expand Down Expand Up @@ -107,8 +113,7 @@ class KeyringTrace(object):
.. versionadded:: 1.5.0

:param MasterKeyInfo wrapping_key: Wrapping key used
:param flags: Actions performed
:type flags: set of :class:`KeyringTraceFlag`
:param Set[KeyringTraceFlag] flags: Actions performed
"""

wrapping_key = attr.ib(validator=instance_of(MasterKeyInfo))
Expand All @@ -120,19 +125,14 @@ class MessageHeader(object):
# pylint: disable=too-many-instance-attributes
"""Deserialized message header object.

:param version: Message format version, per spec
:type version: SerializationVersion
:param type: Message content type, per spec
:type type: ObjectType
:param algorithm: Algorithm to use for encryption
:type algorithm: Algorithm
:param SerializationVersion version: Message format version, per spec
:param ObjectType type: Message content type, per spec
:param AlgorithmSuite algorithm: Algorithm to use for encryption
:param bytes message_id: Message ID
:param dict encryption_context: Dictionary defining encryption context
:param encrypted_data_keys: Encrypted data keys
:type encrypted_data_keys: set of :class:`aws_encryption_sdk.structures.EncryptedDataKey`
:param content_type: Message content framing type (framed/non-framed)
:type content_type: ContentType
:param bytes content_aad_length: empty
:param Dict[str,str] encryption_context: Dictionary defining encryption context
:param Sequence[EncryptedDataKey] encrypted_data_keys: Encrypted data keys
:param ContentType content_type: Message content framing type (framed/non-framed)
:param int content_aad_length: empty
:param int header_iv_length: Bytes in Initialization Vector value found in header
:param int frame_length: Length of message frame in bytes
"""
Expand All @@ -152,3 +152,41 @@ class MessageHeader(object):
content_aad_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
header_iv_length = attr.ib(hash=True, validator=instance_of(six.integer_types))
frame_length = attr.ib(hash=True, validator=instance_of(six.integer_types))


@attr.s
class CryptoResult(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any value in calling this AwsCryptoResult to match Java?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO no, but I'm willing to be convinced otherwise.

My reasoning is that aside from the name/branding and the AWS KMS keyring, nothing about the ESDK is AWS-specific. As such, I've tried to avoid including "AWS" in the names of things except where they specifically relate to AWS (ex: keyrings.aws_kms).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm fine with keeping it. As you know we changed the name more out of necessity than because it was a better name.

"""Result container for one-shot cryptographic API results.

.. versionadded:: 1.5.0

.. note::

For backwards compatibility,
this container also unpacks like a 2-member tuple.
This allows for backwards compatibility with the previous outputs.

:param bytes result: Binary results of the cryptographic operation
:param MessageHeader header: Encrypted message metadata
:param Tuple[KeyringTrace] keyring_trace: Keyring trace entries
"""

result = attr.ib(validator=instance_of(bytes))
header = attr.ib(validator=instance_of(MessageHeader))
keyring_trace = attr.ib(validator=deep_iterable(member_validator=instance_of(KeyringTrace)))

def __attrs_post_init__(self):
"""Construct the inner tuple for backwards compatibility."""
self._legacy_container = (self.result, self.header)

def __len__(self):
"""Emulate the inner tuple."""
return self._legacy_container.__len__()

def __iter__(self):
"""Emulate the inner tuple."""
return self._legacy_container.__iter__()

def __getitem__(self, key):
"""Emulate the inner tuple."""
return self._legacy_container.__getitem__(key)
Loading