Skip to content

Commit 018a826

Browse files
committed
refactoring Algorithm definition components into separate encryption, kdf, and authentication algorithm suites
1 parent c767187 commit 018a826

File tree

2 files changed

+181
-105
lines changed

2 files changed

+181
-105
lines changed

src/aws_encryption_sdk/identifiers.py

+158-90
Original file line numberDiff line numberDiff line change
@@ -25,116 +25,181 @@
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+
:param algorithm: Encryption algorithm to use
32+
:type algorithm: cryptography.io ciphers algorithm object
33+
:param mode: Encryption mode in which to operate
34+
:type mode: cryptography.io ciphers modes object
35+
:param int data_key_length: Number of bytes in envelope encryption data key
36+
:param int iv_length: Number of bytes in IV
37+
:param int auth_length: Number of bytes in auth data (tag)
38+
:param int auth_key_length: Number of bytes in auth key (not currently supported by any algorithms)
39+
"""
40+
41+
AES_128_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 16, 12, 16)
42+
AES_192_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 24, 12, 16)
43+
AES_256_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 32, 12, 16)
44+
45+
def __init__(self, algorithm, mode, data_key_length, iv_length, auth_length, auth_key_length=0):
46+
"""Prepare a new EncryptionSuite."""
47+
self.algorithm = algorithm
48+
self.mode = mode
49+
self.data_key_length = data_key_length
50+
self.iv_length = iv_length
51+
self.auth_length = self.tag_len = auth_length
52+
# Auth keys are not currently supported
53+
self.auth_key_length = auth_key_length
54+
55+
def valid_kdf(self, kdf):
56+
"""Determine whether a KDFSuite can be used with this EncryptionSuite.
57+
58+
:param kdf: KDFSuite to evaluate
59+
:type kdf: aws_encryption_sdk.identifiers.KDFSuite
60+
:rtype: bool
61+
"""
62+
if kdf.input_length is None:
63+
return True
64+
65+
if self.data_key_length > kdf.input_length(self):
66+
raise InvalidAlgorithmError(
67+
'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len'
68+
)
69+
70+
return True
71+
72+
73+
class KDFSuite(Enum):
74+
"""Static definition of key derivation algorithm details.
75+
76+
:param algorithm: KDF algorithm to use
77+
:type algorithm: cryptography.io KDF object
78+
:param int input_length: Number of bytes of input data to feed into KDF function
79+
:param hash_algorithm: Hash algorithm to use in KDF
80+
:type hash_algorithm: cryptography.io hashes object
81+
"""
82+
83+
NONE = (None, None, None)
84+
HKDF_SHA256 = (hkdf.HKDF, None, hashes.SHA256)
85+
HKDF_SHA384 = (hkdf.HKDF, None, hashes.SHA384)
86+
87+
def __init__(self, algorithm, input_length, hash_algorithm):
88+
"""Prepare a new KDFSuite."""
89+
self.algorithm = algorithm
90+
self._input_length = input_length
91+
self.hash_algorithm = hash_algorithm
3092

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
93+
def input_length(self, encryption):
94+
"""Determine the correct KDF input value length for this KDFSuite when used with
95+
a specific EncryptionSuite.
96+
97+
:param encryption: EncryptionSuite to use
98+
:type encryption: aws_encryption_sdk.identifiers.EncryptionSuite
99+
:rtype: int
100+
"""
101+
# type: (EncryptionSuite) -> bool
102+
if self._input_length is None:
103+
return encryption.data_key_length
104+
105+
return self._input_length
106+
107+
108+
class AuthenticationSuite(Enum):
109+
"""Static definition of authentication algorithm details.
110+
111+
:param algorithm: Information needed by signing algorithm to define behavior
112+
:type algorithm: may vary (currently only ECC curve object)
113+
:param hash_algorithm: Hash algorithm to use in signature
114+
:type hash_algorithm: cryptography.io hashes object
115+
:param int signature_lenth: Number of bytes in signature
35116
"""
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-
)
117+
118+
NONE = (None, None, 0)
119+
SHA256_ECDSA_P256 = (ec.SECP256R1, hashes.SHA256, 71)
120+
SHA256_ECDSA_P384 = (ec.SECP384R1, hashes.SHA384, 103)
121+
122+
def __init__(self, algorithm, hash_algorithm, signature_length):
123+
"""Prepare a new AuthenticationSuite."""
124+
self.algorithm = algorithm
125+
self.hash_algorithm = hash_algorithm
126+
self.signature_length = signature_length
44127

45128

46-
class Algorithm(Enum): # pylint: disable=too-many-instance-attributes
47-
"""IDs of cryptographic algorithms this library knows about.
129+
class AlgorithmSuite(Enum): # pylint: disable=too-many-instance-attributes
130+
"""Static combinations of encryption, KDF, and authentication algorithms.
48131
49132
: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
133+
:param encryption_suite: EncryptionSuite to use with this AlgorithmSuite
134+
:type encryption_suite: aws_encryption_sdk.identifiers.EncryptionSuite
135+
:param kdf_suite: KDFSuite to use with this AlgorithmSuite
136+
:type kdf_suite: aws_encryption_sdk.identifiers.KDFSuite
137+
:param authentication_suite: AuthenticationSuite to use with this AlgorithmSuite
138+
:type authentication_suite: aws_encryption_sdk.identifiers.AuthenticationSuite
68139
"""
69140

70-
__rlookup__ = {} # algorithm_id -> Algorithm
141+
__rlookup__ = {} # algorithm_id -> AlgorithmSuite
71142

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-
)
143+
AES_128_GCM_IV12_TAG16 = (0x0014, EncryptionSuite.AES_128_GCM_IV12_TAG16)
144+
AES_192_GCM_IV12_TAG16 = (0x0046, EncryptionSuite.AES_192_GCM_IV12_TAG16)
145+
AES_256_GCM_IV12_TAG16 = (0x0078, EncryptionSuite.AES_256_GCM_IV12_TAG16)
146+
AES_128_GCM_IV12_TAG16_HKDF_SHA256 = (0x0114, EncryptionSuite.AES_128_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
147+
AES_192_GCM_IV12_TAG16_HKDF_SHA256 = (0x0146, EncryptionSuite.AES_192_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
148+
AES_256_GCM_IV12_TAG16_HKDF_SHA256 = (0x0178, EncryptionSuite.AES_256_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
84149
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
150+
0x0214, EncryptionSuite.AES_128_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256, AuthenticationSuite.SHA256_ECDSA_P256
86151
)
87152
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
153+
0x0346, EncryptionSuite.AES_192_GCM_IV12_TAG16, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384
89154
)
90155
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
156+
0x0378, EncryptionSuite.AES_256_GCM_IV12_TAG16, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384
92157
)
93158

94-
def __init__( # pylint: disable=too-many-arguments
159+
def __init__(
95160
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
161+
algorithm_id, # type: int
162+
encryption, # type: EncryptionSuite
163+
kdf=KDFSuite.NONE, # type: Optional[KDFSuite]
164+
authentication=AuthenticationSuite.NONE, # type: Optional[AuthenticationSuite]
165+
allowed=True # type: bool
109166
):
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-
)
167+
# type: (...) -> None
168+
"""Prepare a new AlgorithmSuite."""
116169
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
170+
self.encryption = encryption
171+
self.encryption.valid_kdf(kdf)
172+
self.kdf = kdf
173+
self.authentication = authentication
174+
self.allowed = allowed
175+
176+
# Encryption Suite Legacy Compatibility
177+
self.encryption_algorithm = self.encryption.algorithm
178+
self.encryption_mode = self.encryption.mode
179+
self.data_key_len = self.encryption.data_key_length
180+
self.iv_len = self.encryption.iv_length
181+
self.auth_key_len = self.encryption.auth_key_length
182+
self.auth_len = self.tag_len = self.encryption.auth_length
183+
184+
# KDF Suite Legacy Compatibility
185+
self.kdf_type = self.kdf.algorithm
186+
self.kdf_hash_type = self.kdf.hash_algorithm
187+
188+
# Authentication Suite Legacy Compatibility
189+
self.signing_algorithm_info = self.authentication.algorithm
190+
self.signing_hash_type = self.authentication.hash_algorithm
191+
self.signature_len = self.authentication.signature_length
192+
133193
self.__rlookup__[algorithm_id] = self
134194

195+
@property
196+
def kdf_input_len(self):
197+
"""Determine the correct KDF input value length for this algorithm suite."""
198+
return self.kdf.input_length(self.encryption)
199+
135200
@classmethod
136201
def get_by_id(cls, algorithm_id):
137-
"""Returns the correct member based on the algorithm_id value.
202+
"""Return the correct member based on the algorithm_id value.
138203
139204
:param algorithm_id: Value of algorithm_id field with which to retrieve Algorithm
140205
:type algorithm_id: int
@@ -144,12 +209,15 @@ def get_by_id(cls, algorithm_id):
144209
return cls.__rlookup__[algorithm_id]
145210

146211
def id_as_bytes(self):
147-
"""Returns the algorithm suite ID as a 2-byte array"""
212+
"""Return the algorithm suite ID as a 2-byte array"""
148213
return struct.pack('>H', self.algorithm_id)
149214

150215
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
216+
"""Determine whether encryption materials for this algorithm suite should be cached."""
217+
return self.kdf is not KDFSuite.NONE
218+
219+
220+
Algorithm = AlgorithmSuite
153221

154222

155223
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)