Skip to content

Commit

Permalink
Merge pull request #36 from mattsb42-aws/algorithm-reorg
Browse files Browse the repository at this point in the history
refactoring Algorithm definition components into separate encryption, kdf, and authentication algorithm suites
  • Loading branch information
SalusaSecondus authored Jan 5, 2018
2 parents 04ae2a4 + f71e23e commit f98382a
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 105 deletions.
256 changes: 166 additions & 90 deletions src/aws_encryption_sdk/identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,116 +25,189 @@
USER_AGENT_SUFFIX = 'AwsEncryptionSdkPython-KMSMasterKey/{}'.format(__version__)


def _kdf_input_len_check(data_key_len, kdf_type, kdf_input_len):
"""Validates that data_key_len and kdf_input_len have the correct relationship.
class EncryptionSuite(Enum):
"""Static definition of encryption algorithm details.
.. warning:: These members must only be used as part of an AlgorithmSuite.
:param algorithm: Encryption algorithm to use
:type algorithm: cryptography.io ciphers algorithm object
:param mode: Encryption mode in which to operate
:type mode: cryptography.io ciphers modes object
:param int data_key_length: Number of bytes in envelope encryption data key
:param int iv_length: Number of bytes in IV
:param int auth_length: Number of bytes in auth data (tag)
:param int auth_key_length: Number of bytes in auth key (not currently supported by any algorithms)
"""

AES_128_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 16, 12, 16)
AES_192_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 24, 12, 16)
AES_256_GCM_IV12_TAG16 = (algorithms.AES, modes.GCM, 32, 12, 16)

def __init__(self, algorithm, mode, data_key_length, iv_length, auth_length, auth_key_length=0):
"""Prepare a new EncryptionSuite."""
self.algorithm = algorithm
self.mode = mode
self.data_key_length = data_key_length
self.iv_length = iv_length
self.auth_length = self.tag_len = auth_length
# Auth keys are not currently supported
self.auth_key_length = auth_key_length

def valid_kdf(self, kdf):
"""Determine whether a KDFSuite can be used with this EncryptionSuite.
:param kdf: KDFSuite to evaluate
:type kdf: aws_encryption_sdk.identifiers.KDFSuite
:rtype: bool
"""
if kdf.input_length is None:
return True

if self.data_key_length > kdf.input_length(self):
raise InvalidAlgorithmError(
'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len'
)

return True

:param int data_key_len: Number of bytes in key
:param kdf_type: KDF algorithm to use
:param kdf_type: cryptography.io KDF object
:param int kdf_input_len: Length of input data to feed into KDF function

class KDFSuite(Enum):
"""Static definition of key derivation algorithm details.
.. warning:: These members must only be used as part of an AlgorithmSuite.
:param algorithm: KDF algorithm to use
:type algorithm: cryptography.io KDF object
:param int input_length: Number of bytes of input data to feed into KDF function
:param hash_algorithm: Hash algorithm to use in KDF
:type hash_algorithm: cryptography.io hashes object
"""
if kdf_type is None and data_key_len != kdf_input_len:
raise InvalidAlgorithmError(
'Invalid Algorithm definition: data_key_len must equal kdf_input_len for non-KDF algorithms'
)
elif data_key_len > kdf_input_len:
raise InvalidAlgorithmError(
'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len'
)

NONE = (None, None, None)
HKDF_SHA256 = (hkdf.HKDF, None, hashes.SHA256)
HKDF_SHA384 = (hkdf.HKDF, None, hashes.SHA384)

def __init__(self, algorithm, input_length, hash_algorithm):
"""Prepare a new KDFSuite."""
self.algorithm = algorithm
self._input_length = input_length
self.hash_algorithm = hash_algorithm

def input_length(self, encryption):
"""Determine the correct KDF input value length for this KDFSuite when used with
a specific EncryptionSuite.
:param encryption: EncryptionSuite to use
:type encryption: aws_encryption_sdk.identifiers.EncryptionSuite
:rtype: int
"""
# type: (EncryptionSuite) -> bool
if self._input_length is None:
return encryption.data_key_length

return self._input_length

class Algorithm(Enum): # pylint: disable=too-many-instance-attributes
"""IDs of cryptographic algorithms this library knows about.

class AuthenticationSuite(Enum):
"""Static definition of authentication algorithm details.
.. warning:: These members must only be used as part of an AlgorithmSuite.
:param algorithm: Information needed by signing algorithm to define behavior
:type algorithm: may vary (currently only ECC curve object)
:param hash_algorithm: Hash algorithm to use in signature
:type hash_algorithm: cryptography.io hashes object
:param int signature_lenth: Number of bytes in signature
"""

NONE = (None, None, 0)
SHA256_ECDSA_P256 = (ec.SECP256R1, hashes.SHA256, 71)
SHA256_ECDSA_P384 = (ec.SECP384R1, hashes.SHA384, 103)

def __init__(self, algorithm, hash_algorithm, signature_length):
"""Prepare a new AuthenticationSuite."""
self.algorithm = algorithm
self.hash_algorithm = hash_algorithm
self.signature_length = signature_length


class AlgorithmSuite(Enum): # pylint: disable=too-many-instance-attributes
"""Static combinations of encryption, KDF, and authentication algorithms.
.. warning:: No AlgorithmSuites except those defined here are supported.
:param int algorithm_id: KMS Encryption Algorithm ID
:param encryption_algorithm: Encryption algorithm to use
:type encryption_algorithm: cryptography.io ciphers algorithm object
:param encryption_mode: Encryption mode in which to operate
:type encryption_mode: cryptography.io ciphers modes object
:param int iv_len: Number of bytes in IV
:param int auth_len: Number of bytes in auth data (tag)
:param int auth_key_len: Number of bytes in auth key (not currently supported by any algorithms)
:param int data_key_len: Number of bytes in envelope encryption data key
:param kdf_type: KDF algorithm to use
:type kdf_type: cryptography.io KDF object
:param int kdf_input_len: Number of bytes of input data to feed into KDF function
:param kdf_hash_type: Hash algorithm to use in KDF
:type kdf_hash_type: cryptography.io hashes object
:param signing_algorithm_info: Information needed by signing algorithm to define behavior
:type signing_algorithm_info: may vary (currently only ECC curve object)
:param signature_hash_type: Hash algorithm to use in signature
:type signature_hash_type: cryptography.io hashes object
:param int signature_len: Number of bytes in signature
:param encryption_suite: EncryptionSuite to use with this AlgorithmSuite
:type encryption_suite: aws_encryption_sdk.identifiers.EncryptionSuite
:param kdf_suite: KDFSuite to use with this AlgorithmSuite
:type kdf_suite: aws_encryption_sdk.identifiers.KDFSuite
:param authentication_suite: AuthenticationSuite to use with this AlgorithmSuite
:type authentication_suite: aws_encryption_sdk.identifiers.AuthenticationSuite
"""

__rlookup__ = {} # algorithm_id -> Algorithm
__rlookup__ = {} # algorithm_id -> AlgorithmSuite

AES_128_GCM_IV12_TAG16 = (0x0014, algorithms.AES, modes.GCM, 12, 16, 0, 16, None, 16, None, None, None, 0)
AES_192_GCM_IV12_TAG16 = (0x0046, algorithms.AES, modes.GCM, 12, 16, 0, 24, None, 24, None, None, None, 0)
AES_256_GCM_IV12_TAG16 = (0x0078, algorithms.AES, modes.GCM, 12, 16, 0, 32, None, 32, None, None, None, 0)
AES_128_GCM_IV12_TAG16_HKDF_SHA256 = (
0x0114, algorithms.AES, modes.GCM, 12, 16, 0, 16, hkdf.HKDF, 16, hashes.SHA256, None, None, 0
)
AES_192_GCM_IV12_TAG16_HKDF_SHA256 = (
0x0146, algorithms.AES, modes.GCM, 12, 16, 0, 24, hkdf.HKDF, 24, hashes.SHA256, None, None, 0
)
AES_256_GCM_IV12_TAG16_HKDF_SHA256 = (
0x0178, algorithms.AES, modes.GCM, 12, 16, 0, 32, hkdf.HKDF, 32, hashes.SHA256, None, None, 0
)
AES_128_GCM_IV12_TAG16 = (0x0014, EncryptionSuite.AES_128_GCM_IV12_TAG16)
AES_192_GCM_IV12_TAG16 = (0x0046, EncryptionSuite.AES_192_GCM_IV12_TAG16)
AES_256_GCM_IV12_TAG16 = (0x0078, EncryptionSuite.AES_256_GCM_IV12_TAG16)
AES_128_GCM_IV12_TAG16_HKDF_SHA256 = (0x0114, EncryptionSuite.AES_128_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
AES_192_GCM_IV12_TAG16_HKDF_SHA256 = (0x0146, EncryptionSuite.AES_192_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
AES_256_GCM_IV12_TAG16_HKDF_SHA256 = (0x0178, EncryptionSuite.AES_256_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256)
AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256 = (
0x0214, algorithms.AES, modes.GCM, 12, 16, 0, 16, hkdf.HKDF, 16, hashes.SHA256, ec.SECP256R1, hashes.SHA256, 71
0x0214, EncryptionSuite.AES_128_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256, AuthenticationSuite.SHA256_ECDSA_P256
)
AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 = (
0x0346, algorithms.AES, modes.GCM, 12, 16, 0, 24, hkdf.HKDF, 24, hashes.SHA384, ec.SECP384R1, hashes.SHA384, 103
0x0346, EncryptionSuite.AES_192_GCM_IV12_TAG16, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384
)
AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 = (
0x0378, algorithms.AES, modes.GCM, 12, 16, 0, 32, hkdf.HKDF, 32, hashes.SHA384, ec.SECP384R1, hashes.SHA384, 103
0x0378, EncryptionSuite.AES_256_GCM_IV12_TAG16, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384
)

def __init__( # pylint: disable=too-many-arguments
def __init__(
self,
algorithm_id,
encryption_algorithm,
encryption_mode,
iv_len,
auth_len,
auth_key_len,
data_key_len,
kdf_type,
kdf_input_len,
kdf_hash_type,
signing_algorithm_info,
signing_hash_type,
signature_len
algorithm_id, # type: int
encryption, # type: EncryptionSuite
kdf=KDFSuite.NONE, # type: Optional[KDFSuite]
authentication=AuthenticationSuite.NONE, # type: Optional[AuthenticationSuite]
allowed=True # type: bool
):
"""Prepares new Algorithm."""
_kdf_input_len_check(
data_key_len=data_key_len,
kdf_type=kdf_type,
kdf_input_len=kdf_input_len
)
# type: (...) -> None
"""Prepare a new AlgorithmSuite."""
self.algorithm_id = algorithm_id
self.encryption_algorithm = encryption_algorithm
self.encryption_mode = encryption_mode
self.iv_len = iv_len
# Auth keys are not currently supported
self.auth_key_len = auth_key_len
self.auth_len = self.tag_len = auth_len
self.data_key_len = data_key_len
self.kdf_type = kdf_type
self.kdf_input_len = kdf_input_len
self.kdf_hash_type = kdf_hash_type
self.signing_algorithm_info = signing_algorithm_info
self.signing_hash_type = signing_hash_type
self.signature_len = signature_len
# All algorithms in this enum are allowed for now.
# This might change in the future.
self.allowed = True
self.encryption = encryption
self.encryption.valid_kdf(kdf)
self.kdf = kdf
self.authentication = authentication
self.allowed = allowed

# Encryption Values
self.encryption_algorithm = self.encryption.algorithm
self.encryption_mode = self.encryption.mode
self.data_key_len = self.encryption.data_key_length
self.iv_len = self.encryption.iv_length
self.auth_key_len = self.encryption.auth_key_length
self.auth_len = self.tag_len = self.encryption.auth_length

# KDF Values
self.kdf_type = self.kdf.algorithm
self.kdf_hash_type = self.kdf.hash_algorithm

# Authentication Values
self.signing_algorithm_info = self.authentication.algorithm
self.signing_hash_type = self.authentication.hash_algorithm
self.signature_len = self.authentication.signature_length

self.__rlookup__[algorithm_id] = self

@property
def kdf_input_len(self):
"""Determine the correct KDF input value length for this algorithm suite."""
return self.kdf.input_length(self.encryption)

@classmethod
def get_by_id(cls, algorithm_id):
"""Returns the correct member based on the algorithm_id value.
"""Return the correct member based on the algorithm_id value.
:param algorithm_id: Value of algorithm_id field with which to retrieve Algorithm
:type algorithm_id: int
Expand All @@ -144,12 +217,15 @@ def get_by_id(cls, algorithm_id):
return cls.__rlookup__[algorithm_id]

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

def safe_to_cache(self):
"""Determines whether encryption materials for this algorithm suite should be cached."""
return self.kdf_type is not None
"""Determine whether encryption materials for this algorithm suite should be cached."""
return self.kdf is not KDFSuite.NONE


Algorithm = AlgorithmSuite


class EncryptionType(Enum):
Expand Down
38 changes: 23 additions & 15 deletions test/unit/test_identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Unit test suite for aws_encryption_sdk.identifiers"""
from mock import Mock
import pytest

from aws_encryption_sdk.exceptions import InvalidAlgorithmError
from aws_encryption_sdk.identifiers import _kdf_input_len_check, Algorithm
from aws_encryption_sdk.identifiers import Algorithm, EncryptionSuite, KDFSuite


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


def test_kdf_input_len_check_valid():
_kdf_input_len_check(
data_key_len=5,
kdf_type=5,
kdf_input_len=5
)
@pytest.mark.parametrize('suite', [suite for suite in EncryptionSuite])
def test_encryption_suite_invalid_kdf(suite):
mock_kdf = Mock()
mock_kdf.input_length.return_value = 1
with pytest.raises(InvalidAlgorithmError) as excinfo:
suite.valid_kdf(mock_kdf)

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

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

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


def test_kdf_input_len_check_invalid_with_kdf():
with pytest.raises(InvalidAlgorithmError) as excinfo:
_kdf_input_len_check(data_key_len=5, kdf_type=5, kdf_input_len=2)
@pytest.mark.parametrize('encryption, kdf, expected', build_valid_kdf_checks())
def test_encryption_suite_valid_kdf(encryption, kdf, expected):
actual = encryption.valid_kdf(kdf)

excinfo.match(r'Invalid Algorithm definition: data_key_len must not be greater than kdf_input_len')
if expected:
assert actual
else:
assert not actual

0 comments on commit f98382a

Please sign in to comment.