Skip to content

Keyring materials #163

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 16 commits into from
Jul 11, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
boto3>=1.4.4
cryptography>=1.8.1
attrs>=17.4.0
attrs>=19.1.0
wrapt>=1.10.11
14 changes: 14 additions & 0 deletions src/aws_encryption_sdk/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ class InvalidDataKeyError(AWSEncryptionSDKClientError):
"""Exception class for Invalid Data Keys."""


class InvalidKeyringTraceError(AWSEncryptionSDKClientError):
"""Exception class for invalid Keyring Traces.

.. versionadded:: 1.5.0
"""


class InvalidProviderIdError(AWSEncryptionSDKClientError):
"""Exception class for Invalid Provider IDs."""

Expand All @@ -73,6 +80,13 @@ class DecryptKeyError(AWSEncryptionSDKClientError):
"""Exception class for errors encountered when MasterKeys try to decrypt data keys."""


class SignatureKeyError(AWSEncryptionSDKClientError):
"""Exception class for errors encountered with signing or verification keys.

.. versionadded:: 1.5.0
"""


class ActionNotAllowedError(AWSEncryptionSDKClientError):
"""Exception class for errors encountered when attempting to perform unallowed actions."""

Expand Down
10 changes: 10 additions & 0 deletions src/aws_encryption_sdk/identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,13 @@ class ContentAADString(Enum):
FRAME_STRING_ID = b"AWSKMSEncryptionClient Frame"
FINAL_FRAME_STRING_ID = b"AWSKMSEncryptionClient Final Frame"
NON_FRAMED_STRING_ID = b"AWSKMSEncryptionClient Single Block"


class KeyringTraceFlag(Enum):
Copy link
Contributor

Choose a reason for hiding this comment

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

My spidey sense is tingling that we're going to hit an assumption impedance mismatch down the road between this being an incrementing integer enum here and bit fields in C https://github.com/aws/aws-encryption-sdk-c/blob/969c71d7b48a9c48e00a3ec8bc420c245681bab9/include/aws/cryptosdk/keyring_trace.h#L56

Have we hashed this out in the specification yet? Is there a good reason not to keep consistent keyring trace definition and value mappings across implementations?

Copy link
Contributor

Choose a reason for hiding this comment

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

JS does the bit flag thing because I copied C. I don't much like the bit flag because people will not understand it.
While I can get behind your "specification" point, I'm not sure that this is strictly a specification thing. Knowing what wrapping key did what should be, but how this is recorded?

Copy link
Member Author

Choose a reason for hiding this comment

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

We haven't touched on this yet in the spec, no.

My general take on this is that we have not really been defining this as a thing that should be passed around between implementations, but instead have been defining it as a thing that is used as a unique reference within an implementation.

Talking with the folks who worked on the C client, the reason for using the bitshift values was for simple storage and comparison in a world where you lack things like sets.

I guess it wouldn't hurt anything to set the values to those bit-shift values just in case we want to use them later, and make the decision on if we do or not when we get to that point with the spec.

Copy link
Member Author

Choose a reason for hiding this comment

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

While I can get behind your "specification" point, I'm not sure that this is strictly a specification thing. Knowing what wrapping key did what should be, but how this is recorded?

I think the question is more: "is this a value that we want to expose in any artifact that might cross implementations?"

We have precedent for values like this in, for example, the cache entry identifier and encryption context sorting order.

Copy link
Contributor

Choose a reason for hiding this comment

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

We have precedent for values like this...

Then I vote bit flags all the way down.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

"""KeyRing Trace actions."""

WRAPPING_KEY_GENERATED_DATA_KEY = 1
WRAPPING_KEY_ENCRYPTED_DATA_KEY = 2
WRAPPING_KEY_DECRYPTED_DATA_KEY = 3
WRAPPING_KEY_SIGNED_ENC_CTX = 4
WRAPPING_KEY_VERIFIED_ENC_CTX = 5
368 changes: 334 additions & 34 deletions src/aws_encryption_sdk/materials_managers/__init__.py

Large diffs are not rendered by default.

137 changes: 92 additions & 45 deletions src/aws_encryption_sdk/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,16 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Public data structures for aws_encryption_sdk."""
import copy

import attr
import six
from attr.validators import deep_iterable, deep_mapping, instance_of

import aws_encryption_sdk.identifiers
from aws_encryption_sdk.identifiers import Algorithm, ContentType, KeyringTraceFlag, ObjectType, SerializationVersion
from aws_encryption_sdk.internal.str_ops import to_bytes, to_str


@attr.s(hash=True)
class MessageHeader(object):
"""Deserialized message header object.

:param version: Message format version, per spec
:type version: aws_encryption_sdk.identifiers.SerializationVersion
:param type: Message content type, per spec
:type type: aws_encryption_sdk.identifiers.ObjectType
:param algorithm: Algorithm to use for encryption
:type algorithm: aws_encryption_sdk.identifiers.Algorithm
: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: aws_encryption_sdk.identifiers.ContentType
:param bytes 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
"""

version = attr.ib(
hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.SerializationVersion)
)
type = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.ObjectType))
algorithm = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.Algorithm))
message_id = attr.ib(hash=True, validator=attr.validators.instance_of(bytes))
encryption_context = attr.ib(hash=True, validator=attr.validators.instance_of(dict))
encrypted_data_keys = attr.ib(hash=True, validator=attr.validators.instance_of(set))
content_type = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.ContentType))
content_aad_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types))
header_iv_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types))
frame_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types))


@attr.s(hash=True)
class MasterKeyInfo(object):
"""Contains information necessary to identify a Master Key.
Expand All @@ -61,8 +29,8 @@ class MasterKeyInfo(object):
:param bytes key_info: MasterKey key_info value
"""

provider_id = attr.ib(hash=True, validator=attr.validators.instance_of((six.string_types, bytes)), converter=to_str)
key_info = attr.ib(hash=True, validator=attr.validators.instance_of((six.string_types, bytes)), converter=to_bytes)
provider_id = attr.ib(hash=True, validator=instance_of((six.string_types, bytes)), converter=to_str)
key_info = attr.ib(hash=True, validator=instance_of((six.string_types, bytes)), converter=to_bytes)


@attr.s(hash=True)
Expand All @@ -74,8 +42,20 @@ class RawDataKey(object):
:param bytes data_key: Plaintext data key
"""

key_provider = attr.ib(hash=True, validator=attr.validators.instance_of(MasterKeyInfo))
data_key = attr.ib(hash=True, repr=False, validator=attr.validators.instance_of(bytes))
key_provider = attr.ib(hash=True, validator=instance_of(MasterKeyInfo))
data_key = attr.ib(hash=True, repr=False, validator=instance_of(bytes))

@classmethod
def from_data_key(cls, data_key):
# type: (DataKey) -> RawDataKey
"""Build an :class:`RawDataKey` from a :class:`DataKey`.

.. versionadded:: 1.5.0
"""
if not isinstance(data_key, DataKey):
raise TypeError("data_key must be type DataKey not {}".format(type(data_key).__name__))

return RawDataKey(key_provider=copy.copy(data_key.key_provider), data_key=copy.copy(data_key.data_key))


@attr.s(hash=True)
Expand All @@ -88,9 +68,9 @@ class DataKey(object):
:param bytes encrypted_data_key: Encrypted data key
"""

key_provider = attr.ib(hash=True, validator=attr.validators.instance_of(MasterKeyInfo))
data_key = attr.ib(hash=True, repr=False, validator=attr.validators.instance_of(bytes))
encrypted_data_key = attr.ib(hash=True, validator=attr.validators.instance_of(bytes))
key_provider = attr.ib(hash=True, validator=instance_of(MasterKeyInfo))
data_key = attr.ib(hash=True, repr=False, validator=instance_of(bytes))
encrypted_data_key = attr.ib(hash=True, validator=instance_of(bytes))


@attr.s(hash=True)
Expand All @@ -102,5 +82,72 @@ class EncryptedDataKey(object):
:param bytes encrypted_data_key: Encrypted data key
"""

key_provider = attr.ib(hash=True, validator=attr.validators.instance_of(MasterKeyInfo))
encrypted_data_key = attr.ib(hash=True, validator=attr.validators.instance_of(bytes))
key_provider = attr.ib(hash=True, validator=instance_of(MasterKeyInfo))
encrypted_data_key = attr.ib(hash=True, validator=instance_of(bytes))

@classmethod
def from_data_key(cls, data_key):
# type: (DataKey) -> EncryptedDataKey
"""Build an :class:`EncryptedDataKey` from a :class:`DataKey`.

.. versionadded:: 1.5.0
"""
if not isinstance(data_key, DataKey):
raise TypeError("data_key must be type DataKey not {}".format(type(data_key).__name__))

return EncryptedDataKey(
key_provider=copy.copy(data_key.key_provider), encrypted_data_key=copy.copy(data_key.encrypted_data_key)
)


@attr.s
class KeyringTrace(object):
"""Record of all actions that a KeyRing performed with a wrapping key.

.. versionadded:: 1.5.0

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

wrapping_key = attr.ib(validator=instance_of(MasterKeyInfo))
flags = attr.ib(validator=deep_iterable(member_validator=instance_of(KeyringTraceFlag)))


@attr.s(hash=True)
class MessageHeader(object):
"""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 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 int header_iv_length: Bytes in Initialization Vector value found in header
:param int frame_length: Length of message frame in bytes
"""

version = attr.ib(hash=True, validator=instance_of(SerializationVersion))
type = attr.ib(hash=True, validator=instance_of(ObjectType))
algorithm = attr.ib(hash=True, validator=instance_of(Algorithm))
message_id = attr.ib(hash=True, validator=instance_of(bytes))
encryption_context = attr.ib(
hash=True,
validator=deep_mapping(
key_validator=instance_of(six.string_types), value_validator=instance_of(six.string_types)
),
)
encrypted_data_keys = attr.ib(hash=True, validator=deep_iterable(member_validator=instance_of(EncryptedDataKey)))
content_type = attr.ib(hash=True, validator=instance_of(ContentType))
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))
8 changes: 3 additions & 5 deletions test/unit/test_caches.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
)
from aws_encryption_sdk.identifiers import Algorithm
from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest
from aws_encryption_sdk.structures import DataKey, MasterKeyInfo
from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo

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

Expand All @@ -47,19 +47,17 @@
},
"encrypted_data_keys": [
{
"key": DataKey(
"key": EncryptedDataKey(
key_provider=MasterKeyInfo(provider_id="this is a provider ID", key_info=b"this is some key info"),
data_key=b"super secret key!",
encrypted_data_key=b"super secret key, now with encryption!",
),
"hash": b"TYoFeYuxns/FBlaw4dsRDOv25OCEKuZG9iXt5iEdJ8LU7n5glgkDAVxWUEYC4JKKykJdHkaVpxcDvNqS6UswiQ==",
},
{
"key": DataKey(
"key": EncryptedDataKey(
key_provider=MasterKeyInfo(
provider_id="another provider ID!", key_info=b"this is some different key info"
),
data_key=b"better super secret key!",
encrypted_data_key=b"better super secret key, now with encryption!",
),
"hash": b"wSrDlPM2ocIj9MAtD94ULSR0Qrt1muBovBDRL+DsSTNphJEM3CZ/h3OyvYL8BR2EIXx0m7GYwv8dGtyZL2D87w==",
Expand Down
Loading