Skip to content

Commit 2e85bfd

Browse files
MeghaShettymattsb42-aws
authored andcommitted
Multi keyrings (#166)
* Adding Keyring API * Delete __init__.py * Delete raw_keyring.py * Added docstring to public class * Edited docstring * Edited docstring again * Changes in docstring statements * Docstring changes * Changes in docstring * Raw keyring initial * Raw keyring encrypt commit * Encrypt functions for Raw RSA and AES * Raw RSA and AES initial * raw keyrings first commit * Multi keyring first commit * Changes in the base file * Temporary changes in multiple files * Committing initial code * Deleted raw aes test * Multi Keyrings * Updating base API and raw keyrings * Corrected tox errors * Added typehints * Updated raw keyrings * Updated raw keyrings * Changes in error conditions for multi keyrings * Made all suggested changes in multi-keyrings * Corrected tox errors * Added docstring to __attrs_post_init__ * Changed variable name neither_generator_nor_children_defined to neither_generator_nor_children * Changed raw keyrings * Corrected tox errors * Updated raw keyrings * Updated raw keyrings and functional test for multi keyrings * Functional tests for multi-keyrings work * Autoformat errors corrected and changed Exception to BaseException to solve broad exception error * Added pylint disable broad except to raw keyrings and added multi parametrize to multi keyrings functional test * Removed duplicate import statements * Changes in functional test for multi keyrings according to change in raw keyrings * Changed RSA key structure to RSAPublicKey/RSAPrivateKey and functional test passes * Removed unwanted commented lines from test * Pylint errors * More pylint errors * Made suggested changes in multi keyring * Multi keyring unit tests * Optimized loop for decryption keyring * Unit tests for multi keyrings and added sample encryption materials and multi keyrings in test_utils * Multi keyrings unit tests * Making changes in tests and API * Almost all unit tests done * Unit tests for multi keyrings * Unit tests for multi keyrings * Unit tests for multi-keyrings working except the one to check if no further keyrings are called if data encryption key is added * Made changes in raw keyrings to match the latest version * Removed unused imports * Made suggested changes * Removed unused imports * Resolved formatting errors * Made suggested changes - partial * Made all suggested changes * apply autoformatting x_x
1 parent fcc05ba commit 2e85bfd

File tree

4 files changed

+652
-3
lines changed

4 files changed

+652
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Resources required for Multi Keyrings."""
14+
import itertools
15+
16+
import attr
17+
from attr.validators import deep_iterable, instance_of, optional
18+
19+
from aws_encryption_sdk.exceptions import EncryptKeyError, GenerateKeyError
20+
from aws_encryption_sdk.keyring.base import DecryptionMaterials, EncryptedDataKey, EncryptionMaterials, Keyring
21+
22+
try: # Python 3.5.0 and 3.5.1 have incompatible typing modules
23+
from typing import Iterable # noqa pylint: disable=unused-import
24+
except ImportError: # pragma: no cover
25+
# We only actually need these imports when running the mypy checks
26+
pass
27+
28+
29+
@attr.s
30+
class MultiKeyring(Keyring):
31+
"""Public class for Multi Keyring.
32+
33+
:param generator: Generator keyring used to generate data encryption key (optional)
34+
:type generator: Keyring
35+
:param list children: List of keyrings used to encrypt the data encryption key (optional)
36+
:raises EncryptKeyError: if encryption of data key fails for any reason
37+
"""
38+
39+
children = attr.ib(
40+
default=attr.Factory(tuple), validator=optional(deep_iterable(member_validator=instance_of(Keyring)))
41+
)
42+
generator = attr.ib(default=None, validator=optional(instance_of(Keyring)))
43+
44+
def __attrs_post_init__(self):
45+
# type: () -> None
46+
"""Prepares initial values not handled by attrs."""
47+
neither_generator_nor_children = self.generator is None and not self.children
48+
if neither_generator_nor_children:
49+
raise TypeError("At least one of generator or children must be provided")
50+
51+
_generator = (self.generator,) if self.generator is not None else ()
52+
self._decryption_keyrings = list(itertools.chain(_generator, self.children))
53+
54+
def on_encrypt(self, encryption_materials):
55+
# type: (EncryptionMaterials) -> EncryptionMaterials
56+
"""Generate a data key using generator keyring
57+
and encrypt it using any available wrapping key in any child keyring.
58+
59+
:param encryption_materials: Encryption materials for keyring to modify.
60+
:type encryption_materials: aws_encryption_sdk.materials_managers.EncryptionMaterials
61+
:returns: Optionally modified encryption materials.
62+
:rtype: aws_encryption_sdk.materials_managers.EncryptionMaterials
63+
:raises EncryptKeyError: if unable to encrypt data key.
64+
"""
65+
# Check if generator keyring is not provided and data key is not generated
66+
if self.generator is None and encryption_materials.data_encryption_key is None:
67+
raise EncryptKeyError(
68+
"Generator keyring not provided "
69+
"and encryption materials do not already contain a plaintext data key."
70+
)
71+
72+
# Call on_encrypt on the generator keyring if it is provided
73+
if self.generator is not None:
74+
75+
encryption_materials = self.generator.on_encrypt(encryption_materials=encryption_materials)
76+
77+
# Check if data key is generated
78+
if encryption_materials.data_encryption_key is None:
79+
raise GenerateKeyError("Unable to generate data encryption key.")
80+
81+
# Call on_encrypt on all other keyrings
82+
for keyring in self.children:
83+
encryption_materials = keyring.on_encrypt(encryption_materials=encryption_materials)
84+
85+
return encryption_materials
86+
87+
def on_decrypt(self, decryption_materials, encrypted_data_keys):
88+
# type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
89+
"""Attempt to decrypt the encrypted data keys.
90+
91+
:param decryption_materials: Decryption materials for keyring to modify.
92+
:type decryption_materials: aws_encryption_sdk.materials_managers.DecryptionMaterials
93+
:param encrypted_data_keys: List of encrypted data keys.
94+
:type: List of `aws_encryption_sdk.structures.EncryptedDataKey`
95+
:returns: Optionally modified decryption materials.
96+
:rtype: aws_encryption_sdk.materials_managers.DecryptionMaterials
97+
"""
98+
# Call on_decrypt on all keyrings till decryption is successful
99+
for keyring in self._decryption_keyrings:
100+
if decryption_materials.data_encryption_key is not None:
101+
return decryption_materials
102+
decryption_materials = keyring.on_decrypt(
103+
decryption_materials=decryption_materials, encrypted_data_keys=encrypted_data_keys
104+
)
105+
return decryption_materials
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Functional tests for Multi keyring encryption decryption path."""
14+
15+
import pytest
16+
from cryptography.hazmat.backends import default_backend
17+
from cryptography.hazmat.primitives.asymmetric import rsa
18+
19+
from aws_encryption_sdk.identifiers import KeyringTraceFlag, WrappingAlgorithm
20+
from aws_encryption_sdk.internal.defaults import ALGORITHM
21+
from aws_encryption_sdk.keyring.multi_keyring import MultiKeyring
22+
from aws_encryption_sdk.keyring.raw_keyring import RawAESKeyring, RawRSAKeyring
23+
from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials
24+
from aws_encryption_sdk.structures import KeyringTrace, MasterKeyInfo, RawDataKey
25+
26+
pytestmark = [pytest.mark.functional, pytest.mark.local]
27+
28+
_ENCRYPTION_CONTEXT = {"encryption": "context", "values": "here"}
29+
_PROVIDER_ID = "Random Raw Keys"
30+
_KEY_ID = b"5325b043-5843-4629-869c-64794af77ada"
31+
_WRAPPING_KEY_AES = b"\xeby-\x80A6\x15rA8\x83#,\xe4\xab\xac`\xaf\x99Z\xc1\xce\xdb\xb6\x0f\xb7\x805\xb2\x14J3"
32+
33+
_ENCRYPTION_MATERIALS_WITHOUT_DATA_KEY = EncryptionMaterials(
34+
algorithm=ALGORITHM, encryption_context=_ENCRYPTION_CONTEXT
35+
)
36+
37+
_ENCRYPTION_MATERIALS_WITH_DATA_KEY = EncryptionMaterials(
38+
algorithm=ALGORITHM,
39+
data_encryption_key=RawDataKey(
40+
key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID),
41+
data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(',
42+
),
43+
encryption_context=_ENCRYPTION_CONTEXT,
44+
keyring_trace=[
45+
KeyringTrace(
46+
wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID),
47+
flags={KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY},
48+
)
49+
],
50+
)
51+
52+
_MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN = MultiKeyring(
53+
generator=RawAESKeyring(
54+
key_namespace=_PROVIDER_ID,
55+
key_name=_KEY_ID,
56+
wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING,
57+
wrapping_key=_WRAPPING_KEY_AES,
58+
),
59+
children=[
60+
RawRSAKeyring(
61+
key_namespace=_PROVIDER_ID,
62+
key_name=_KEY_ID,
63+
wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1,
64+
private_wrapping_key=rsa.generate_private_key(
65+
public_exponent=65537, key_size=2048, backend=default_backend()
66+
),
67+
),
68+
RawRSAKeyring(
69+
key_namespace=_PROVIDER_ID,
70+
key_name=_KEY_ID,
71+
wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1,
72+
private_wrapping_key=rsa.generate_private_key(
73+
public_exponent=65537, key_size=2048, backend=default_backend()
74+
),
75+
),
76+
],
77+
)
78+
79+
_MULTI_KEYRING_WITHOUT_CHILDREN = MultiKeyring(
80+
generator=RawRSAKeyring(
81+
key_namespace=_PROVIDER_ID,
82+
key_name=_KEY_ID,
83+
wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1,
84+
private_wrapping_key=rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()),
85+
)
86+
)
87+
88+
_MULTI_KEYRING_WITHOUT_GENERATOR = MultiKeyring(
89+
children=[
90+
RawRSAKeyring(
91+
key_namespace=_PROVIDER_ID,
92+
key_name=_KEY_ID,
93+
wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1,
94+
private_wrapping_key=rsa.generate_private_key(
95+
public_exponent=65537, key_size=2048, backend=default_backend()
96+
),
97+
),
98+
RawAESKeyring(
99+
key_namespace=_PROVIDER_ID,
100+
key_name=_KEY_ID,
101+
wrapping_algorithm=WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING,
102+
wrapping_key=_WRAPPING_KEY_AES,
103+
),
104+
]
105+
)
106+
107+
108+
@pytest.mark.parametrize(
109+
"multi_keyring, encryption_materials",
110+
[
111+
(_MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN, _ENCRYPTION_MATERIALS_WITHOUT_DATA_KEY),
112+
(_MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN, _ENCRYPTION_MATERIALS_WITH_DATA_KEY),
113+
(_MULTI_KEYRING_WITHOUT_CHILDREN, _ENCRYPTION_MATERIALS_WITH_DATA_KEY),
114+
(_MULTI_KEYRING_WITHOUT_GENERATOR, _ENCRYPTION_MATERIALS_WITH_DATA_KEY),
115+
],
116+
)
117+
def test_multi_keyring_encryption_decryption(multi_keyring, encryption_materials):
118+
# Call on_encrypt function for the keyring
119+
encryption_materials = multi_keyring.on_encrypt(encryption_materials)
120+
121+
# Generate decryption materials
122+
decryption_materials = DecryptionMaterials(
123+
algorithm=ALGORITHM, verification_key=b"ex_verification_key", encryption_context=_ENCRYPTION_CONTEXT
124+
)
125+
126+
# Call on_decrypt function for the keyring
127+
decryption_materials = multi_keyring.on_decrypt(
128+
decryption_materials=decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys
129+
)
130+
131+
# Check if the data keys match
132+
assert encryption_materials.data_encryption_key == decryption_materials.data_encryption_key

0 commit comments

Comments
 (0)