Skip to content

Commit f98382a

Browse files
Merge pull request #36 from mattsb42-aws/algorithm-reorg
refactoring Algorithm definition components into separate encryption, kdf, and authentication algorithm suites
2 parents 04ae2a4 + f71e23e commit f98382a

File tree

2 files changed

+189
-105
lines changed

2 files changed

+189
-105
lines changed

src/aws_encryption_sdk/identifiers.py

+166-90
Original file line numberDiff line numberDiff line change
@@ -25,116 +25,189 @@
2525
USER_AGENT_SUFFIX = 'AwsEncryptionSdkPython-KMSMasterKey/{}'.format(__version__)
2626

2727

28-
def _kdf_input_len_check(data_key_len, kdf_type, kdf_input_len):
29-
"""Validates that data_key_len and kdf_input_len have the correct relationship.
28+
class EncryptionSuite(Enum):
29+
"""Static definition of encryption algorithm details.
30+
31+
.. warning:: These members must only be used as part of an AlgorithmSuite.
32+
33+
:param algorithm: Encryption algorithm to use
34+
:type algorithm: cryptography.io ciphers algorithm object
35+
:param mode: Encryption mode in which to operate
36+
:type mode: cryptography.io ciphers modes object
37+
:param int data_key_length: Number of bytes in envelope encryption data key
38+
:param int iv_length: Number of bytes in IV
39+
:param int auth_length: Number of bytes in auth data (tag)
40+
:param int auth_key_length: Number of bytes in auth key (not currently supported by any algorithms)
41+
"""
42+
43+
AES_128_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 16, 12, 16)
44+
AES_192_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 24, 12, 16)
45+
AES_256_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 32, 12, 16)
46+
47+
def __init__(self, algorithm, mode, data_key_length, iv_length, auth_length, auth_key_length=0):
48+
"""Prepare a new EncryptionSuite."""
49+
self.algorithm = algorithm
50+
self.mode = mode
51+
self.data_key_length = data_key_length
52+
self.iv_length = iv_length
53+
self.auth_length = self.tag_len = auth_length
54+
# Auth keys are not currently supported
55+
self.auth_key_length = auth_key_length
56+
57+
def valid_kdf(self, kdf):
58+
"""Determine whether a KDFSuite can be used with this EncryptionSuite.
59+
60+
:param kdf: KDFSuite to evaluate
61+
:type kdf: aws_encryption_sdk.identifiers.KDFSuite
62+
:rtype: bool
63+
"""
64+
if kdf.input_length is None:
65+
return True
66+
67+
if self.data_key_length > kdf.input_length(self):
68+
raise InvalidAlgorithmError(
69+
'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len'
70+
)
71+
72+
return True
3073

31-
:param int data_key_len: Number of bytes in key
32-
:param kdf_type: KDF algorithm to use
33-
:param kdf_type: cryptography.io KDF object
34-
:param int kdf_input_len: Length of input data to feed into KDF function
74+
75+
class KDFSuite(Enum):
76+
"""Static definition of key derivation algorithm details.
77+
78+
.. warning:: These members must only be used as part of an AlgorithmSuite.
79+
80+
:param algorithm: KDF algorithm to use
81+
:type algorithm: cryptography.io KDF object
82+
:param int input_length: Number of bytes of input data to feed into KDF function
83+
:param hash_algorithm: Hash algorithm to use in KDF
84+
:type hash_algorithm: cryptography.io hashes object
3585
"""
36-
if kdf_type is None and data_key_len != kdf_input_len:
37-
raise InvalidAlgorithmError(
38-
'Invalid Algorithm definition: data_key_len must equal kdf_input_len for non-KDF algorithms'
39-
)
40-
elif data_key_len > kdf_input_len:
41-
raise InvalidAlgorithmError(
42-
'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len'
43-
)
4486

87+
NONE = (None, None, None)
88+
HKDF_SHA256 = (hkdf.HKDF, None, hashes.SHA256)
89+
HKDF_SHA384 = (hkdf.HKDF, None, hashes.SHA384)
90+
91+
def __init__(self, algorithm, input_length, hash_algorithm):
92+
"""Prepare a new KDFSuite."""
93+
self.algorithm = algorithm
94+
self._input_length = input_length
95+
self.hash_algorithm = hash_algorithm
96+
97+
def input_length(self, encryption):
98+
"""Determine the correct KDF input value length for this KDFSuite when used with
99+
a specific EncryptionSuite.
100+
101+
:param encryption: EncryptionSuite to use
102+
:type encryption: aws_encryption_sdk.identifiers.EncryptionSuite
103+
:rtype: int
104+
"""
105+
# type: (EncryptionSuite) -> bool
106+
if self._input_length is None:
107+
return encryption.data_key_length
108+
109+
return self._input_length
45110

46-
class Algorithm(Enum): # pylint: disable=too-many-instance-attributes
47-
"""IDs of cryptographic algorithms this library knows about.
111+
112+
class AuthenticationSuite(Enum):
113+
"""Static definition of authentication algorithm details.
114+
115+
.. warning:: These members must only be used as part of an AlgorithmSuite.
116+
117+
:param algorithm: Information needed by signing algorithm to define behavior
118+
:type algorithm: may vary (currently only ECC curve object)
119+
:param hash_algorithm: Hash algorithm to use in signature
120+
:type hash_algorithm: cryptography.io hashes object
121+
:param int signature_lenth: Number of bytes in signature
122+
"""
123+
124+
NONE = (None, None, 0)
125+
SHA256_ECDSA_P256 = (ec.SECP256R1, hashes.SHA256, 71)
126+
SHA256_ECDSA_P384 = (ec.SECP384R1, hashes.SHA384, 103)
127+
128+
def __init__(self, algorithm, hash_algorithm, signature_length):
129+
"""Prepare a new AuthenticationSuite."""
130+
self.algorithm = algorithm
131+
self.hash_algorithm = hash_algorithm
132+
self.signature_length = signature_length
133+
134+
135+
class AlgorithmSuite(Enum): # pylint: disable=too-many-instance-attributes
136+
"""Static combinations of encryption, KDF, and authentication algorithms.
137+
138+
.. warning:: No AlgorithmSuites except those defined here are supported.
48139
49140
:param int algorithm_id: KMS Encryption Algorithm ID
50-
:param encryption_algorithm: Encryption algorithm to use
51-
:type encryption_algorithm: cryptography.io ciphers algorithm object
52-
:param encryption_mode: Encryption mode in which to operate
53-
:type encryption_mode: cryptography.io ciphers modes object
54-
:param int iv_len: Number of bytes in IV
55-
:param int auth_len: Number of bytes in auth data (tag)
56-
:param int auth_key_len: Number of bytes in auth key (not currently supported by any algorithms)
57-
:param int data_key_len: Number of bytes in envelope encryption data key
58-
:param kdf_type: KDF algorithm to use
59-
:type kdf_type: cryptography.io KDF object
60-
:param int kdf_input_len: Number of bytes of input data to feed into KDF function
61-
:param kdf_hash_type: Hash algorithm to use in KDF
62-
:type kdf_hash_type: cryptography.io hashes object
63-
:param signing_algorithm_info: Information needed by signing algorithm to define behavior
64-
:type signing_algorithm_info: may vary (currently only ECC curve object)
65-
:param signature_hash_type: Hash algorithm to use in signature
66-
:type signature_hash_type: cryptography.io hashes object
67-
:param int signature_len: Number of bytes in signature
141+
:param encryption_suite: EncryptionSuite to use with this AlgorithmSuite
142+
:type encryption_suite: aws_encryption_sdk.identifiers.EncryptionSuite
143+
:param kdf_suite: KDFSuite to use with this AlgorithmSuite
144+
:type kdf_suite: aws_encryption_sdk.identifiers.KDFSuite
145+
:param authentication_suite: AuthenticationSuite to use with this AlgorithmSuite
146+
:type authentication_suite: aws_encryption_sdk.identifiers.AuthenticationSuite
68147
"""
69148

70-
__rlookup__ = {} # algorithm_id -> Algorithm
149+
__rlookup__ = {} # algorithm_id -> AlgorithmSuite
71150

72-
AES_128_GCM_IV12_TAG16 = (0x0014, algorithms.AES, modes.GCM, 12, 16, 0, 16, None, 16, None, None, None, 0)
73-
AES_192_GCM_IV12_TAG16 = (0x0046, algorithms.AES, modes.GCM, 12, 16, 0, 24, None, 24, None, None, None, 0)
74-
AES_256_GCM_IV12_TAG16 = (0x0078, algorithms.AES, modes.GCM, 12, 16, 0, 32, None, 32, None, None, None, 0)
75-
AES_128_GCM_IV12_TAG16_HKDF_SHA256 = (
76-
0x0114, algorithms.AES, modes.GCM, 12, 16, 0, 16, hkdf.HKDF, 16, hashes.SHA256, None, None, 0
77-
)
78-
AES_192_GCM_IV12_TAG16_HKDF_SHA256 = (
79-
0x0146, algorithms.AES, modes.GCM, 12, 16, 0, 24, hkdf.HKDF, 24, hashes.SHA256, None, None, 0
80-
)
81-
AES_256_GCM_IV12_TAG16_HKDF_SHA256 = (
82-
0x0178, algorithms.AES, modes.GCM, 12, 16, 0, 32, hkdf.HKDF, 32, hashes.SHA256, None, None, 0
83-
)
151+
AES_128_GCM_IV12_TAG16 = (0x0014, EncryptionSuite.AES_128_GCM_IV12_TAG16)
152+
AES_192_GCM_IV12_TAG16 = (0x0046, EncryptionSuite.AES_192_GCM_IV12_TAG16)
153+
AES_256_GCM_IV12_TAG16 = (0x0078, EncryptionSuite.AES_256_GCM_IV12_TAG16)
154+
AES_128_GCM_IV12_TAG16_HKDF_SHA256 = (0x0114, EncryptionSuite.AES_128_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
155+
AES_192_GCM_IV12_TAG16_HKDF_SHA256 = (0x0146, EncryptionSuite.AES_192_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
156+
AES_256_GCM_IV12_TAG16_HKDF_SHA256 = (0x0178, EncryptionSuite.AES_256_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
84157
AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256 = (
85-
0x0214, algorithms.AES, modes.GCM, 12, 16, 0, 16, hkdf.HKDF, 16, hashes.SHA256, ec.SECP256R1, hashes.SHA256, 71
158+
0x0214, EncryptionSuite.AES_128_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256, AuthenticationSuite.SHA256_ECDSA_P256
86159
)
87160
AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 = (
88-
0x0346, algorithms.AES, modes.GCM, 12, 16, 0, 24, hkdf.HKDF, 24, hashes.SHA384, ec.SECP384R1, hashes.SHA384, 103
161+
0x0346, EncryptionSuite.AES_192_GCM_IV12_TAG16, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384
89162
)
90163
AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 = (
91-
0x0378, algorithms.AES, modes.GCM, 12, 16, 0, 32, hkdf.HKDF, 32, hashes.SHA384, ec.SECP384R1, hashes.SHA384, 103
164+
0x0378, EncryptionSuite.AES_256_GCM_IV12_TAG16, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384
92165
)
93166

94-
def __init__( # pylint: disable=too-many-arguments
167+
def __init__(
95168
self,
96-
algorithm_id,
97-
encryption_algorithm,
98-
encryption_mode,
99-
iv_len,
100-
auth_len,
101-
auth_key_len,
102-
data_key_len,
103-
kdf_type,
104-
kdf_input_len,
105-
kdf_hash_type,
106-
signing_algorithm_info,
107-
signing_hash_type,
108-
signature_len
169+
algorithm_id, # type: int
170+
encryption, # type: EncryptionSuite
171+
kdf=KDFSuite.NONE, # type: Optional[KDFSuite]
172+
authentication=AuthenticationSuite.NONE, # type: Optional[AuthenticationSuite]
173+
allowed=True # type: bool
109174
):
110-
"""Prepares new Algorithm."""
111-
_kdf_input_len_check(
112-
data_key_len=data_key_len,
113-
kdf_type=kdf_type,
114-
kdf_input_len=kdf_input_len
115-
)
175+
# type: (...) -> None
176+
"""Prepare a new AlgorithmSuite."""
116177
self.algorithm_id = algorithm_id
117-
self.encryption_algorithm = encryption_algorithm
118-
self.encryption_mode = encryption_mode
119-
self.iv_len = iv_len
120-
# Auth keys are not currently supported
121-
self.auth_key_len = auth_key_len
122-
self.auth_len = self.tag_len = auth_len
123-
self.data_key_len = data_key_len
124-
self.kdf_type = kdf_type
125-
self.kdf_input_len = kdf_input_len
126-
self.kdf_hash_type = kdf_hash_type
127-
self.signing_algorithm_info = signing_algorithm_info
128-
self.signing_hash_type = signing_hash_type
129-
self.signature_len = signature_len
130-
# All algorithms in this enum are allowed for now.
131-
# This might change in the future.
132-
self.allowed = True
178+
self.encryption = encryption
179+
self.encryption.valid_kdf(kdf)
180+
self.kdf = kdf
181+
self.authentication = authentication
182+
self.allowed = allowed
183+
184+
# Encryption Values
185+
self.encryption_algorithm = self.encryption.algorithm
186+
self.encryption_mode = self.encryption.mode
187+
self.data_key_len = self.encryption.data_key_length
188+
self.iv_len = self.encryption.iv_length
189+
self.auth_key_len = self.encryption.auth_key_length
190+
self.auth_len = self.tag_len = self.encryption.auth_length
191+
192+
# KDF Values
193+
self.kdf_type = self.kdf.algorithm
194+
self.kdf_hash_type = self.kdf.hash_algorithm
195+
196+
# Authentication Values
197+
self.signing_algorithm_info = self.authentication.algorithm
198+
self.signing_hash_type = self.authentication.hash_algorithm
199+
self.signature_len = self.authentication.signature_length
200+
133201
self.__rlookup__[algorithm_id] = self
134202

203+
@property
204+
def kdf_input_len(self):
205+
"""Determine the correct KDF input value length for this algorithm suite."""
206+
return self.kdf.input_length(self.encryption)
207+
135208
@classmethod
136209
def get_by_id(cls, algorithm_id):
137-
"""Returns the correct member based on the algorithm_id value.
210+
"""Return the correct member based on the algorithm_id value.
138211
139212
:param algorithm_id: Value of algorithm_id field with which to retrieve Algorithm
140213
:type algorithm_id: int
@@ -144,12 +217,15 @@ def get_by_id(cls, algorithm_id):
144217
return cls.__rlookup__[algorithm_id]
145218

146219
def id_as_bytes(self):
147-
"""Returns the algorithm suite ID as a 2-byte array"""
220+
"""Return the algorithm suite ID as a 2-byte array"""
148221
return struct.pack('>H', self.algorithm_id)
149222

150223
def safe_to_cache(self):
151-
"""Determines whether encryption materials for this algorithm suite should be cached."""
152-
return self.kdf_type is not None
224+
"""Determine whether encryption materials for this algorithm suite should be cached."""
225+
return self.kdf is not KDFSuite.NONE
226+
227+
228+
Algorithm = AlgorithmSuite
153229

154230

155231
class EncryptionType(Enum):

test/unit/test_identifiers.py

+23-15
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""Unit test suite for aws_encryption_sdk.identifiers"""
14+
from mock import Mock
1415
import pytest
1516

1617
from aws_encryption_sdk.exceptions import InvalidAlgorithmError
17-
from aws_encryption_sdk.identifiers import _kdf_input_len_check, Algorithm
18+
from aws_encryption_sdk.identifiers import Algorithm, EncryptionSuite, KDFSuite
1819

1920

2021
@pytest.mark.parametrize('check_algorithm, safe_to_cache', (
@@ -35,23 +36,30 @@ def test_algorithm_safe_to_cache(check_algorithm, safe_to_cache):
3536
assert not check_algorithm.safe_to_cache()
3637

3738

38-
def test_kdf_input_len_check_valid():
39-
_kdf_input_len_check(
40-
data_key_len=5,
41-
kdf_type=5,
42-
kdf_input_len=5
43-
)
39+
@pytest.mark.parametrize('suite', [suite for suite in EncryptionSuite])
40+
def test_encryption_suite_invalid_kdf(suite):
41+
mock_kdf = Mock()
42+
mock_kdf.input_length.return_value = 1
43+
with pytest.raises(InvalidAlgorithmError) as excinfo:
44+
suite.valid_kdf(mock_kdf)
4445

46+
excinfo.match(r'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len')
4547

46-
def test_kdf_input_len_check_invalid_no_kdf():
47-
with pytest.raises(InvalidAlgorithmError) as excinfo:
48-
_kdf_input_len_check(data_key_len=2, kdf_type=None, kdf_input_len=5)
4948

50-
excinfo.match(r'Invalid Algorithm definition: data_key_len must equal kdf_input_len for non-KDF algorithms')
49+
def build_valid_kdf_checks():
50+
checks = []
51+
for suite in EncryptionSuite:
52+
checks.append((suite, KDFSuite.NONE, True))
53+
checks.append((suite, KDFSuite.HKDF_SHA256, True))
54+
checks.append((suite, KDFSuite.HKDF_SHA384, True))
55+
return checks
5156

5257

53-
def test_kdf_input_len_check_invalid_with_kdf():
54-
with pytest.raises(InvalidAlgorithmError) as excinfo:
55-
_kdf_input_len_check(data_key_len=5, kdf_type=5, kdf_input_len=2)
58+
@pytest.mark.parametrize('encryption, kdf, expected', build_valid_kdf_checks())
59+
def test_encryption_suite_valid_kdf(encryption, kdf, expected):
60+
actual = encryption.valid_kdf(kdf)
5661

57-
excinfo.match(r'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len')
62+
if expected:
63+
assert actual
64+
else:
65+
assert not actual

0 commit comments

Comments
 (0)