diff --git a/.gitignore b/.gitignore index 11bef1cc1..9285010d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,20 @@ +*.class +*.egg-info *.pyc *.pyo *~ -test/integration/test_values.conf .DS_Store -*.pyc +.tox +/.cache* +/.coverage* +/build +/doc/generated/* +/runpy +/test/integration/test_values.conf __pycache__ -dist +aws_encryption_sdk_resources build +dist docs/build -*.egg-info \ No newline at end of file +test/integration/test_values.conf +.python-version diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d491e3d53..f8e7e6951 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,29 @@ -========= +********* Changelog -========= +********* + +1.3.1 +===== + +Reorganization +-------------- +* Moved source into ``src``. +* Moved examples into ``examples``. +* Broke out ``internal.crypto`` into smaller, feature-oriented, modules. + +Tooling +------- +* Added `tox`_ configuration to support automation and development tooling. +* Added `pylint`_, `flake8`_, and `doc8`_ configuration to enforce style rules. + +Maintenance +----------- +* Updated ``internal.crypto.authentication.Verifier`` to use ``Prehashed``. +* Addressed `docstring issue #7 `_. +* Addressed `docstring issue #8 `_. +* Addressed `logging issue #10 `_. +* Addressed assorted linting issues to bring source, tests, examples, and docs up to configured + linting standards. 1.3.0 ===== @@ -17,8 +40,8 @@ Minor * Fixed attrs usage to provide consistent behavior with 16.3.0 and 17.x * Fixed performance bug which caused KDF calculations to be performed too frequently * Removed ``line_length`` as a configurable parameter of ``EncryptingStream`` and - ``DecryptingStream`` objects to simplify class APIs after it was found in further - testing to have no measurable impact on performance + ``DecryptingStream`` objects to simplify class APIs after it was found in further + testing to have no measurable impact on performance * Added deterministic length eliptic curve signature generation * Added support for calculating ciphertext message length from header * Migrated README from md to rst @@ -31,4 +54,8 @@ Minor ===== * Initial public release -.. _breaking changes in attrs 17.1.0: https://attrs.readthedocs.io/en/stable/changelog.html \ No newline at end of file +.. _breaking changes in attrs 17.1.0: https://attrs.readthedocs.io/en/stable/changelog.html +.. _tox: https://tox.readthedocs.io/en/latest/ +.. _pylint: https://www.pylint.org/ +.. _flake8: http://flake8.pycqa.org/en/latest/ +.. _doc8: https://launchpad.net/doc8 diff --git a/MANIFEST.in b/MANIFEST.in index ec4a436b4..9c401ed72 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ include README.rst +include CHANGELOG.rst +include CONTRIBUTING.rst include LICENSE include requirements.txt \ No newline at end of file diff --git a/README.rst b/README.rst index 213908bbc..dc74097bf 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ Getting Started Required Prerequisites ====================== -* Python 2.7+ or 3.x +* Python 2.7+ or 3.4+ * cryptography >= 1.8.1 * boto3 * attrs @@ -28,7 +28,7 @@ Installation detailed in the `cryptography installation guide`_ for your operating system. .. code:: - + $ pip install aws-encryption-sdk Concepts @@ -83,9 +83,9 @@ you want to reuse an existing instance of a botocore session in order to decreas import aws_encryption_sdk import botocore.session - + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider() - + existing_botocore_session = botocore.session.Session() kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(botocore_session=existing_botocore_session) @@ -98,7 +98,7 @@ will include a copy of the data key encrypted by each configured CMK. .. code:: python import aws_encryption_sdk - + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' @@ -109,7 +109,7 @@ You can add CMKs from multiple regions to the ``KMSMasterKeyProvider``. .. code:: python import aws_encryption_sdk - + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-west-2:3333333333333:key/33333333-3333-3333-3333-333333333333', @@ -125,23 +125,23 @@ high-level ``encrypt``/``decrypt`` functions to encrypt and decrypt your data. .. code:: python import aws_encryption_sdk - + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ]) my_plaintext = 'This is some super secret data! Yup, sure is!' - + my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( source=my_plaintext, key_provider=kms_key_provider ) - + decrypted_plaintext, decryptor_header = aws_encryption_sdk.decrypt( source=my_ciphertext, key_provider=kms_key_provider ) - + assert my_plaintext == decrypted_plaintext assert encryptor_header.encryption_context == decryptor_header.encryption_context @@ -150,13 +150,13 @@ You can provide an `encryption context`_: a form of additional authenticating in .. code:: python import aws_encryption_sdk - + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ]) my_plaintext = 'This is some super secret data! Yup, sure is!' - + my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( source=my_plaintext, key_provider=kms_key_provider, @@ -165,12 +165,12 @@ You can provide an `encryption context`_: a form of additional authenticating in 'but adds': 'some authentication' } ) - + decrypted_plaintext, decryptor_header = aws_encryption_sdk.decrypt( source=my_ciphertext, key_provider=kms_key_provider ) - + assert my_plaintext == decrypted_plaintext assert encryptor_header.encryption_context == decryptor_header.encryption_context @@ -186,15 +186,14 @@ offering context manager and iteration support. import aws_encryption_sdk import filecmp - + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ]) plaintext_filename = 'my-secret-data.dat' ciphertext_filename = 'my-encrypted-data.ct' - - + with open(plaintext_filename, 'rb') as pt_file, open(ciphertext_filename, 'wb') as ct_file: with aws_encryption_sdk.stream( mode='e', @@ -203,9 +202,9 @@ offering context manager and iteration support. ) as encryptor: for chunk in encryptor: ct_file.write(chunk) - + new_plaintext_filename = 'my-decrypted-data.dat' - + with open(ciphertext_filename, 'rb') as ct_file, open(new_plaintext_filename, 'wb') as pt_file: with aws_encryption_sdk.stream( mode='d', @@ -214,7 +213,7 @@ offering context manager and iteration support. ) as decryptor: for chunk in decryptor: pt_file.write(chunk) - + assert filecmp.cmp(plaintext_filename, new_plaintext_filename) assert encryptor.header.encryption_context == decryptor.header.encryption_context diff --git a/aws_encryption_sdk/internal/__init__.py b/aws_encryption_sdk/internal/__init__.py deleted file mode 100644 index 0b5368cc7..000000000 --- a/aws_encryption_sdk/internal/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Internal Implementation Details - -.. warning:: - No guarantee is provided on the modules and APIs within this - namespace staying consistent. Directly reference at your own risk. -""" diff --git a/aws_encryption_sdk/internal/crypto/__init__.py b/aws_encryption_sdk/internal/crypto/__init__.py deleted file mode 100644 index 394eef53a..000000000 --- a/aws_encryption_sdk/internal/crypto/__init__.py +++ /dev/null @@ -1,653 +0,0 @@ -"""Primary cryptographic handling functions.""" -from __future__ import division -import base64 -from collections import namedtuple -import logging -import os -import struct - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization, hashes -from cryptography.hazmat.primitives.ciphers import Cipher -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric.utils import ( - Prehashed, decode_dss_signature, encode_dss_signature -) -import cryptography.utils -import six - -from aws_encryption_sdk.exceptions import ( - NotSupportedError, InvalidDataKeyError, IncorrectMasterKeyError, SerializationError -) -from aws_encryption_sdk.internal.formatting.encryption_context import serialize_encryption_context -from aws_encryption_sdk.identifiers import EncryptionType, EncryptionKeyType -from aws_encryption_sdk.internal.str_ops import to_bytes -from aws_encryption_sdk.internal.structures import EncryptedData -from ..defaults import ENCODED_SIGNER_KEY - -_LOGGER = logging.getLogger(__name__) - - -class Encryptor(object): - """Abstract encryption handler. - - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes key: Encryption key - :param bytes associated_data: Associated Data to send to encryption subsystem - :param bytes iv: IV to use when encrypting message - """ - - def __init__(self, algorithm, key, associated_data, iv): - self.source_key = key - - # Construct an encryptor object with the given key and a provided IV. - # This is intentionally generic to leave an option for non-Cipher encryptor types in the future. - self.iv = iv - self._encryptor = Cipher( - algorithm.encryption_algorithm(key), - algorithm.encryption_mode(self.iv), - backend=default_backend() - ).encryptor() - - # associated_data will be authenticated but not encrypted, - # it must also be passed in on decryption. - self._encryptor.authenticate_additional_data(associated_data) - - def update(self, plaintext): - """Updates _encryptor with provided plaintext. - - :param bytes plaintext: Plaintext to encrypt - :returns: Encrypted ciphertext - :rtype: bytes - """ - return self._encryptor.update(plaintext) - - def finalize(self): - """Finalizes and closes _encryptor. - - :returns: Final encrypted ciphertext - :rtype: bytes - """ - return self._encryptor.finalize() - - @property - def tag(self): - """Returns the _encryptor tag from the encryption subsystem. - - :returns: Encryptor tag - :rtype: bytes - """ - return self._encryptor.tag - - -def encrypt(algorithm, key, plaintext, associated_data, iv): - """Encrypts a frame body. - - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes key: Encryption key - :param bytes plaintext: Body plaintext - :param bytes associated_data: Body AAD Data - :param bytes iv: IV to use when encrypting message - :returns: Deserialized object containing encrypted body - :rtype: aws_encryption_sdk.internal.structures.EncryptedData - """ - encryptor = Encryptor(algorithm, key, associated_data, iv) - ciphertext = encryptor.update(plaintext) + encryptor.finalize() - return EncryptedData(encryptor.iv, ciphertext, encryptor.tag) - - -class Decryptor(object): - """Abstract decryption handler. - - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes key: Raw source key - :param bytes associated_data: Associated Data to send to decryption subsystem - :param bytes iv: IV value with which to initialize decryption subsystem - :param bytes tag: Tag with which to validate ciphertext - """ - - def __init__(self, algorithm, key, associated_data, iv, tag): - self.source_key = key - - # Construct a decryptor object with the given key and a provided IV. - # This is intentionally generic to leave an option for non-Cipher decryptor types in the future. - self._decryptor = Cipher( - algorithm.encryption_algorithm(key), - algorithm.encryption_mode(iv, tag), - backend=default_backend() - ).decryptor() - - # Put associated_data back in or the tag will fail to verify when the _decryptor is finalized. - self._decryptor.authenticate_additional_data(associated_data) - - def update(self, ciphertext): - """Updates _decryptor with provided ciphertext. - - :param bytes ciphertext: Ciphertext to decrypt - :returns: Decrypted plaintext - :rtype: bytes - """ - return self._decryptor.update(ciphertext) - - def finalize(self): - """Finalizes and closes _decryptor. - - :returns: Final decrypted plaintext - :rtype: bytes - """ - return self._decryptor.finalize() - - -def decrypt(algorithm, key, encrypted_data, associated_data): - """Decrypts a frame body. - - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes key: Plaintext data key - :param encrypted_data: EncryptedData containing body data - :type encrypted_data: :class:`aws_encryption_sdk.internal.structures.EncryptedData`, - :class:`aws_encryption_sdk.internal.structures.FrameBody`, - or :class:`aws_encryption_sdk.internal.structures.MessageNoFrameBody` - :param bytes associated_data: AAD string generated for body - :type associated_data: bytes - :returns: Plaintext of body - :rtype: bytes - """ - decryptor = Decryptor(algorithm, key, associated_data, encrypted_data.iv, encrypted_data.tag) - return decryptor.update(encrypted_data.ciphertext) + decryptor.finalize() - - -def derive_data_encryption_key(source_key, algorithm, message_id): - """Derives the data encryption key using the defined algorithm. - - :param bytes source_key: Raw source key - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes message_id: Message ID - :returns: Derived data encryption key - :rtype: bytes - """ - key = source_key - if algorithm.kdf_type is not None: - key = algorithm.kdf_type( - algorithm=algorithm.kdf_hash_type(), - length=algorithm.data_key_len, - salt=None, - info=struct.pack('>H16s', algorithm.algorithm_id, message_id), - backend=default_backend() - ).derive(source_key) - return key - - -class Signer(object): - """Abstract signing handler. - - :param algorithm: Algorithm on which to base signer - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param key: Private key from which a signer can be generated - :type key: currently only Elliptic Curve Private Keys are supported - """ - - def __init__(self, algorithm, key): - self.algorithm = algorithm - self._signature_type = self._set_signature_type() - self.key = key - self._hasher = self._build_hasher() - - @classmethod - def from_key_bytes(cls, algorithm, key_bytes): - """Builds a `Signer` from an algorithm suite and a raw signing key. - - :param algorithm: Algorithm on which to base signer - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes key_bytes: Raw signing key - :rtype: aws_encryption_sdk.internal.crypto.Signer - """ - key = serialization.load_der_private_key( - data=key_bytes, - password=None, - backend=default_backend() - ) - return cls(algorithm, key) - - def key_bytes(self): - """Returns the raw signing key. - - :rtype: bytes - """ - return self.key.private_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption() - ) - - def _set_signature_type(self): - """Ensures that the algorithm signature type is a known type and sets a reference value.""" - try: - cryptography.utils.verify_interface(ec.EllipticCurve, self.algorithm.signing_algorithm_info) - return ec.EllipticCurve - except cryptography.utils.InterfaceNotImplemented: - raise NotSupportedError('Unsupported signing algorithm info') - - def _build_hasher(self): - """Builds the hasher instance which will calculate the digest of all passed data. - - :returns: Hasher object - """ - return hashes.Hash( - self.algorithm.signing_hash_type(), - backend=default_backend() - ) - - def encoded_public_key(self): - """Returns the encoded public key. - - .. note:: - For ECC curves, this will return the encoded compressed public point. - - :returns: Encoded public key from signer - :rtype: bytes - """ - return base64.b64encode(_ecc_encode_compressed_point(self.key)) - - def update(self, data): - """Updates the cryptographic signer with the supplied data. - - :param bytes data: Data to be signed - """ - self._hasher.update(data) - - def finalize(self): - """Finalizes the signer and returns the signature. - - :returns: Calculated signer signature - :rtype: bytes - """ - prehashed_digest = self._hasher.finalize() - return _ecc_static_length_signature( - key=self.key, - algorithm=self.algorithm, - digest=prehashed_digest - ) - - -class Verifier(object): - """Abstract signature verification handler. - - .. note:: - For ECC curves, the signature must be DER encoded as specified in RFC 3279. - - :param algorithm: Algorithm on which to base verifier - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param public_key: Appropriate public key object for algorithm - :type public_key: may vary - :param signature: The signature to verify (optional) - :type signature: bytes - """ - - def __init__(self, algorithm, public_key, signature=b''): - self.algorithm = algorithm - self.key = public_key - self.verifier = self._verifier(signature) - - @classmethod - def from_encoded_point(cls, algorithm, encoded_point, signature=b''): - """Creates a Verifier object based on the supplied algorithm and encoded compressed ECC curve point. - - :param algorithm: Algorithm on which to base verifier - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes encoded_point: ECC public point compressed and encoded with _ecc_encode_compressed_point - :param bytes signature: The signature to verify (optional) - :returns: Instance of Verifier generated from encoded point - :rtype: aws_encryption_sdk.internal.crypto.Verifier - """ - return cls( - algorithm=algorithm, - public_key=_ecc_public_numbers_from_compressed_point( - curve=algorithm.signing_algorithm_info(), - compressed_point=base64.b64decode(encoded_point) - ).public_key(default_backend()), - signature=signature - ) - - @classmethod - def from_key_bytes(cls, algorithm, key_bytes, signature=b''): - """Creates a `Verifier` object based on the supplied algorithm and raw verification key. - - :param algorithm: Algorithm on which to base verifier - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes encoded_point: Raw verification key - :param bytes signature: The signature to verify (optional) - :returns: Instance of Verifier generated from encoded point - :rtype: aws_encryption_sdk.internal.crypto.Verifier - """ - return cls( - algorithm=algorithm, - public_key=serialization.load_der_public_key( - data=key_bytes, - backend=default_backend() - ), - signature=signature - ) - - def key_bytes(self): - """Returns the raw verification key. - - :rtype: bytes - """ - return self.key.public_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PublicFormat.SubjectPublicKeyInfo - ) - - def _verifier(self, signature=b''): - """Creates the cryptographic verifier object. - - :param bytes signature: The signature to verify (optional) - :returns: Cryptographic verifier object - :rtype: may vary - """ - return self.key.verifier( - signature=signature, - signature_algorithm=ec.ECDSA(self.algorithm.signing_hash_type()) - ) - - def set_signature(self, signature): - """Sets the signature for the cryptographic verifier object. - - .. note:: - This is needed as the cryptography library requires - setting the signature when the verifier is created. - - :param bytes signature: The signature to verify - """ - self.verifier._signature = signature - - def update(self, data): - """Updates the cryptographic verifier with the supplied data. - - :param bytes data: Data to verify using the signature - """ - self.verifier.update(data) - - def verify(self): - """Verifies the signature against the current cryptographic verifier state.""" - self.verifier.verify() - - -# Curve parameter values are included strictly as a temporary measure -# until they can be rolled into the cryptography.io library. -# Expanded values from http://www.secg.org/sec2-v2.pdf -_ECCCurveParameters = namedtuple('_ECCCurveParameters', ['p', 'a', 'b', 'order']) -_ECC_CURVE_PARAMETERS = { - 'secp256r1': _ECCCurveParameters( - p=0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF, - a=0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC, - b=0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B, - order=0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 - ), - 'secp384r1': _ECCCurveParameters( - p=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF, - a=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC, - b=0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF, - order=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973 - ), - 'secp521r1': _ECCCurveParameters( - p=0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa - a=0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC, # noqa - b=0x0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00, # noqa - order=0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409 # noqa - ) -} - - -def _ecc_static_length_signature(key, algorithm, digest): - """Calculates an elliptic curve signature with a static length using pre-calculated hash. - - :param key: Elliptic curve private key - :type key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey - :param algorithm: Master algorithm to use - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param bytes digest: Pre-calculated hash digest - :returns: Signature with required length - :rtype: bytes - """ - pre_hashed_algorithm = ec.ECDSA(Prehashed(algorithm.signing_hash_type())) - signature = b'' - while len(signature) != algorithm.signature_len: - _LOGGER.debug( - 'Signature length %d is not desired length %d. Recalculating.', - len(signature), - algorithm.signature_len - ) - signature = key.sign(digest, pre_hashed_algorithm) - if len(signature) != algorithm.signature_len: - # Most of the time, a signature of the wrong length can be fixed - # by negating s in the signature relative to the group order. - _LOGGER.debug( - 'Signature length %d is not desired length %d. Negating s.', - len(signature), - algorithm.signature_len - ) - r, s = decode_dss_signature(signature) - s = _ECC_CURVE_PARAMETERS[algorithm.signing_algorithm_info.name].order - s - signature = encode_dss_signature(r, s) - return signature - - -def _ecc_encode_compressed_point(private_key): - """Encodes a compressed elliptic curve point - as described in SEC-1 v2 section 2.3.3 - http://www.secg.org/sec1-v2.pdf - - :param private_key: Private key from which to extract point data - :type private_key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey - :returns: Encoded compressed elliptic curve point - :rtype: bytes - :raises NotSupportedError: for non-prime curves - """ - # key_size is in bits. Convert to bytes and round up - byte_length = (private_key.curve.key_size + 7) // 8 - public_numbers = private_key.public_key().public_numbers() - y_map = [b'\x02', b'\x03'] - # If curve in prime field. - if private_key.curve.name.startswith('secp'): - yp = public_numbers.y % 2 - Y = y_map[yp] - else: - raise NotSupportedError('Non-prime curves are not supported at this time') - return Y + cryptography.utils.int_to_bytes(public_numbers.x, byte_length) - - -def _ecc_decode_compressed_point(curve, compressed_point): - """Decodes a compressed elliptic curve point - as described in SEC-1 v2 section 2.3.4 - http://www.secg.org/sec1-v2.pdf - - :param curve: Elliptic curve type to generate - :type curve: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve - :param bytes compressed_point: Encoded compressed elliptic curve point - :returns: X and Y coordinates from compressed point - :rtype: tuple of longs - :raises NotSupportedError: for non-prime curves, unsupported prime curves, and points at infinity - """ - if not compressed_point: - raise NotSupportedError('Points at infinity are not allowed') - yp_map = { - b'\x02': 0, - b'\x03': 1 - } - X = compressed_point[1:] - X = to_bytes(X) - x = cryptography.utils.int_from_bytes(X, 'big') - Y = compressed_point[0] - # In Python3, bytes index calls return int values rather than strings - if isinstance(Y, six.integer_types): - Y = six.b(chr(Y)) - elif isinstance(Y, six.string_types): - Y = six.b(Y) - yp = yp_map[Y] - # If curve in prime field. - if curve.name.startswith('secp'): - try: - params = _ECC_CURVE_PARAMETERS[curve.name] - except KeyError: - raise NotSupportedError( - 'Curve {name} is not supported at this time'.format(name=curve.name) - ) - alpha = (pow(x, 3, params.p) + (params.a * x % params.p) + params.b) % params.p - # Only works for p % 4 == 3 at this time. - # TODO: This is the case for all currently supported algorithms - # This will need to be expanded if curves which do not match this are added. - # Python-ecdsa has these algorithms implemented. Copy or reference? - # https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm - # Handbook of Applied Cryptography, algorithms 3.34 - 3.39 - if params.p % 4 == 3: - beta = pow(alpha, (params.p + 1) // 4, params.p) - else: - raise NotSupportedError('S not 1 :: Curve not supported at this time') - if beta % 2 == yp: - y = beta - else: - y = params.p - beta - else: - raise NotSupportedError('Non-prime curves are not supported at this time') - return x, y - - -def _ecc_public_numbers_from_compressed_point(curve, compressed_point): - """Decodes a compressed elliptic curve point - as described in SEC-1 v2 section 2.3.3 - and returns a PublicNumbers instance - based on the decoded point. - http://www.secg.org/sec1-v2.pdf - - :param curve: Elliptic curve type to generate - :type curve: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve - :param bytes compressed_point: Encoded compressed elliptic curve point - :returns: EllipticCurvePublicNumbers instance generated from compressed point and curve - :rtype: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers - """ - x, y = _ecc_decode_compressed_point(curve, compressed_point) - return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=curve) - - -class WrappingKey(object): - """Creates a wrapping encryption key object to encrypt and decrypt data keys. - - For use inside :class:`aws_encryption_sdk.key_providers.raw.RawMasterKeyProvider` objects. - - :param wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext_data_key - :type wrapping_algorithm: aws_encryption_sdk.identifiers.WrappingAlgorithm - :param bytes wrapping_key: Encryption key with which to wrap plaintext_data_key - :param wrapping_key_type: Type of encryption key with which to wrap plaintext_data_key - :type wrapping_key_type: aws_encryption_sdk.identifiers.EncryptionKeyType - :param str password: Password to decrypt wrapping_key (optional, currently only relevant for RSA) - """ - - def __init__(self, wrapping_algorithm, wrapping_key, wrapping_key_type, password=None): - self.wrapping_algorithm = wrapping_algorithm - self.wrapping_key_type = wrapping_key_type - if wrapping_key_type is EncryptionKeyType.PRIVATE: - self._wrapping_key = serialization.load_pem_private_key( - data=wrapping_key, - password=password, - backend=default_backend() - ) - elif wrapping_key_type is EncryptionKeyType.PUBLIC: - self._wrapping_key = serialization.load_pem_public_key( - data=wrapping_key, - backend=default_backend() - ) - elif wrapping_key_type is EncryptionKeyType.SYMMETRIC: - self._wrapping_key = wrapping_key - self._derived_wrapping_key = derive_data_encryption_key( - source_key=self._wrapping_key, - algorithm=self.wrapping_algorithm.algorithm, - message_id=None - ) - else: - raise InvalidDataKeyError('Invalid wrapping_key_type: {}'.format(wrapping_key_type)) - - def encrypt(self, plaintext_data_key, encryption_context): - """Encrypts a data key using a direct wrapping key. - - :param bytes plaintext_data_key: Data key to encrypt - :param dict encryption_context: Encryption context to use in encryption - :returns: Deserialized object containing encrypted key - :rtype: aws_encryption_sdk.internal.structures.EncryptedData - """ - if self.wrapping_algorithm.encryption_type is EncryptionType.ASYMMETRIC: - if self.wrapping_key_type is EncryptionKeyType.PRIVATE: - encrypted_key = self._wrapping_key.public_key().encrypt( - plaintext=plaintext_data_key, - padding=self.wrapping_algorithm.padding - ) - else: - encrypted_key = self._wrapping_key.encrypt( - plaintext=plaintext_data_key, - padding=self.wrapping_algorithm.padding - ) - return EncryptedData( - iv=None, - ciphertext=encrypted_key, - tag=None - ) - serialized_encryption_context = serialize_encryption_context( - encryption_context=encryption_context - ) - iv = os.urandom(self.wrapping_algorithm.algorithm.iv_len) - return encrypt( - algorithm=self.wrapping_algorithm.algorithm, - key=self._derived_wrapping_key, - plaintext=plaintext_data_key, - associated_data=serialized_encryption_context, - iv=iv - ) - - def decrypt(self, encrypted_wrapped_data_key, encryption_context): - """Decrypts a wrapped, encrypted, data key. - - :param encrypted_wrapped_data_key: Encrypted, wrapped, data key - :type encrypted_wrapped_data_key: aws_encryption_sdk.internal.structures.EncryptedData - :param dict encryption_context: Encryption context to use in decryption - :returns: Plaintext of data key - :rtype: bytes - """ - if self.wrapping_key_type is EncryptionKeyType.PUBLIC: - raise IncorrectMasterKeyError('Public key cannot decrypt') - if self.wrapping_key_type is EncryptionKeyType.PRIVATE: - return self._wrapping_key.decrypt( - ciphertext=encrypted_wrapped_data_key.ciphertext, - padding=self.wrapping_algorithm.padding - ) - serialized_encryption_context = serialize_encryption_context( - encryption_context=encryption_context - ) - return decrypt( - algorithm=self.wrapping_algorithm.algorithm, - key=self._derived_wrapping_key, - encrypted_data=encrypted_wrapped_data_key, - associated_data=serialized_encryption_context - ) - - -def generate_ecc_signing_key(algorithm): - """Returns an ECC signing key. - - :param algorithm: Algorithm object which determines what signature to generate - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :returns: Generated signing key - :raises NotSupportedError: if signing algorithm is not supported on this platform - """ - try: - cryptography.utils.verify_interface(ec.EllipticCurve, algorithm.signing_algorithm_info) - return ec.generate_private_key( - curve=algorithm.signing_algorithm_info(), - backend=default_backend() - ) - except cryptography.utils.InterfaceNotImplemented: - raise NotSupportedError('Unsupported signing algorithm info') diff --git a/aws_encryption_sdk/key_providers/__init__.py b/aws_encryption_sdk/key_providers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/doc/conf.py b/doc/conf.py index d07162ce1..068873b86 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,8 +1,35 @@ # -*- coding: utf-8 -*- +import os +import re from datetime import datetime -version = '1.2' +VERSION_RE = re.compile(r'''__version__ = ['"]([0-9.]+)['"]''') +HERE = os.path.abspath(os.path.dirname(__file__)) + + +def read(*args): + """Reads complete file contents.""" + return open(os.path.join(HERE, *args)).read() + + +def get_release(): + """Reads the release (full three-part version number) from this module.""" + init = read('..', 'src', 'aws_encryption_sdk', 'identifiers.py') + return VERSION_RE.search(init).group(1) + + +def get_version(): + """Reads the version (MAJOR.MINOR) from this module.""" + release = get_release() + split_version = release.split('.') + if len(split_version) == 3: + return '.'.join(split_version[:2]) + return release + + project = u'aws-encryption-sdk-python' +version = get_version() +release = get_release() # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -20,9 +47,6 @@ copyright = u'%s, Amazon' % datetime.now().year -# The full version, including alpha/beta/rc tags. -release = version - # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] @@ -30,7 +54,7 @@ pygments_style = 'sphinx' autoclass_content = "both" -autodoc_default_flags = ['show-inheritance', 'members']#, 'undoc-members'] +autodoc_default_flags = ['show-inheritance', 'members'] autodoc_member_order = 'bysource' html_theme = 'sphinx_rtd_theme' diff --git a/doc/index.rst b/doc/index.rst index 1ca01e2a0..e9ada3c34 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -24,8 +24,12 @@ Modules aws_encryption_sdk.streaming_client aws_encryption_sdk.structures aws_encryption_sdk.internal - aws_encryption_sdk.internal.crypto + aws_encryption_sdk.internal.crypto.authentication + aws_encryption_sdk.internal.crypto.data_keys + aws_encryption_sdk.internal.crypto.elliptic_curve + aws_encryption_sdk.internal.crypto.encryption aws_encryption_sdk.internal.crypto.iv + aws_encryption_sdk.internal.crypto.wrapping_keys aws_encryption_sdk.internal.defaults aws_encryption_sdk.internal.formatting aws_encryption_sdk.internal.formatting.deserialize diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 000000000..29e319455 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,2 @@ +sphinx>=1.3.0 +sphinx_rtd_theme \ No newline at end of file diff --git a/examples/src/__init__.py b/examples/src/__init__.py new file mode 100644 index 000000000..e8fd618b1 --- /dev/null +++ b/examples/src/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Stub module indicator to make linter configuration simpler.""" diff --git a/test/integration/docs_examples_strings.py b/examples/src/basic_encryption.py similarity index 72% rename from test/integration/docs_examples_strings.py rename to examples/src/basic_encryption.py index cb9b906b5..cf73bc83e 100644 --- a/test/integration/docs_examples_strings.py +++ b/examples/src/basic_encryption.py @@ -1,30 +1,29 @@ -""" -Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except -in compliance with the License. A copy of the License is located at - -https://aws.amazon.com/apache-2-0/ - -or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. -""" - +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Example showing basic encryption and decryption of a value already in memory.""" from __future__ import print_function import aws_encryption_sdk def cycle_string(key_arn, source_plaintext, botocore_session=None): - """Encrypts and then decrypts a string under a KMS customer master key (CMK) + """Encrypts and then decrypts a string under a KMS customer master key (CMK). :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK :param bytes source_plaintext: Data to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ - # Create a KMS master key provider kms_kwargs = dict(key_ids=[key_arn]) if botocore_session is not None: diff --git a/test/integration/docs_examples_multiple_providers.py b/examples/src/basic_file_encryption_with_multiple_providers.py similarity index 81% rename from test/integration/docs_examples_multiple_providers.py rename to examples/src/basic_file_encryption_with_multiple_providers.py index 65d514030..79dea76a2 100644 --- a/test/integration/docs_examples_multiple_providers.py +++ b/examples/src/basic_file_encryption_with_multiple_providers.py @@ -1,23 +1,26 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Example showing creation of a RawMasterKeyProvider, how to use multiple +master key providers to encrypt, and demonstrating that each master key +provider can then be used independently to decrypt the same encrypted message. """ -Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except -in compliance with the License. A copy of the License is located at - -https://aws.amazon.com/apache-2-0/ - -or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. -""" - import filecmp import os import aws_encryption_sdk -from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider -from aws_encryption_sdk.identifiers import WrappingAlgorithm, EncryptionKeyType from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa @@ -25,9 +28,11 @@ class StaticRandomMasterKeyProvider(RawMasterKeyProvider): """Randomly generates and provides 4096-bit RSA keys consistently per unique key id.""" + provider_id = 'static-random' - def __init__(self, **kwargs): + def __init__(self, **kwargs): # pylint: disable=unused-argument + """Initialize empty map of keys.""" self._static_keys = {} def _get_raw_key(self, key_id): @@ -60,16 +65,16 @@ def _get_raw_key(self, key_id): def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): """Encrypts and then decrypts a file using a KMS master key provider and a custom static master - key provider. Both master key providers are used to encrypt the plaintext file, so either one alone + key provider. Both master key providers are used to encrypt the plaintext file, so either one alone can decrypt it. - :param str key_arn: Amazon Resource Name (ARN) of the KMS Customer Master Key (CMK) (http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html) + :param str key_arn: Amazon Resource Name (ARN) of the KMS Customer Master Key (CMK) + (http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html) :param str source_plaintext_filename: Filename of file to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ - - # "Cycled" means encrypted and then decrypted + # "Cycled" means encrypted and then decrypted ciphertext_filename = source_plaintext_filename + '.encrypted' cycled_kms_plaintext_filename = source_plaintext_filename + '.kms.decrypted' cycled_static_plaintext_filename = source_plaintext_filename + '.static.decrypted' @@ -86,7 +91,7 @@ def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): static_master_key_provider.add_master_key(static_key_id) # Add the static master key provider to the KMS master key provider - # The resulting master key provider uses KMS master keys to generate (and encrypt) + # The resulting master key provider uses KMS master keys to generate (and encrypt) # data keys and static master keys to create an additional encrypted copy of each data key. kms_master_key_provider.add_master_key_provider(static_master_key_provider) @@ -124,7 +129,6 @@ def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): assert filecmp.cmp(source_plaintext_filename, cycled_kms_plaintext_filename) assert filecmp.cmp(source_plaintext_filename, cycled_static_plaintext_filename) - # Verify that the encryption context in the decrypt operation includes all key pairs from the # encrypt operation. # diff --git a/test/integration/docs_examples_bytes.py b/examples/src/basic_file_encryption_with_raw_key_provider.py similarity index 76% rename from test/integration/docs_examples_bytes.py rename to examples/src/basic_file_encryption_with_raw_key_provider.py index ef7a46e6f..2e47a1cae 100644 --- a/test/integration/docs_examples_bytes.py +++ b/examples/src/basic_file_encryption_with_raw_key_provider.py @@ -1,30 +1,32 @@ -""" -Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except -in compliance with the License. A copy of the License is located at - -https://aws.amazon.com/apache-2-0/ - -or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. -""" - +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Example showing creation and use of a RawMasterKeyProvider.""" import filecmp import os import aws_encryption_sdk -from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider -from aws_encryption_sdk.identifiers import WrappingAlgorithm, EncryptionKeyType class StaticRandomMasterKeyProvider(RawMasterKeyProvider): """Randomly generates 256-bit keys for each unique key ID.""" + provider_id = 'static-random' - def __init__(self, **kwargs): + def __init__(self, **kwargs): # pylint: disable=unused-argument + """Initialize empty map of keys.""" self._static_keys = {} def _get_raw_key(self, key_id): @@ -51,7 +53,6 @@ def cycle_file(source_plaintext_filename): :param str source_plaintext_filename: Filename of file to encrypt """ - # Create a static random master key provider key_id = os.urandom(8) master_key_provider = StaticRandomMasterKeyProvider() @@ -80,7 +81,7 @@ def cycle_file(source_plaintext_filename): for chunk in decryptor: plaintext.write(chunk) - # Verify that the "cycled" (encrypted, then decrypted) plaintext is identical to the source + # Verify that the "cycled" (encrypted, then decrypted) plaintext is identical to the source # plaintext assert filecmp.cmp(source_plaintext_filename, cycled_plaintext_filename) diff --git a/examples/src/data_key_caching_basic.py b/examples/src/data_key_caching_basic.py new file mode 100644 index 000000000..c2479e702 --- /dev/null +++ b/examples/src/data_key_caching_basic.py @@ -0,0 +1,56 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Example showing basic configuration and use of data key caching.""" +import aws_encryption_sdk + + +def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): + """Encrypts a string using an AWS KMS customer master key (CMK) and data key caching. + + :param str kms_cmk_arn: Amazon Resource Name (ARN) of the KMS customer master key + :param float max_age_in_cache: Maximum time in seconds that a cached entry can be used + :param int cache_capacity: Maximum number of entries to retain in cache at once + """ + # Data to be encrypted + my_data = 'My plaintext data' + + # Security thresholds + # Max messages (or max bytes per) data key are optional + MAX_ENTRY_MESSAGES = 100 + + # Create an encryption context. + encryption_context = {'purpose': 'test'} + + # Create a master key provider for the KMS master key + key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[kms_cmk_arn]) + + # Create a cache + cache = aws_encryption_sdk.LocalCryptoMaterialsCache(cache_capacity) + + # Create a caching CMM + caching_cmm = aws_encryption_sdk.CachingCryptoMaterialsManager( + master_key_provider=key_provider, + cache=cache, + max_age=max_age_in_cache, + max_messages_encrypted=MAX_ENTRY_MESSAGES + ) + + # When the call to encryptData specifies a caching CMM, + # the encryption operation uses the data key cache + encrypted_message, _header = aws_encryption_sdk.encrypt( + source=my_data, + materials_manager=caching_cmm, + encryption_context=encryption_context + ) + + return encrypted_message diff --git a/examples/src/pylintrc b/examples/src/pylintrc new file mode 100644 index 000000000..9d5f15039 --- /dev/null +++ b/examples/src/pylintrc @@ -0,0 +1,41 @@ +[MESSAGE CONTROL] +# Disabling messages that either we don't care about we intentionally break. +# +# C0103 : invalid-name (we prefer long, descriptive, names for examples) +# E1101 : no-member (breaks with attrs) +# R0201 : no-self-use (interesting to keep in mind for later refactoring, but not blocking) +# R0801 : duplicate-code (some examples may be similar) +# R0903 : too-few-public-methods (does not allow value stores) +# R0914 : too-many-locals (examples may sometimes have more locals defined for clarity than would be appropriate in code) +# R1705 : no-else-return (we omit this on purpose for brevity where it would add no value) +# W0201 : attribute-defined-outside-init (breaks with attrs_post_init) +# W0223 : abstract-method (throws false positives on io.BaseIO grandchildren) +# W0621 : redefined-outer-name (we do this on purpose in multiple places) +disable = C0103, E1101, R0201, R0801, R0903, R0914, R1705, W0201, W0223, W0621 + +[BASIC] +# Allow function names up to 50 characters +function-rgx = [a-z_][a-z0-9_]{2,50}$ +# Allow method names up to 50 characters +method-rgx = [a-z_][a-z0-9_]{2,50}$ +# Allow class attribute names up to 50 characters +# Whitelist class attribute names: iv +class-attribute-rgx = (([A-Za-z_][A-Za-z0-9_]{2,50}|(__.*__))$)|(^iv$) +# Whitelist attribute names: iv +attr-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$) +# Whitelist argument names: iv, b +argument-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$)|(^b$) +# Whitelist variable names: iv, b, _b, x, y, r, s +variable-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$)|(^b$)|(^_b$)|(^x$)|(^y$)|(^r$)|(^s$) + +[VARIABLES] +additional-builtins = raw_input + +[DESIGN] +max-args = 10 + +[FORMAT] +max-line-length = 120 + +[REPORTS] +msg-template = {path}:{line}: [{msg_id}({symbol}), {obj}] {msg} diff --git a/examples/test/README b/examples/test/README new file mode 100644 index 000000000..82b4f161e --- /dev/null +++ b/examples/test/README @@ -0,0 +1,3 @@ +The tests in this directory all rely on the same configuration as this library's integration tests. + +For more information see test/integration/README diff --git a/examples/test/__init__.py b/examples/test/__init__.py new file mode 100644 index 000000000..e8fd618b1 --- /dev/null +++ b/examples/test/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Stub module indicator to make linter configuration simpler.""" diff --git a/examples/test/pylintrc b/examples/test/pylintrc new file mode 100644 index 000000000..a625616c1 --- /dev/null +++ b/examples/test/pylintrc @@ -0,0 +1,25 @@ +[MESSAGES CONTROL] +# Disabling messages that we either don't care about +# for tests or are necessary to break for tests. +# +# C0103 : invalid-name (we prefer long, descriptive, names for tests) +# C0111 : missing-docstring (we don't write docstrings for tests) +# C0413 : wrong-import-position (similar to E0401, pylint does not appear to identify +# unknown modules as non-standard-library. flake8 tests for this as well +# and does treat them properly) +# E0401 : import-error (because the examples are not actually in a module, sys.path +# is patched to find tests and test utils. pylint does not recognize this) +# R0801 : duplicate-code (tests for similar things tend to be similar) +disable = C0103, C0111, C0413, E0401, R0801 + +[VARIABLES] +additional-builtins = raw_input + +[DESIGN] +max-args = 10 + +[FORMAT] +max-line-length = 120 + +[REPORTS] +msg-template = {path}:{line}: [{msg_id}({symbol}), {obj}] {msg} diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py new file mode 100644 index 000000000..d83e1fa63 --- /dev/null +++ b/examples/test/test_i_basic_encryption.py @@ -0,0 +1,38 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for the Strings examples in the AWS-hosted documentation.""" +import os +import sys +sys.path.extend([ # noqa + os.sep.join([os.path.dirname(__file__), '..', '..', 'test', 'integration']), + os.sep.join([os.path.dirname(__file__), '..', 'src']) +]) + +from basic_encryption import cycle_string +from integration_test_utils import ( + get_cmk_arn, read_test_config, setup_botocore_session, SKIP_MESSAGE, skip_tests +) +import pytest + + +@pytest.mark.skipif(skip_tests(), reason=SKIP_MESSAGE) +def test_cycle_string(): + plaintext = os.urandom(1024) + config = read_test_config() + cmk_arn = get_cmk_arn(config) + botocore_session = setup_botocore_session(config) + cycle_string( + key_arn=cmk_arn, + source_plaintext=plaintext, + botocore_session=botocore_session + ) diff --git a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py new file mode 100644 index 000000000..902a8dea5 --- /dev/null +++ b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py @@ -0,0 +1,46 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for the Bytes Streams Multiple Providers examples in the AWS-hosted documentation.""" +import os +import sys +sys.path.extend([ # noqa + os.sep.join([os.path.dirname(__file__), '..', '..', 'test', 'integration']), + os.sep.join([os.path.dirname(__file__), '..', 'src']) +]) +import tempfile + +from basic_file_encryption_with_multiple_providers import cycle_file +from integration_test_utils import ( + get_cmk_arn, read_test_config, setup_botocore_session, SKIP_MESSAGE, skip_tests +) +import pytest + + +@pytest.mark.skipif(skip_tests(), reason=SKIP_MESSAGE) +def test_cycle_file(): + config = read_test_config() + cmk_arn = get_cmk_arn(config) + botocore_session = setup_botocore_session(config) + _handle, filename = tempfile.mkstemp() + with open(filename, 'wb') as f: + f.write(os.urandom(1024)) + try: + new_files = cycle_file( + key_arn=cmk_arn, + source_plaintext_filename=filename, + botocore_session=botocore_session + ) + for f in new_files: + os.remove(f) + finally: + os.remove(filename) diff --git a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py new file mode 100644 index 000000000..573820199 --- /dev/null +++ b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py @@ -0,0 +1,37 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for the Bytes Streams examples in the AWS-hosted documentation.""" +import os +import sys +sys.path.extend([ # noqa + os.sep.join([os.path.dirname(__file__), '..', '..', 'test', 'integration']), + os.sep.join([os.path.dirname(__file__), '..', 'src']) +]) +import tempfile + +from basic_file_encryption_with_raw_key_provider import cycle_file +from integration_test_utils import SKIP_MESSAGE, skip_tests +import pytest + + +@pytest.mark.skipif(skip_tests(), reason=SKIP_MESSAGE) +def test_cycle_file(): + _handle, filename = tempfile.mkstemp() + with open(filename, 'wb') as f: + f.write(os.urandom(1024)) + try: + new_files = cycle_file(source_plaintext_filename=filename) + for f in new_files: + os.remove(f) + finally: + os.remove(filename) diff --git a/examples/test/test_i_data_key_caching_basic.py b/examples/test/test_i_data_key_caching_basic.py new file mode 100644 index 000000000..5da304d65 --- /dev/null +++ b/examples/test/test_i_data_key_caching_basic.py @@ -0,0 +1,34 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for the basic data key caching example in the AWS-hosted documentation.""" +import os +import sys +sys.path.extend([ # noqa + os.sep.join([os.path.dirname(__file__), '..', '..', 'test', 'integration']), + os.sep.join([os.path.dirname(__file__), '..', 'src']) +]) + +from data_key_caching_basic import encrypt_with_caching +from integration_test_utils import get_cmk_arn, read_test_config, SKIP_MESSAGE, skip_tests +import pytest + + +@pytest.mark.skipif(skip_tests(), reason=SKIP_MESSAGE) +def test_encrypt_with_caching(): + config = read_test_config() + cmk_arn = get_cmk_arn(config) + encrypt_with_caching( + kms_cmk_arn=cmk_arn, + max_age_in_cache=10.0, + cache_capacity=10 + ) diff --git a/pylintrc b/pylintrc new file mode 100644 index 000000000..1431bc1ed --- /dev/null +++ b/pylintrc @@ -0,0 +1,38 @@ +[MESSAGE CONTROL] +# Disabling messages that either we don't care about we intentionally break. +# +# E1101 : no-member (breaks with attrs) +# R0201 : no-self-use (interesting to keep in mind for later refactoring, but not blocking) +# R0903 : too-few-public-methods (does not allow value stores) +# R1705 : no-else-return (we omit this on purpose for brevity where it would add no value) +# W0201 : attribute-defined-outside-init (breaks with attrs_post_init) +# W0223 : abstract-method (throws false positives on io.BaseIO grandchildren) +# W0621 : redefined-outer-name (we do this on purpose in multiple places) +disable = E1101, R0201, R0903, R1705, W0201, W0223, W0621 + +[BASIC] +# Allow function names up to 50 characters +function-rgx = [a-z_][a-z0-9_]{2,50}$ +# Allow method names up to 50 characters +method-rgx = [a-z_][a-z0-9_]{2,50}$ +# Allow class attribute names up to 50 characters +# Whitelist class attribute names: iv +class-attribute-rgx = (([A-Za-z_][A-Za-z0-9_]{2,50}|(__.*__))$)|(^iv$) +# Whitelist attribute names: iv +attr-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$) +# Whitelist argument names: iv, b +argument-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$)|(^b$) +# Whitelist variable names: iv, b, _b, x, y, r, s +variable-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$)|(^b$)|(^_b$)|(^x$)|(^y$)|(^r$)|(^s$) + +[VARIABLES] +additional-builtins = raw_input + +[DESIGN] +max-args = 10 + +[FORMAT] +max-line-length = 120 + +[REPORTS] +msg-template = {path}:{line}: [{msg_id}({symbol}), {obj}] {msg} diff --git a/setup.cfg b/setup.cfg index 1451b1703..2e5e9d47c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,10 @@ universal = 1 [metadata] -license_file = LICENSE \ No newline at end of file +license_file = LICENSE + +[coverage:run] +branch = True + +[coverage:report] +show_missing = True diff --git a/setup.py b/setup.py index d934fc584..e59de60a2 100644 --- a/setup.py +++ b/setup.py @@ -1,38 +1,44 @@ -#!/usr/bin/env python +"""AWS Encryption SDK for Python.""" import os import re -from setuptools import setup, find_packages +from setuptools import find_packages, setup VERSION_RE = re.compile(r'''__version__ = ['"]([0-9.]+)['"]''') HERE = os.path.abspath(os.path.dirname(__file__)) def read(*args): + """Reads complete file contents.""" return open(os.path.join(HERE, *args)).read() def get_version(): - init = read('aws_encryption_sdk', 'identifiers.py') + """Reads the version from this module.""" + init = read('src', 'aws_encryption_sdk', 'identifiers.py') return VERSION_RE.search(init).group(1) +def get_requirements(): + """Reads the requirements file.""" + requirements = read('requirements.txt') + return [r for r in requirements.strip().splitlines()] + + setup( name='aws-encryption-sdk', - packages=find_packages(exclude=['test*']), + packages=find_packages('src'), + package_dir={'': 'src'}, version=get_version(), author='Amazon Web Services', maintainer='Amazon Web Services', + author_email='aws-cryptools@amazon.com', url='https://github.com/awslabs/aws-encryption-sdk-python', description='AWS Encryption SDK implementation for Python', long_description=read('README.rst'), keywords='aws-encryption-sdk aws kms encryption', license='Apache License 2.0', - install_requires=[ - 'boto3>=1.4.4', - 'cryptography>=1.8.1', - 'attrs>=16.3.0' - ], + install_requires=get_requirements(), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', diff --git a/aws_encryption_sdk/__init__.py b/src/aws_encryption_sdk/__init__.py similarity index 91% rename from aws_encryption_sdk/__init__.py rename to src/aws_encryption_sdk/__init__.py index 097d66c31..bc0f8414d 100644 --- a/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -1,9 +1,21 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """High level AWS Encryption SDK client functions.""" from aws_encryption_sdk.streaming_client import ( # noqa StreamEncryptor, StreamDecryptor, EncryptorConfig, DecryptorConfig ) # Below are imported for ease of use by implementors -from aws_encryption_sdk.identifiers import Algorithm, __version__ # noqa +from aws_encryption_sdk.identifiers import __version__, Algorithm # noqa from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider, KMSMasterKeyProviderConfig # noqa from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager # noqa from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager # noqa @@ -58,7 +70,7 @@ def encrypt(**kwargs): :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param int frame_length: Frame length in bytes :returns: Tuple containing the encrypted ciphertext and the message header object - :rtype: tuple of str and :class:`aws_encryption_sdk.structures.MessageHeader` + :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` """ with StreamEncryptor(**kwargs) as encryptor: ciphertext = encryptor.read() @@ -103,7 +115,7 @@ def decrypt(**kwargs): :param int max_body_length: Maximum frame size (or content length for non-framed messages) in bytes to read from ciphertext message. :returns: Tuple containing the decrypted plaintext and the message header object - :rtype: tuple of str and :class:`aws_encryption_sdk.structures.MessageHeader` + :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` """ with StreamDecryptor(**kwargs) as decryptor: plaintext = decryptor.read() diff --git a/aws_encryption_sdk/caches/__init__.py b/src/aws_encryption_sdk/caches/__init__.py similarity index 87% rename from aws_encryption_sdk/caches/__init__.py rename to src/aws_encryption_sdk/caches/__init__.py index 9dbefecf7..e01cc6690 100644 --- a/aws_encryption_sdk/caches/__init__.py +++ b/src/aws_encryption_sdk/caches/__init__.py @@ -1,20 +1,30 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Common functions and structures for use in cryptographic materials caches. .. versionadded:: 1.3.0 """ from threading import Lock import time -import uuid import attr -from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend -import six +from cryptography.hazmat.primitives import hashes from ..exceptions import NotSupportedError -from ..materials_managers import EncryptionMaterials, DecryptionMaterials from ..internal.formatting.encryption_context import serialize_encryption_context from ..internal.formatting.serialize import serialize_encrypted_data_key +from ..materials_managers import DecryptionMaterials, EncryptionMaterials def _new_cache_key_hasher(): @@ -145,6 +155,7 @@ class CryptoMaterialsCacheEntryHints(object): :param float lifetime: Number of seconds to retain entry in cache (optional) """ + lifetime = attr.ib( default=None, validator=attr.validators.optional(attr.validators.instance_of(float)) @@ -160,6 +171,7 @@ class CryptoMaterialsCacheEntry(object): :param hints: Metadata to associate with entry (optional) :type hints: aws_encryption_sdk.caches.CryptoMaterialsCacheEntryHints """ + cache_key = attr.ib(validator=attr.validators.instance_of(bytes)) value = attr.ib(validator=attr.validators.instance_of((EncryptionMaterials, DecryptionMaterials))) hints = attr.ib( @@ -168,6 +180,7 @@ class CryptoMaterialsCacheEntry(object): ) def __attrs_post_init__(self): + """Prepares initial values.""" self.creation_time = time.time() self.bytes_encrypted = 0 self.messages_encrypted = 0 @@ -180,7 +193,8 @@ def __attrs_post_init__(self): def __setattr__(self, name, value): """Disable setting of attributes after __attrs_post_init__ has run. This provides a bit - more certainty that usage values have not been modified.""" + more certainty that usage values have not been modified. + """ if hasattr(self, '_init_completed'): raise NotSupportedError('Attributes may not be set on CryptoMaterialsCacheEntry objects') return super(CryptoMaterialsCacheEntry, self).__setattr__(name, value) @@ -209,8 +223,14 @@ def _update_with_message_bytes_encrypted(self, bytes_encrypted): :param int bytes_encrypted: Number of bytes encrypted in registered use. """ with self._lock: - super(CryptoMaterialsCacheEntry, self).__setattr__('messages_encrypted', self.messages_encrypted + 1) - super(CryptoMaterialsCacheEntry, self).__setattr__('bytes_encrypted', self.bytes_encrypted + bytes_encrypted) + super(CryptoMaterialsCacheEntry, self).__setattr__( + 'messages_encrypted', + self.messages_encrypted + 1 + ) + super(CryptoMaterialsCacheEntry, self).__setattr__( + 'bytes_encrypted', + self.bytes_encrypted + bytes_encrypted + ) def invalidate(self): """Marks a cache entry as invalidated.""" diff --git a/aws_encryption_sdk/caches/base.py b/src/aws_encryption_sdk/caches/base.py similarity index 81% rename from aws_encryption_sdk/caches/base.py rename to src/aws_encryption_sdk/caches/base.py index 3cf23322d..1d923ab2b 100644 --- a/aws_encryption_sdk/caches/base.py +++ b/src/aws_encryption_sdk/caches/base.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Base class interface for caches for use with caching crypto material managers.""" import abc diff --git a/aws_encryption_sdk/caches/local.py b/src/aws_encryption_sdk/caches/local.py similarity index 92% rename from aws_encryption_sdk/caches/local.py rename to src/aws_encryption_sdk/caches/local.py index 24843d900..2aab6c963 100644 --- a/aws_encryption_sdk/caches/local.py +++ b/src/aws_encryption_sdk/caches/local.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Local, in-memory, LRU, cryptographic materials cache for use with caching cryptographic materials providers.""" from collections import deque, OrderedDict import logging @@ -7,10 +19,9 @@ import attr import six -from ..exceptions import CacheKeyError, NotSupportedError - from . import CryptoMaterialsCacheEntry from .base import CryptoMaterialsCache +from ..exceptions import CacheKeyError, NotSupportedError _OPPORTUNISTIC_EVICTION_ROUNDS = 10 _LOGGER = logging.getLogger(__name__) @@ -24,9 +35,11 @@ class LocalCryptoMaterialsCache(CryptoMaterialsCache): :param int capacity: Maximum number of entries to retain in cache at once """ + capacity = attr.ib(validator=attr.validators.instance_of(six.integer_types)) def __attrs_post_init__(self): + """Prepares initial values not handled by attrs.""" if self.capacity < 1: raise ValueError('LocalCryptoMaterialsCache capacity cannot be less than 1') self._cache_lock = RLock() @@ -113,7 +126,7 @@ def put_encryption_materials(self, cache_key, encryption_materials, plaintext_le value=encryption_materials, hints=entry_hints ) - entry._update_with_message_bytes_encrypted(plaintext_length) + entry._update_with_message_bytes_encrypted(plaintext_length) # pylint: disable=protected-access with self._cache_lock: self._try_to_evict_some_entries() self._add_value_to_cache(entry) @@ -187,7 +200,7 @@ def get_encryption_materials(self, cache_key, plaintext_length): _LOGGER.debug('Looking in cache for encryption materials to encrypt %d bytes.', plaintext_length) with self._cache_lock: entry = self._get_single_entry(cache_key) - entry._update_with_message_bytes_encrypted(plaintext_length) + entry._update_with_message_bytes_encrypted(plaintext_length) # pylint: disable=protected-access return entry def get_decryption_materials(self, cache_key): diff --git a/aws_encryption_sdk/caches/null.py b/src/aws_encryption_sdk/caches/null.py similarity index 82% rename from aws_encryption_sdk/caches/null.py rename to src/aws_encryption_sdk/caches/null.py index cf3ae5b17..115671e02 100644 --- a/aws_encryption_sdk/caches/null.py +++ b/src/aws_encryption_sdk/caches/null.py @@ -1,8 +1,19 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Null cache: a cache which does not cache.""" -from ..exceptions import CacheKeyError - from . import CryptoMaterialsCacheEntry from .base import CryptoMaterialsCache +from ..exceptions import CacheKeyError class NullCryptoMaterialsCache(CryptoMaterialsCache): diff --git a/aws_encryption_sdk/exceptions.py b/src/aws_encryption_sdk/exceptions.py similarity index 83% rename from aws_encryption_sdk/exceptions.py rename to src/aws_encryption_sdk/exceptions.py index 804bc13ef..a71d414c0 100644 --- a/aws_encryption_sdk/exceptions.py +++ b/src/aws_encryption_sdk/exceptions.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Contains exception classes for AWS Encryption SDK.""" diff --git a/aws_encryption_sdk/identifiers.py b/src/aws_encryption_sdk/identifiers.py similarity index 86% rename from aws_encryption_sdk/identifiers.py rename to src/aws_encryption_sdk/identifiers.py index 3946fc5cf..d9432718b 100644 --- a/aws_encryption_sdk/identifiers.py +++ b/src/aws_encryption_sdk/identifiers.py @@ -1,17 +1,28 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """AWS Encryption SDK native data structures for defining implementation-specific characteristics.""" -import struct - from enum import Enum +import struct from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding +from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa from cryptography.hazmat.primitives.ciphers import algorithms, modes from cryptography.hazmat.primitives.kdf import hkdf from aws_encryption_sdk.exceptions import InvalidAlgorithmError -__version__ = '1.3.0' -user_agent_suffix = 'AwsEncryptionSdkPython-KMSMasterKey/{}'.format(__version__) +__version__ = '1.3.1' +USER_AGENT_SUFFIX = 'AwsEncryptionSdkPython-KMSMasterKey/{}'.format(__version__) def _kdf_input_len_check(data_key_len, kdf_type, kdf_input_len): @@ -32,7 +43,7 @@ def _kdf_input_len_check(data_key_len, kdf_type, kdf_input_len): ) -class Algorithm(Enum): +class Algorithm(Enum): # pylint: disable=too-many-instance-attributes """IDs of cryptographic algorithms this library knows about. :param int algorithm_id: KMS Encryption Algorithm ID @@ -55,6 +66,7 @@ class Algorithm(Enum): :type signature_hash_type: cryptography.io hashes object :param int signature_len: Number of bytes in signature """ + __rlookup__ = {} # algorithm_id -> Algorithm AES_128_GCM_IV12_TAG16 = (0x0014, algorithms.AES, modes.GCM, 12, 16, 0, 16, None, 16, None, None, None, 0) @@ -79,22 +91,23 @@ class Algorithm(Enum): 0x0378, algorithms.AES, modes.GCM, 12, 16, 0, 32, hkdf.HKDF, 32, hashes.SHA384, ec.SECP384R1, hashes.SHA384, 103 ) - 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 + def __init__( # pylint: disable=too-many-arguments + 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 ): + """Prepares new Algorithm.""" _kdf_input_len_check( data_key_len=data_key_len, kdf_type=kdf_type, @@ -141,12 +154,14 @@ def safe_to_cache(self): class EncryptionType(Enum): """Identifies symmetric vs asymmetric encryption. Used to identify encryption type for WrappingAlgorithm.""" + SYMMETRIC = 0 ASYMMETRIC = 1 class EncryptionKeyType(Enum): """Identifies raw encryption key type. Used to identify key capabilities for WrappingAlgorithm.""" + SYMMETRIC = 0 PUBLIC = 1 PRIVATE = 2 @@ -173,6 +188,7 @@ class WrappingAlgorithm(Enum): RSA_OAEP_SHA256_MGF1 = (EncryptionType.ASYMMETRIC, rsa, padding.OAEP, hashes.SHA256, padding.MGF1) def __init__(self, encryption_type, algorithm, padding_type, padding_algorithm, padding_mgf): + """Prepares new WrappingAlgorithm.""" self.encryption_type = encryption_type self.algorithm = algorithm if padding_type == padding.OAEP: @@ -192,27 +208,32 @@ def __init__(self, encryption_type, algorithm, padding_type, padding_algorithm, class ObjectType(Enum): """Valid Type values per the AWS Encryption SDK message format.""" + CUSTOMER_AE_DATA = 128 class SequenceIdentifier(Enum): """Identifiers for specific sequence frames.""" + SEQUENCE_NUMBER_END = 0xFFFFFFFF class SerializationVersion(Enum): """Valid Versions of AWS Encryption SDK message format.""" - V1 = 1 + + V1 = 1 # pylint: disable=invalid-name class ContentType(Enum): """Type of content framing contained in message.""" + NO_FRAMING = 1 FRAMED_DATA = 2 class ContentAADString(Enum): """Body Additional Authenticated Data values for building the AAD for a message body.""" + FRAME_STRING_ID = b'AWSKMSEncryptionClient Frame' FINAL_FRAME_STRING_ID = b'AWSKMSEncryptionClient Final Frame' NON_FRAMED_STRING_ID = b'AWSKMSEncryptionClient Single Block' diff --git a/src/aws_encryption_sdk/internal/__init__.py b/src/aws_encryption_sdk/internal/__init__.py new file mode 100644 index 000000000..7a78f48ea --- /dev/null +++ b/src/aws_encryption_sdk/internal/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Internal Implementation Details + +.. warning:: + No guarantee is provided on the modules and APIs within this + namespace staying consistent. Directly reference at your own risk. +""" diff --git a/src/aws_encryption_sdk/internal/crypto/__init__.py b/src/aws_encryption_sdk/internal/crypto/__init__.py new file mode 100644 index 000000000..9717f65bd --- /dev/null +++ b/src/aws_encryption_sdk/internal/crypto/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Cryptographic modules.""" +# Backwards compatible import for use by RawMasterKeyProvider implementations. +from .wrapping_keys import WrappingKey # noqa diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py new file mode 100644 index 000000000..aa176d912 --- /dev/null +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -0,0 +1,208 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Contains authentication primitives.""" +import base64 +import logging + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed +from cryptography.utils import InterfaceNotImplemented, verify_interface + +from .elliptic_curve import ( + _ecc_encode_compressed_point, _ecc_public_numbers_from_compressed_point, _ecc_static_length_signature +) +from ...exceptions import NotSupportedError + +_LOGGER = logging.getLogger(__name__) + + +class _PrehashingAuthenticator(object): + """Parent class for Signer/Verifier. Provides common behavior and interface. + + :param algorithm: Algorithm on which to base authenticator + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param key: Key with which to build authenticator + """ + + def __init__(self, algorithm, key): + """Prepares initial values.""" + self.algorithm = algorithm + self._signature_type = self._set_signature_type() + self.key = key + self._hasher = self._build_hasher() + + def _set_signature_type(self): + """Ensures that the algorithm signature type is a known type and sets a reference value.""" + try: + verify_interface(ec.EllipticCurve, self.algorithm.signing_algorithm_info) + return ec.EllipticCurve + except InterfaceNotImplemented: + raise NotSupportedError('Unsupported signing algorithm info') + + def _build_hasher(self): + """Builds the hasher instance which will calculate the digest of all passed data. + + :returns: Hasher object + """ + return hashes.Hash( + self.algorithm.signing_hash_type(), + backend=default_backend() + ) + + +class Signer(_PrehashingAuthenticator): + """Abstract signing handler. + + :param algorithm: Algorithm on which to base signer + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param key: Private key from which a signer can be generated + :type key: currently only Elliptic Curve Private Keys are supported + """ + + @classmethod + def from_key_bytes(cls, algorithm, key_bytes): + """Builds a `Signer` from an algorithm suite and a raw signing key. + + :param algorithm: Algorithm on which to base signer + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes key_bytes: Raw signing key + :rtype: aws_encryption_sdk.internal.crypto.Signer + """ + key = serialization.load_der_private_key( + data=key_bytes, + password=None, + backend=default_backend() + ) + return cls(algorithm, key) + + def key_bytes(self): + """Returns the raw signing key. + + :rtype: bytes + """ + return self.key.private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + + def encoded_public_key(self): + """Returns the encoded public key. + + .. note:: + For ECC curves, this will return the encoded compressed public point. + + :returns: Encoded public key from signer + :rtype: bytes + """ + return base64.b64encode(_ecc_encode_compressed_point(self.key)) + + def update(self, data): + """Updates the cryptographic signer with the supplied data. + + :param bytes data: Data to be signed + """ + self._hasher.update(data) + + def finalize(self): + """Finalizes the signer and returns the signature. + + :returns: Calculated signer signature + :rtype: bytes + """ + prehashed_digest = self._hasher.finalize() + return _ecc_static_length_signature( + key=self.key, + algorithm=self.algorithm, + digest=prehashed_digest + ) + + +class Verifier(_PrehashingAuthenticator): + """Abstract signature verification handler. + + .. note:: + For ECC curves, the signature must be DER encoded as specified in RFC 3279. + + :param algorithm: Algorithm on which to base verifier + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param public_key: Appropriate public key object for algorithm + :type public_key: may vary + """ + + @classmethod + def from_encoded_point(cls, algorithm, encoded_point): + """Creates a Verifier object based on the supplied algorithm and encoded compressed ECC curve point. + + :param algorithm: Algorithm on which to base verifier + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes encoded_point: ECC public point compressed and encoded with _ecc_encode_compressed_point + :returns: Instance of Verifier generated from encoded point + :rtype: aws_encryption_sdk.internal.crypto.Verifier + """ + return cls( + algorithm=algorithm, + key=_ecc_public_numbers_from_compressed_point( + curve=algorithm.signing_algorithm_info(), + compressed_point=base64.b64decode(encoded_point) + ).public_key(default_backend()) + ) + + @classmethod + def from_key_bytes(cls, algorithm, key_bytes): + """Creates a `Verifier` object based on the supplied algorithm and raw verification key. + + :param algorithm: Algorithm on which to base verifier + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes encoded_point: Raw verification key + :returns: Instance of Verifier generated from encoded point + :rtype: aws_encryption_sdk.internal.crypto.Verifier + """ + return cls( + algorithm=algorithm, + key=serialization.load_der_public_key( + data=key_bytes, + backend=default_backend() + ) + ) + + def key_bytes(self): + """Returns the raw verification key. + + :rtype: bytes + """ + return self.key.public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def update(self, data): + """Updates the cryptographic verifier with the supplied data. + + :param bytes data: Data to verify using the signature + """ + self._hasher.update(data) + + def verify(self, signature): + """Verifies the signature against the current cryptographic verifier state. + + :param bytes signature: The signature to verify + """ + prehashed_digest = self._hasher.finalize() + self.key.verify( + signature=signature, + data=prehashed_digest, + signature_algorithm=ec.ECDSA(Prehashed(self.algorithm.signing_hash_type())) + ) diff --git a/src/aws_encryption_sdk/internal/crypto/data_keys.py b/src/aws_encryption_sdk/internal/crypto/data_keys.py new file mode 100644 index 000000000..bdec635aa --- /dev/null +++ b/src/aws_encryption_sdk/internal/crypto/data_keys.py @@ -0,0 +1,41 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Contains data key helper functions.""" +import logging +import struct + +from cryptography.hazmat.backends import default_backend + +_LOGGER = logging.getLogger(__name__) + + +def derive_data_encryption_key(source_key, algorithm, message_id): + """Derives the data encryption key using the defined algorithm. + + :param bytes source_key: Raw source key + :param algorithm: Algorithm used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes message_id: Message ID + :returns: Derived data encryption key + :rtype: bytes + """ + key = source_key + if algorithm.kdf_type is not None: + key = algorithm.kdf_type( + algorithm=algorithm.kdf_hash_type(), + length=algorithm.data_key_len, + salt=None, + info=struct.pack('>H16s', algorithm.algorithm_id, message_id), + backend=default_backend() + ).derive(source_key) + return key diff --git a/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py b/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py new file mode 100644 index 000000000..7172756f6 --- /dev/null +++ b/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py @@ -0,0 +1,201 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Contains elliptic curve functionality.""" +from collections import namedtuple +import logging + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature, Prehashed +from cryptography.utils import int_from_bytes, int_to_bytes, InterfaceNotImplemented, verify_interface +import six + +from ..str_ops import to_bytes +from ...exceptions import NotSupportedError + +_LOGGER = logging.getLogger(__name__) + + +# Curve parameter values are included strictly as a temporary measure +# until they can be rolled into the cryptography.io library. +# Expanded values from http://www.secg.org/sec2-v2.pdf +_ECCCurveParameters = namedtuple('_ECCCurveParameters', ['p', 'a', 'b', 'order']) +_ECC_CURVE_PARAMETERS = { + 'secp256r1': _ECCCurveParameters( + p=0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF, + a=0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC, + b=0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B, + order=0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + ), + 'secp384r1': _ECCCurveParameters( + p=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF, + a=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC, + b=0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF, + order=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973 + ), + 'secp521r1': _ECCCurveParameters( + p=0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa pylint: disable=line-too-long + a=0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC, # noqa pylint: disable=line-too-long + b=0x0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00, # noqa pylint: disable=line-too-long + order=0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409 # noqa pylint: disable=line-too-long + ) +} + + +def _ecc_static_length_signature(key, algorithm, digest): + """Calculates an elliptic curve signature with a static length using pre-calculated hash. + + :param key: Elliptic curve private key + :type key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey + :param algorithm: Master algorithm to use + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes digest: Pre-calculated hash digest + :returns: Signature with required length + :rtype: bytes + """ + pre_hashed_algorithm = ec.ECDSA(Prehashed(algorithm.signing_hash_type())) + signature = b'' + while len(signature) != algorithm.signature_len: + _LOGGER.debug( + 'Signature length %d is not desired length %d. Recalculating.', + len(signature), + algorithm.signature_len + ) + signature = key.sign(digest, pre_hashed_algorithm) + if len(signature) != algorithm.signature_len: + # Most of the time, a signature of the wrong length can be fixed + # by negating s in the signature relative to the group order. + _LOGGER.debug( + 'Signature length %d is not desired length %d. Negating s.', + len(signature), + algorithm.signature_len + ) + r, s = decode_dss_signature(signature) + s = _ECC_CURVE_PARAMETERS[algorithm.signing_algorithm_info.name].order - s + signature = encode_dss_signature(r, s) + return signature + + +def _ecc_encode_compressed_point(private_key): + """Encodes a compressed elliptic curve point + as described in SEC-1 v2 section 2.3.3 + http://www.secg.org/sec1-v2.pdf + + :param private_key: Private key from which to extract point data + :type private_key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey + :returns: Encoded compressed elliptic curve point + :rtype: bytes + :raises NotSupportedError: for non-prime curves + """ + # key_size is in bits. Convert to bytes and round up + byte_length = (private_key.curve.key_size + 7) // 8 + public_numbers = private_key.public_key().public_numbers() + y_map = [b'\x02', b'\x03'] + # If curve in prime field. + if private_key.curve.name.startswith('secp'): + y_order = public_numbers.y % 2 + y = y_map[y_order] + else: + raise NotSupportedError('Non-prime curves are not supported at this time') + return y + int_to_bytes(public_numbers.x, byte_length) + + +def _ecc_decode_compressed_point(curve, compressed_point): + """Decodes a compressed elliptic curve point + as described in SEC-1 v2 section 2.3.4 + http://www.secg.org/sec1-v2.pdf + + :param curve: Elliptic curve type to generate + :type curve: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve + :param bytes compressed_point: Encoded compressed elliptic curve point + :returns: X and Y coordinates from compressed point + :rtype: tuple of longs + :raises NotSupportedError: for non-prime curves, unsupported prime curves, and points at infinity + """ + if not compressed_point: + raise NotSupportedError('Points at infinity are not allowed') + y_order_map = { + b'\x02': 0, + b'\x03': 1 + } + raw_x = compressed_point[1:] + raw_x = to_bytes(raw_x) + x = int_from_bytes(raw_x, 'big') + raw_y = compressed_point[0] + # In Python3, bytes index calls return int values rather than strings + if isinstance(raw_y, six.integer_types): + raw_y = six.b(chr(raw_y)) + elif isinstance(raw_y, six.string_types): + raw_y = six.b(raw_y) + y_order = y_order_map[raw_y] + # If curve in prime field. + if curve.name.startswith('secp'): + try: + params = _ECC_CURVE_PARAMETERS[curve.name] + except KeyError: + raise NotSupportedError( + 'Curve {name} is not supported at this time'.format(name=curve.name) + ) + alpha = (pow(x, 3, params.p) + (params.a * x % params.p) + params.b) % params.p + # Only works for p % 4 == 3 at this time. + # This is the case for all currently supported algorithms. + # This will need to be expanded if curves which do not match this are added. + # Python-ecdsa has these algorithms implemented. Copy or reference? + # https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm + # Handbook of Applied Cryptography, algorithms 3.34 - 3.39 + if params.p % 4 == 3: + beta = pow(alpha, (params.p + 1) // 4, params.p) + else: + raise NotSupportedError('S not 1 :: Curve not supported at this time') + if beta % 2 == y_order: + y = beta + else: + y = params.p - beta + else: + raise NotSupportedError('Non-prime curves are not supported at this time') + return x, y + + +def _ecc_public_numbers_from_compressed_point(curve, compressed_point): + """Decodes a compressed elliptic curve point + as described in SEC-1 v2 section 2.3.3 + and returns a PublicNumbers instance + based on the decoded point. + http://www.secg.org/sec1-v2.pdf + + :param curve: Elliptic curve type to generate + :type curve: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve + :param bytes compressed_point: Encoded compressed elliptic curve point + :returns: EllipticCurvePublicNumbers instance generated from compressed point and curve + :rtype: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers + """ + x, y = _ecc_decode_compressed_point(curve, compressed_point) + return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=curve) + + +def generate_ecc_signing_key(algorithm): + """Returns an ECC signing key. + + :param algorithm: Algorithm object which determines what signature to generate + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :returns: Generated signing key + :raises NotSupportedError: if signing algorithm is not supported on this platform + """ + try: + verify_interface(ec.EllipticCurve, algorithm.signing_algorithm_info) + return ec.generate_private_key( + curve=algorithm.signing_algorithm_info(), + backend=default_backend() + ) + except InterfaceNotImplemented: + raise NotSupportedError('Unsupported signing algorithm info') diff --git a/src/aws_encryption_sdk/internal/crypto/encryption.py b/src/aws_encryption_sdk/internal/crypto/encryption.py new file mode 100644 index 000000000..4e25aea12 --- /dev/null +++ b/src/aws_encryption_sdk/internal/crypto/encryption.py @@ -0,0 +1,155 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Contains encryption primitives and helper functions.""" +import logging + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import Cipher + +from ..structures import EncryptedData + +_LOGGER = logging.getLogger(__name__) + + +class Encryptor(object): + """Abstract encryption handler. + + :param algorithm: Algorithm used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes key: Encryption key + :param bytes associated_data: Associated Data to send to encryption subsystem + :param bytes iv: IV to use when encrypting message + """ + + def __init__(self, algorithm, key, associated_data, iv): + """Prepares initial values.""" + self.source_key = key + + # Construct an encryptor object with the given key and a provided IV. + # This is intentionally generic to leave an option for non-Cipher encryptor types in the future. + self.iv = iv + self._encryptor = Cipher( + algorithm.encryption_algorithm(key), + algorithm.encryption_mode(self.iv), + backend=default_backend() + ).encryptor() + + # associated_data will be authenticated but not encrypted, + # it must also be passed in on decryption. + self._encryptor.authenticate_additional_data(associated_data) + + def update(self, plaintext): + """Updates _encryptor with provided plaintext. + + :param bytes plaintext: Plaintext to encrypt + :returns: Encrypted ciphertext + :rtype: bytes + """ + return self._encryptor.update(plaintext) + + def finalize(self): + """Finalizes and closes _encryptor. + + :returns: Final encrypted ciphertext + :rtype: bytes + """ + return self._encryptor.finalize() + + @property + def tag(self): + """Returns the _encryptor tag from the encryption subsystem. + + :returns: Encryptor tag + :rtype: bytes + """ + return self._encryptor.tag + + +def encrypt(algorithm, key, plaintext, associated_data, iv): + """Encrypts a frame body. + + :param algorithm: Algorithm used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes key: Encryption key + :param bytes plaintext: Body plaintext + :param bytes associated_data: Body AAD Data + :param bytes iv: IV to use when encrypting message + :returns: Deserialized object containing encrypted body + :rtype: aws_encryption_sdk.internal.structures.EncryptedData + """ + encryptor = Encryptor(algorithm, key, associated_data, iv) + ciphertext = encryptor.update(plaintext) + encryptor.finalize() + return EncryptedData(encryptor.iv, ciphertext, encryptor.tag) + + +class Decryptor(object): + """Abstract decryption handler. + + :param algorithm: Algorithm used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes key: Raw source key + :param bytes associated_data: Associated Data to send to decryption subsystem + :param bytes iv: IV value with which to initialize decryption subsystem + :param bytes tag: Tag with which to validate ciphertext + """ + + def __init__(self, algorithm, key, associated_data, iv, tag): + """Prepares initial values.""" + self.source_key = key + + # Construct a decryptor object with the given key and a provided IV. + # This is intentionally generic to leave an option for non-Cipher decryptor types in the future. + self._decryptor = Cipher( + algorithm.encryption_algorithm(key), + algorithm.encryption_mode(iv, tag), + backend=default_backend() + ).decryptor() + + # Put associated_data back in or the tag will fail to verify when the _decryptor is finalized. + self._decryptor.authenticate_additional_data(associated_data) + + def update(self, ciphertext): + """Updates _decryptor with provided ciphertext. + + :param bytes ciphertext: Ciphertext to decrypt + :returns: Decrypted plaintext + :rtype: bytes + """ + return self._decryptor.update(ciphertext) + + def finalize(self): + """Finalizes and closes _decryptor. + + :returns: Final decrypted plaintext + :rtype: bytes + """ + return self._decryptor.finalize() + + +def decrypt(algorithm, key, encrypted_data, associated_data): + """Decrypts a frame body. + + :param algorithm: Algorithm used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes key: Plaintext data key + :param encrypted_data: EncryptedData containing body data + :type encrypted_data: :class:`aws_encryption_sdk.internal.structures.EncryptedData`, + :class:`aws_encryption_sdk.internal.structures.FrameBody`, + or :class:`aws_encryption_sdk.internal.structures.MessageNoFrameBody` + :param bytes associated_data: AAD string generated for body + :type associated_data: bytes + :returns: Plaintext of body + :rtype: bytes + """ + decryptor = Decryptor(algorithm, key, associated_data, encrypted_data.iv, encrypted_data.tag) + return decryptor.update(encrypted_data.ciphertext) + decryptor.finalize() diff --git a/aws_encryption_sdk/internal/crypto/iv.py b/src/aws_encryption_sdk/internal/crypto/iv.py similarity index 84% rename from aws_encryption_sdk/internal/crypto/iv.py rename to src/aws_encryption_sdk/internal/crypto/iv.py index f53c672bd..c8c9632f7 100644 --- a/aws_encryption_sdk/internal/crypto/iv.py +++ b/src/aws_encryption_sdk/internal/crypto/iv.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """ Helper functions used for generating deterministic initialization vectors (IVs). diff --git a/src/aws_encryption_sdk/internal/crypto/wrapping_keys.py b/src/aws_encryption_sdk/internal/crypto/wrapping_keys.py new file mode 100644 index 000000000..bc8124cf6 --- /dev/null +++ b/src/aws_encryption_sdk/internal/crypto/wrapping_keys.py @@ -0,0 +1,128 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Contains wrapping key primitives.""" +import logging +import os + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization + +from .data_keys import derive_data_encryption_key +from .encryption import decrypt, encrypt +from ..formatting.encryption_context import serialize_encryption_context +from ..structures import EncryptedData +from ...exceptions import IncorrectMasterKeyError, InvalidDataKeyError +from ...identifiers import EncryptionKeyType, EncryptionType + +_LOGGER = logging.getLogger(__name__) + + +class WrappingKey(object): + """Creates a wrapping encryption key object to encrypt and decrypt data keys. + + For use inside :class:`aws_encryption_sdk.key_providers.raw.RawMasterKeyProvider` objects. + + :param wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext_data_key + :type wrapping_algorithm: aws_encryption_sdk.identifiers.WrappingAlgorithm + :param bytes wrapping_key: Encryption key with which to wrap plaintext_data_key + :param wrapping_key_type: Type of encryption key with which to wrap plaintext_data_key + :type wrapping_key_type: aws_encryption_sdk.identifiers.EncryptionKeyType + :param bytes password: Password to decrypt wrapping_key (optional, currently only relevant for RSA) + """ + + def __init__(self, wrapping_algorithm, wrapping_key, wrapping_key_type, password=None): + """Prepares initial values.""" + self.wrapping_algorithm = wrapping_algorithm + self.wrapping_key_type = wrapping_key_type + if wrapping_key_type is EncryptionKeyType.PRIVATE: + self._wrapping_key = serialization.load_pem_private_key( + data=wrapping_key, + password=password, + backend=default_backend() + ) + elif wrapping_key_type is EncryptionKeyType.PUBLIC: + self._wrapping_key = serialization.load_pem_public_key( + data=wrapping_key, + backend=default_backend() + ) + elif wrapping_key_type is EncryptionKeyType.SYMMETRIC: + self._wrapping_key = wrapping_key + self._derived_wrapping_key = derive_data_encryption_key( + source_key=self._wrapping_key, + algorithm=self.wrapping_algorithm.algorithm, + message_id=None + ) + else: + raise InvalidDataKeyError('Invalid wrapping_key_type: {}'.format(wrapping_key_type)) + + def encrypt(self, plaintext_data_key, encryption_context): + """Encrypts a data key using a direct wrapping key. + + :param bytes plaintext_data_key: Data key to encrypt + :param dict encryption_context: Encryption context to use in encryption + :returns: Deserialized object containing encrypted key + :rtype: aws_encryption_sdk.internal.structures.EncryptedData + """ + if self.wrapping_algorithm.encryption_type is EncryptionType.ASYMMETRIC: + if self.wrapping_key_type is EncryptionKeyType.PRIVATE: + encrypted_key = self._wrapping_key.public_key().encrypt( + plaintext=plaintext_data_key, + padding=self.wrapping_algorithm.padding + ) + else: + encrypted_key = self._wrapping_key.encrypt( + plaintext=plaintext_data_key, + padding=self.wrapping_algorithm.padding + ) + return EncryptedData( + iv=None, + ciphertext=encrypted_key, + tag=None + ) + serialized_encryption_context = serialize_encryption_context( + encryption_context=encryption_context + ) + iv = os.urandom(self.wrapping_algorithm.algorithm.iv_len) + return encrypt( + algorithm=self.wrapping_algorithm.algorithm, + key=self._derived_wrapping_key, + plaintext=plaintext_data_key, + associated_data=serialized_encryption_context, + iv=iv + ) + + def decrypt(self, encrypted_wrapped_data_key, encryption_context): + """Decrypts a wrapped, encrypted, data key. + + :param encrypted_wrapped_data_key: Encrypted, wrapped, data key + :type encrypted_wrapped_data_key: aws_encryption_sdk.internal.structures.EncryptedData + :param dict encryption_context: Encryption context to use in decryption + :returns: Plaintext of data key + :rtype: bytes + """ + if self.wrapping_key_type is EncryptionKeyType.PUBLIC: + raise IncorrectMasterKeyError('Public key cannot decrypt') + if self.wrapping_key_type is EncryptionKeyType.PRIVATE: + return self._wrapping_key.decrypt( + ciphertext=encrypted_wrapped_data_key.ciphertext, + padding=self.wrapping_algorithm.padding + ) + serialized_encryption_context = serialize_encryption_context( + encryption_context=encryption_context + ) + return decrypt( + algorithm=self.wrapping_algorithm.algorithm, + key=self._derived_wrapping_key, + encrypted_data=encrypted_wrapped_data_key, + associated_data=serialized_encryption_context + ) diff --git a/aws_encryption_sdk/internal/defaults.py b/src/aws_encryption_sdk/internal/defaults.py similarity index 75% rename from aws_encryption_sdk/internal/defaults.py rename to src/aws_encryption_sdk/internal/defaults.py index 047e04aa7..0a2b404a6 100644 --- a/aws_encryption_sdk/internal/defaults.py +++ b/src/aws_encryption_sdk/internal/defaults.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Default values for AWS Encryption SDK.""" import io diff --git a/aws_encryption_sdk/internal/formatting/__init__.py b/src/aws_encryption_sdk/internal/formatting/__init__.py similarity index 88% rename from aws_encryption_sdk/internal/formatting/__init__.py rename to src/aws_encryption_sdk/internal/formatting/__init__.py index 0d19efafa..a98ee1574 100644 --- a/aws_encryption_sdk/internal/formatting/__init__.py +++ b/src/aws_encryption_sdk/internal/formatting/__init__.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Formatting functions for aws_encryption_sdk.""" from .serialize import serialize_header diff --git a/aws_encryption_sdk/internal/formatting/deserialize.py b/src/aws_encryption_sdk/internal/formatting/deserialize.py similarity index 89% rename from aws_encryption_sdk/internal/formatting/deserialize.py rename to src/aws_encryption_sdk/internal/formatting/deserialize.py index f8deb4cb3..6d165f810 100644 --- a/aws_encryption_sdk/internal/formatting/deserialize.py +++ b/src/aws_encryption_sdk/internal/formatting/deserialize.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Components for handling AWS Encryption SDK message deserialization.""" from __future__ import division import logging @@ -5,18 +17,19 @@ from cryptography.exceptions import InvalidTag -from aws_encryption_sdk.exceptions import SerializationError, UnknownIdentityError, NotSupportedError -import aws_encryption_sdk.internal.crypto -from aws_encryption_sdk.internal.formatting.encryption_context import deserialize_encryption_context +from aws_encryption_sdk.exceptions import NotSupportedError, SerializationError, UnknownIdentityError from aws_encryption_sdk.identifiers import ( - Algorithm, ObjectType, SerializationVersion, ContentType, SequenceIdentifier + Algorithm, ContentType, ObjectType, SequenceIdentifier, SerializationVersion ) +from aws_encryption_sdk.internal.crypto.encryption import decrypt +from aws_encryption_sdk.internal.defaults import MAX_FRAME_SIZE +from aws_encryption_sdk.internal.formatting.encryption_context import deserialize_encryption_context from aws_encryption_sdk.internal.str_ops import to_str from aws_encryption_sdk.internal.structures import ( - MessageHeaderAuthentication, MessageFrameBody, - MessageFooter, EncryptedData + EncryptedData, MessageFooter, + MessageFrameBody, MessageHeaderAuthentication ) -from aws_encryption_sdk.structures import MasterKeyInfo, MessageHeader, EncryptedDataKey +from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo, MessageHeader _LOGGER = logging.getLogger(__name__) @@ -39,7 +52,7 @@ def validate_header(header, header_auth, stream, header_start, header_end, data_ current_position = stream.tell() stream.seek(header_start) try: - aws_encryption_sdk.internal.crypto.decrypt( + decrypt( algorithm=header.algorithm, key=data_key, encrypted_data=EncryptedData(header_auth.iv, b'', header_auth.tag), @@ -65,23 +78,23 @@ def deserialize_header(stream): version_id, message_type_id = unpack_values('>BB', stream) try: message_type = ObjectType(message_type_id) - except ValueError as e: + except ValueError as error: raise NotSupportedError( 'Unsupported type {} discovered in data stream'.format(message_type_id), - e + error ) try: version = SerializationVersion(version_id) - except ValueError as e: - raise NotSupportedError('Unsupported version {}'.format(version_id), e) + except ValueError as error: + raise NotSupportedError('Unsupported version {}'.format(version_id), error) header = {'version': version, 'type': message_type} algorithm_id, message_id, ser_encryption_context_length = unpack_values('>H16sH', stream) try: alg = Algorithm.get_by_id(algorithm_id) - except KeyError as e: - raise UnknownIdentityError('Unknown algorithm {}'.format(algorithm_id), e) + except KeyError as error: + raise UnknownIdentityError('Unknown algorithm {}'.format(algorithm_id), error) if not alg.allowed: raise NotSupportedError('Unsupported algorithm: {}'.format(alg)) header['algorithm'] = alg @@ -118,10 +131,10 @@ def deserialize_header(stream): (content_type_id,) = unpack_values('>B', stream) try: content_type = ContentType(content_type_id) - except ValueError as e: + except ValueError as error: raise UnknownIdentityError( 'Unknown content type {}'.format(content_type_id), - e + error ) header['content_type'] = content_type @@ -143,10 +156,10 @@ def deserialize_header(stream): header['header_iv_length'] = iv_length (frame_length,) = unpack_values('>I', stream) - if content_type == ContentType.FRAMED_DATA and frame_length > aws_encryption_sdk.internal.defaults.MAX_FRAME_SIZE: + if content_type == ContentType.FRAMED_DATA and frame_length > MAX_FRAME_SIZE: raise SerializationError('Specified frame length larger than allowed maximum: {found} > {max}'.format( found=frame_length, - max=aws_encryption_sdk.internal.defaults.MAX_FRAME_SIZE + max=MAX_FRAME_SIZE )) elif content_type == ContentType.NO_FRAMING and frame_length != 0: raise SerializationError('Non-zero frame length found for non-framed message') @@ -185,7 +198,7 @@ def deserialize_non_framed_values(stream, header, verifier=None): :param verifier: Signature verifier object (optional) :type verifier: aws_encryption_sdk.internal.crypto.Verifier :returns: IV, Tag, and Data Length values for body - :rtype: tuple of str, str, and int + :rtype: tuple of bytes, bytes, and int """ _LOGGER.debug('Starting non-framed body iv/tag deserialization') (data_iv, data_length) = unpack_values( @@ -218,7 +231,7 @@ def update_verifier_with_tag(stream, header, verifier): :param verifier: Signature verifier object :type verifier: aws_encryption_sdk.internal.crypto.Verifier :returns: Data authentication tag value - :rtype: str + :rtype: bytes """ return unpack_values( '>{auth_len}s'.format(auth_len=header.algorithm.auth_len), @@ -303,16 +316,14 @@ def deserialize_footer(stream, verifier=None): except SerializationError: raise SerializationError('No signature found in message') if verifier: - verifier.set_signature(signature) - verifier.verify() + verifier.verify(signature) return MessageFooter(signature=signature) def unpack_values(format_string, stream, verifier=None): """Helper function to unpack struct data from a stream and update the signature verifier. - :param format_string: Struct format string - :type format_string: str + :param str format_string: Struct format string :param stream: Source data stream :type stream: io.BytesIO :param verifier: Signature verifier object @@ -325,8 +336,8 @@ def unpack_values(format_string, stream, verifier=None): if verifier: verifier.update(message_bytes) values = struct.unpack(format_string, message_bytes) - except struct.error as e: - raise SerializationError('Unexpected deserialization error', type(e), e.args) + except struct.error as error: + raise SerializationError('Unexpected deserialization error', type(error), error.args) return values @@ -335,7 +346,7 @@ def deserialize_wrapped_key(wrapping_algorithm, wrapping_key_id, wrapped_encrypt :param wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext_data_key :type wrapping_algorithm: aws_encryption_sdk.identifiers.WrappingAlgorithm - :param str wrapping_key_id: Key ID of wrapping MasterKey + :param bytes wrapping_key_id: Key ID of wrapping MasterKey :param wrapped_encrypted_key: Raw Wrapped EncryptedKey :type wrapped_encrypted_key: aws_encryption_sdk.structures.EncryptedDataKey :returns: EncryptedData of deserialized Wrapped EncryptedKey diff --git a/aws_encryption_sdk/internal/formatting/encryption_context.py b/src/aws_encryption_sdk/internal/formatting/encryption_context.py similarity index 91% rename from aws_encryption_sdk/internal/formatting/encryption_context.py rename to src/aws_encryption_sdk/internal/formatting/encryption_context.py index a7ebb96fd..cecaa34ee 100644 --- a/aws_encryption_sdk/internal/formatting/encryption_context.py +++ b/src/aws_encryption_sdk/internal/formatting/encryption_context.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """ Components for handling serialization and deserialization of encryption context data in AWS Encryption SDK messages. @@ -7,8 +19,8 @@ import struct from aws_encryption_sdk.exceptions import SerializationError -import aws_encryption_sdk.internal.defaults import aws_encryption_sdk.identifiers +import aws_encryption_sdk.internal.defaults import aws_encryption_sdk.internal.str_ops _LOGGER = logging.getLogger(__name__) @@ -25,7 +37,7 @@ def assemble_content_aad(message_id, aad_content_string, seq_num, length): :type seq_num: int :param length: Content Length :type length: int - :returns: Properly formatted AAD string for message body structure. + :returns: Properly formatted AAD bytes for message body structure. :rtype: bytes :raises SerializationError: if aad_content_string is not known """ diff --git a/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py similarity index 91% rename from aws_encryption_sdk/internal/formatting/serialize.py rename to src/aws_encryption_sdk/internal/formatting/serialize.py index e30abbcb9..119b747a8 100644 --- a/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -1,13 +1,25 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Components for handling AWS Encryption SDK message serialization.""" import logging import struct from aws_encryption_sdk.exceptions import SerializationError -import aws_encryption_sdk.internal.crypto -from aws_encryption_sdk.internal.crypto.iv import header_auth_iv, frame_iv +from aws_encryption_sdk.identifiers import ContentAADString, EncryptionType, SequenceIdentifier +from aws_encryption_sdk.internal.crypto.encryption import encrypt +from aws_encryption_sdk.internal.crypto.iv import frame_iv, header_auth_iv import aws_encryption_sdk.internal.defaults import aws_encryption_sdk.internal.formatting.encryption_context -from aws_encryption_sdk.identifiers import SequenceIdentifier, ContentAADString, EncryptionType from aws_encryption_sdk.internal.str_ops import to_bytes from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo @@ -48,10 +60,7 @@ def serialize_encrypted_data_key(encrypted_data_key): ) -def serialize_header( - header, - signer=None -): +def serialize_header(header, signer=None): """Serializes a header object. :param header: Header to serialize @@ -122,7 +131,7 @@ def serialize_header_auth(algorithm, header, data_encryption_key, signer=None): :returns: Serialized header authentication data :rtype: bytes """ - header_auth = aws_encryption_sdk.internal.crypto.encrypt( + header_auth = encrypt( algorithm=algorithm, key=data_encryption_key, plaintext=b'', @@ -190,14 +199,14 @@ def serialize_non_framed_close(tag, signer=None): def serialize_frame( - algorithm, - plaintext, - message_id, - data_encryption_key, - frame_length, - sequence_number, - is_final_frame, - signer=None + algorithm, + plaintext, + message_id, + data_encryption_key, + frame_length, + sequence_number, + is_final_frame, + signer=None ): """Receives a message plaintext, breaks off a frame, encrypts and serializes the frame, and returns the encrypted frame and the remaining plaintext. @@ -225,7 +234,7 @@ def serialize_frame( else: content_string = ContentAADString.FRAME_STRING_ID frame_plaintext = plaintext[:frame_length] - frame_ciphertext = aws_encryption_sdk.internal.crypto.encrypt( + frame_ciphertext = encrypt( algorithm=algorithm, key=data_encryption_key, plaintext=frame_plaintext, diff --git a/aws_encryption_sdk/internal/str_ops.py b/src/aws_encryption_sdk/internal/str_ops.py similarity index 62% rename from aws_encryption_sdk/internal/str_ops.py rename to src/aws_encryption_sdk/internal/str_ops.py index 6fb67a559..75b70be92 100644 --- a/aws_encryption_sdk/internal/str_ops.py +++ b/src/aws_encryption_sdk/internal/str_ops.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Helper functions for consistently obtaining str and bytes objects in both Python2 and Python3.""" import codecs diff --git a/aws_encryption_sdk/internal/structures.py b/src/aws_encryption_sdk/internal/structures.py similarity index 81% rename from aws_encryption_sdk/internal/structures.py rename to src/aws_encryption_sdk/internal/structures.py index 95af133e0..2730bd19a 100644 --- a/aws_encryption_sdk/internal/structures.py +++ b/src/aws_encryption_sdk/internal/structures.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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 attr import six @@ -11,6 +23,7 @@ class EncryptedData(object): :param bytes ciphertext: Ciphertext :param bytes tag: Encryption tag """ + iv = attr.ib(hash=True, validator=attr.validators.optional(attr.validators.instance_of(bytes))) ciphertext = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) tag = attr.ib(hash=True, validator=attr.validators.optional(attr.validators.instance_of(bytes))) @@ -23,6 +36,7 @@ class MessageHeaderAuthentication(object): :param bytes iv: Initialization Vector :param bytes tag: Encryption Tag """ + iv = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) tag = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) @@ -37,6 +51,7 @@ class MessageFrameBody(object): :param int sequence_number: Frame sequence number :param bool final_frame: Identifies final frames """ + iv = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) ciphertext = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) tag = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) @@ -52,6 +67,7 @@ class MessageNoFrameBody(object): :param bytes ciphertext: Ciphertext :param bytes tag: Encryption Tag """ + iv = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) ciphertext = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) tag = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) @@ -65,4 +81,5 @@ class MessageFooter(object): :param bytes signature: Message signature """ + signature = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) diff --git a/aws_encryption_sdk/internal/utils.py b/src/aws_encryption_sdk/internal/utils.py similarity index 90% rename from aws_encryption_sdk/internal/utils.py rename to src/aws_encryption_sdk/internal/utils.py index e34810e44..a9f478422 100644 --- a/aws_encryption_sdk/internal/utils.py +++ b/src/aws_encryption_sdk/internal/utils.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Helper utility functions for AWS Encryption SDK.""" import io import logging @@ -6,11 +18,11 @@ import six from aws_encryption_sdk.exceptions import ( - ActionNotAllowedError, SerializationError, - UnknownIdentityError, InvalidDataKeyError + ActionNotAllowedError, InvalidDataKeyError, + SerializationError, UnknownIdentityError ) -import aws_encryption_sdk.internal.defaults from aws_encryption_sdk.identifiers import ContentAADString, ContentType +import aws_encryption_sdk.internal.defaults from aws_encryption_sdk.internal.str_ops import to_bytes from aws_encryption_sdk.structures import EncryptedDataKey @@ -56,7 +68,7 @@ def message_id(): """Generates a new message ID. :returns: Message ID - :rtype: str + :rtype: bytes """ return os.urandom(aws_encryption_sdk.internal.defaults.MESSAGE_ID_LENGTH) @@ -68,7 +80,7 @@ def get_aad_content_string(content_type, is_final_frame): :type content_type: aws_encryption_sdk.identifiers.ContentType :param bool is_final_frame: Boolean stating whether this is the final frame in a body :returns: Appropriate AAD Content String - :rtype: str + :rtype: bytes :raises UnknownIdentityError: if unknown content type """ if content_type == ContentType.NO_FRAMING: @@ -92,6 +104,7 @@ class ROStream(object): """ def __init__(self, source_stream): + """Prepares the passthroughs.""" self._source_stream = source_stream self._duplicate_api() @@ -105,7 +118,7 @@ def _duplicate_api(self): for attribute in source_attributes.difference(self_attributes): setattr(self, attribute, getattr(self._source_stream, attribute)) - def write(self, b): + def write(self, b): # pylint: disable=unused-argument """Blocks calls to write. :raises ActionNotAllowedError: when called @@ -153,7 +166,7 @@ def prepare_data_keys(primary_master_key, master_keys, algorithm, encryption_con try: _FILE_TYPE = file # Python 2 except NameError: - _FILE_TYPE = io.IOBase # Python 3 + _FILE_TYPE = io.IOBase # Python 3 # pylint: disable=invalid-name def prep_stream_data(data): diff --git a/src/aws_encryption_sdk/key_providers/__init__.py b/src/aws_encryption_sdk/key_providers/__init__.py new file mode 100644 index 000000000..867d1e0ca --- /dev/null +++ b/src/aws_encryption_sdk/key_providers/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""All provided master key provider and master keys.""" diff --git a/aws_encryption_sdk/key_providers/base.py b/src/aws_encryption_sdk/key_providers/base.py similarity index 93% rename from aws_encryption_sdk/key_providers/base.py rename to src/aws_encryption_sdk/key_providers/base.py index 46465374e..1d1eba394 100644 --- a/aws_encryption_sdk/key_providers/base.py +++ b/src/aws_encryption_sdk/key_providers/base.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Base class interface for Master Key Providers.""" import abc import logging @@ -6,8 +18,8 @@ import six from aws_encryption_sdk.exceptions import ( - MasterKeyProviderError, InvalidKeyIdError, DecryptKeyError, - IncorrectMasterKeyError, ConfigMismatchError + ConfigMismatchError, DecryptKeyError, IncorrectMasterKeyError, + InvalidKeyIdError, MasterKeyProviderError ) from aws_encryption_sdk.internal.str_ops import to_bytes import aws_encryption_sdk.internal.utils @@ -19,7 +31,7 @@ @attr.s(hash=True) class MasterKeyProviderConfig(object): """Provides a common ancestor for MasterKeyProvider configuration objects - and a stand-in point if common params are needed later. + and a stand-in point if common params are needed later. """ @@ -56,18 +68,19 @@ def __new__(cls, **kwargs): """ instance = super(MasterKeyProvider, cls).__new__(cls) config = kwargs.pop('config', None) - if not isinstance(config, instance._config_class): - config = instance._config_class(**kwargs) + if not isinstance(config, instance._config_class): # pylint: disable=protected-access + config = instance._config_class(**kwargs) # pylint: disable=protected-access instance.config = config #: Index matching key IDs to existing MasterKey objects. - instance._encrypt_key_index = {} + instance._encrypt_key_index = {} # pylint: disable=protected-access #: Set of all member entities of this Provider (both Master Keys and other Providers). - instance._members = [] + instance._members = [] # pylint: disable=protected-access #: Index of matching key IDs to existing MasterKey objects ONLY for decrypt. - instance._decrypt_key_index = {} + instance._decrypt_key_index = {} # pylint: disable=protected-access return instance def __repr__(self): + """Builds the proper repr string.""" return '{name}({kwargs})'.format( name=self.__class__.__name__, kwargs=', '.join( @@ -243,10 +256,10 @@ def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): encrypted_data_key.key_provider.key_info ) data_key = master_key.decrypt_data_key(encrypted_data_key, algorithm, encryption_context) - except (IncorrectMasterKeyError, DecryptKeyError) as e: + except (IncorrectMasterKeyError, DecryptKeyError) as error: _LOGGER.debug( '%s raised when attempting to decrypt data key with master key %s', - repr(e), + repr(error), master_key.key_provider ) continue @@ -285,8 +298,9 @@ def decrypt_data_key_from_list(self, encrypted_data_keys, algorithm, encryption_ class MasterKeyConfig(object): """Configuration object for MasterKey objects. - :param str key_id: Key ID for Master Key + :param bytes key_id: Key ID for Master Key """ + key_id = attr.ib( hash=True, validator=attr.validators.instance_of((six.string_types, bytes)), @@ -316,12 +330,13 @@ def provider_id(self): class MasterKey(MasterKeyProvider): """Parent interface for Master Key classes. - :param str key_id: Key ID for Master Key + :param bytes key_id: Key ID for Master Key :param config: Configuration object :type config: aws_encryption_sdk.key_providers.base.MasterKeyConfig """ def __new__(cls, **kwargs): + """Performs universal prep work for all MasterKeys.""" instance = super(MasterKey, cls).__new__(cls, **kwargs) if instance.config.provider_id is not None: # Only allow override if provider_id is NOT set to non-None for the class @@ -335,10 +350,10 @@ def __new__(cls, **kwargs): ) ) instance.key_id = instance.config.key_id - instance._encrypt_key_index = {instance.key_id: instance} + instance._encrypt_key_index = {instance.key_id: instance} # pylint: disable=protected-access # We cannot make any general statements about key_info, so specifically enforce that decrypt index is empty. - instance._decrypt_key_index = {} - instance._members = [instance] + instance._decrypt_key_index = {} # pylint: disable=protected-access + instance._members = [instance] # pylint: disable=protected-access return instance @property diff --git a/aws_encryption_sdk/key_providers/kms.py b/src/aws_encryption_sdk/key_providers/kms.py similarity index 86% rename from aws_encryption_sdk/key_providers/kms.py rename to src/aws_encryption_sdk/key_providers/kms.py index ca736c276..b89b7e72a 100644 --- a/aws_encryption_sdk/key_providers/kms.py +++ b/src/aws_encryption_sdk/key_providers/kms.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Master Key Providers for use with AWS KMS""" import logging @@ -7,12 +19,12 @@ from botocore.exceptions import ClientError import botocore.session -from aws_encryption_sdk.exceptions import GenerateKeyError, DecryptKeyError, EncryptKeyError, UnknownRegionError -from aws_encryption_sdk.identifiers import user_agent_suffix +from aws_encryption_sdk.exceptions import DecryptKeyError, EncryptKeyError, GenerateKeyError, UnknownRegionError +from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX from aws_encryption_sdk.internal.str_ops import to_str from aws_encryption_sdk.internal.utils import extend_user_agent_suffix from aws_encryption_sdk.key_providers.base import ( - MasterKeyProvider, MasterKeyProviderConfig, MasterKey, MasterKeyConfig + MasterKey, MasterKeyConfig, MasterKeyProvider, MasterKeyProviderConfig ) from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo @@ -30,6 +42,7 @@ class KMSMasterKeyProviderConfig(MasterKeyProviderConfig): :param list key_ids: List of KMS CMK IDs with which to pre-populate provider (optional) :param list region_names: List of regions for which to pre-populate clients (optional) """ + botocore_session = attr.ib( hash=True, default=attr.Factory(botocore.session.Session), @@ -76,11 +89,13 @@ class KMSMasterKeyProvider(MasterKeyProvider): :param list key_ids: List of KMS CMK IDs with which to pre-populate provider (optional) :param list region_names: List of regions for which to pre-populate clients (optional) """ + provider_id = _PROVIDER_ID _config_class = KMSMasterKeyProviderConfig default_region = None - def __init__(self, **kwargs): + def __init__(self, **kwargs): # pylint: disable=unused-argument + """Prepares mutable attributes.""" self._regional_clients = {} self._process_config() @@ -157,6 +172,7 @@ class KMSMasterKeyConfig(MasterKeyConfig): :type client: botocore.client.KMS :param list grant_tokens: List of grant tokens to pass to KMS on CMK operations """ + provider_id = _PROVIDER_ID client = attr.ib(hash=True, validator=attr.validators.instance_of(botocore.client.BaseClient)) grant_tokens = attr.ib( @@ -177,14 +193,16 @@ class KMSMasterKey(MasterKey): :type client: botocore.client.KMS :param list grant_tokens: List of grant tokens to pass to KMS on CMK operations """ + provider_id = _PROVIDER_ID _config_class = KMSMasterKeyConfig - def __init__(self, **kwargs): + def __init__(self, **kwargs): # pylint: disable=unused-argument + """Performs transformations needed for KMS.""" self._key_id = to_str(self.key_id) # KMS client requires str, not bytes self.config.client.meta.config.user_agent_extra = extend_user_agent_suffix( user_agent=self.config.client.meta.config.user_agent_extra, - suffix=user_agent_suffix + suffix=USER_AGENT_SUFFIX ) def _generate_data_key(self, algorithm, encryption_context=None): @@ -211,7 +229,9 @@ def _generate_data_key(self, algorithm, encryption_context=None): ciphertext = response['CiphertextBlob'] key_id = response['KeyId'] except (ClientError, KeyError): - raise GenerateKeyError('Master Key {key_id} unable to generate data key'.format(key_id=self._key_id)) + error_message = 'Master Key {key_id} unable to generate data key'.format(key_id=self._key_id) + _LOGGER.exception(error_message) + raise GenerateKeyError(error_message) return DataKey( key_provider=MasterKeyInfo( provider_id=self.provider_id, @@ -247,7 +267,9 @@ def _encrypt_data_key(self, data_key, algorithm, encryption_context=None): ciphertext = response['CiphertextBlob'] key_id = response['KeyId'] except (ClientError, KeyError): - raise EncryptKeyError('Master Key {key_id} unable to encrypt data key'.format(key_id=self._key_id)) + error_message = 'Master Key {key_id} unable to encrypt data key'.format(key_id=self._key_id) + _LOGGER.exception(error_message) + raise EncryptKeyError(error_message) return EncryptedDataKey( key_provider=MasterKeyInfo( provider_id=self.provider_id, @@ -279,7 +301,9 @@ def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context=No response = self.config.client.decrypt(**kms_params) plaintext = response['Plaintext'] except (ClientError, KeyError): - raise DecryptKeyError('Master Key {key_id} unable to decrypt data key'.format(key_id=self._key_id)) + error_message = 'Master Key {key_id} unable to decrypt data key'.format(key_id=self._key_id) + _LOGGER.exception(error_message) + raise DecryptKeyError(error_message) return DataKey( key_provider=self.key_provider, data_key=plaintext, diff --git a/aws_encryption_sdk/key_providers/raw.py b/src/aws_encryption_sdk/key_providers/raw.py similarity index 84% rename from aws_encryption_sdk/key_providers/raw.py rename to src/aws_encryption_sdk/key_providers/raw.py index 766c98161..221199301 100644 --- a/aws_encryption_sdk/key_providers/raw.py +++ b/src/aws_encryption_sdk/key_providers/raw.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Resources required for Raw Master Keys.""" import abc import logging @@ -6,14 +18,14 @@ import attr import six -from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.identifiers import EncryptionType +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey import aws_encryption_sdk.internal.formatting.deserialize import aws_encryption_sdk.internal.formatting.serialize -from aws_encryption_sdk.identifiers import EncryptionType from aws_encryption_sdk.key_providers.base import ( - MasterKeyProvider, MasterKeyProviderConfig, MasterKey, MasterKeyConfig + MasterKey, MasterKeyConfig, MasterKeyProvider, MasterKeyProviderConfig ) -from aws_encryption_sdk.structures import RawDataKey, DataKey +from aws_encryption_sdk.structures import DataKey, RawDataKey _LOGGER = logging.getLogger(__name__) @@ -22,11 +34,12 @@ class RawMasterKeyConfig(MasterKeyConfig): """Configuration object for RawMasterKey objects. - :param str key_id: Key ID for Master Key + :param bytes key_id: Key ID for Master Key :param str provider_id: String defining provider ID :param wrapping_key: Encryption key with which to wrap plaintext_data_key :type wrapping_key: aws_encryption_sdk.internal.crypto.WrappingKey """ + provider_id = attr.ib( hash=True, validator=attr.validators.instance_of((six.string_types, bytes)), @@ -45,6 +58,7 @@ class RawMasterKey(MasterKey): :param wrapping_key: Encryption key with which to wrap plaintext_data_key :type wrapping_key: aws_encryption_sdk.internal.crypto.WrappingKey """ + provider_id = None _config_class = RawMasterKeyConfig @@ -55,7 +69,7 @@ def __new__(cls, **kwargs): Overloaded here to allow definition of _key_info_prefix on instantiation. """ instance = super(RawMasterKey, cls).__new__(cls, **kwargs) - instance._key_info_prefix = aws_encryption_sdk.internal.formatting.serialize.serialize_raw_master_key_prefix( + instance._key_info_prefix = aws_encryption_sdk.internal.formatting.serialize.serialize_raw_master_key_prefix( # noqa pylint: disable=protected-access raw_master_key=instance ) return instance @@ -72,16 +86,18 @@ def owns_data_key(self, data_key): """ expected_key_info_len = -1 if ( - self.config.wrapping_key.wrapping_algorithm.encryption_type is EncryptionType.ASYMMETRIC - and data_key.key_provider == self.key_provider + self.config.wrapping_key.wrapping_algorithm.encryption_type is EncryptionType.ASYMMETRIC + and data_key.key_provider == self.key_provider ): return True elif self.config.wrapping_key.wrapping_algorithm.encryption_type is EncryptionType.SYMMETRIC: - expected_key_info_len = len(self._key_info_prefix) + self.config.wrapping_key.wrapping_algorithm.algorithm.iv_len + expected_key_info_len = ( + len(self._key_info_prefix) + self.config.wrapping_key.wrapping_algorithm.algorithm.iv_len + ) if ( - data_key.key_provider.provider_id == self.provider_id - and len(data_key.key_provider.key_info) == expected_key_info_len - and data_key.key_provider.key_info.startswith(self._key_info_prefix) + data_key.key_provider.provider_id == self.provider_id + and len(data_key.key_provider.key_info) == expected_key_info_len + and data_key.key_provider.key_info.startswith(self._key_info_prefix) ): return True _LOGGER.debug( diff --git a/aws_encryption_sdk/materials_managers/__init__.py b/src/aws_encryption_sdk/materials_managers/__init__.py similarity index 87% rename from aws_encryption_sdk/materials_managers/__init__.py rename to src/aws_encryption_sdk/materials_managers/__init__.py index 3f28642d5..24b17d408 100644 --- a/aws_encryption_sdk/materials_managers/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/__init__.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Primitive structures for use when interacting with crypto material managers. .. versionadded:: 1.3.0 @@ -6,9 +18,8 @@ import six from ..identifiers import Algorithm -from ..structures import DataKey -from ..internal.crypto import Signer, Verifier from ..internal.utils import ROStream +from ..structures import DataKey @attr.s(hash=False) @@ -28,6 +39,7 @@ class EncryptionMaterialsRequest(object): :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param int plaintext_length: Length of source plaintext (optional) """ + encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) frame_length = attr.ib(validator=attr.validators.instance_of(six.integer_types)) plaintext_rostream = attr.ib( @@ -57,8 +69,9 @@ class EncryptionMaterials(object): :param encrypted_data_keys: List of encrypted data keys :type encrypted_data_keys: list of `aws_encryption_sdk.structures.EncryptedDataKey` :param dict encryption_context: Encryption context tied to `encrypted_data_keys` - :param str signing_key: Encoded signing key + :param bytes signing_key: Encoded signing key """ + algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) data_encryption_key = attr.ib(validator=attr.validators.instance_of(DataKey)) encrypted_data_keys = attr.ib(validator=attr.validators.instance_of(set)) @@ -81,6 +94,7 @@ class DecryptionMaterialsRequest(object): :type encrypted_data_keys: set of `aws_encryption_sdk.structures.EncryptedDataKey` :param dict encryption_context: Encryption context to provide to master keys for underlying decrypt requests """ + algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) encrypted_data_keys = attr.ib(validator=attr.validators.instance_of(set)) encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) @@ -96,6 +110,7 @@ class DecryptionMaterials(object): :type data_key: aws_encryption_sdk.structures.DataKey :param bytes verification_key: Raw signature verification key """ + data_key = attr.ib(validator=attr.validators.instance_of(DataKey)) verification_key = attr.ib( default=None, diff --git a/aws_encryption_sdk/materials_managers/base.py b/src/aws_encryption_sdk/materials_managers/base.py similarity index 69% rename from aws_encryption_sdk/materials_managers/base.py rename to src/aws_encryption_sdk/materials_managers/base.py index 2a4a65e94..a695b99e4 100644 --- a/aws_encryption_sdk/materials_managers/base.py +++ b/src/aws_encryption_sdk/materials_managers/base.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Base class interface for crypto material managers.""" import abc diff --git a/aws_encryption_sdk/materials_managers/caching.py b/src/aws_encryption_sdk/materials_managers/caching.py similarity index 93% rename from aws_encryption_sdk/materials_managers/caching.py rename to src/aws_encryption_sdk/materials_managers/caching.py index 00dcae793..109e373bd 100644 --- a/aws_encryption_sdk/materials_managers/caching.py +++ b/src/aws_encryption_sdk/materials_managers/caching.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Caching crypto material manager.""" import logging import uuid @@ -5,18 +17,18 @@ import attr import six + +from . import EncryptionMaterialsRequest +from .base import CryptoMaterialsManager +from .default import DefaultCryptoMaterialsManager from ..caches import ( - build_encryption_materials_cache_key, build_decryption_materials_cache_key, CryptoMaterialsCacheEntryHints + build_decryption_materials_cache_key, build_encryption_materials_cache_key, CryptoMaterialsCacheEntryHints ) from ..caches.base import CryptoMaterialsCache from ..exceptions import CacheKeyError -from ..key_providers.base import MasterKeyProvider -from ..internal.defaults import MAX_MESSAGES_PER_KEY, MAX_BYTES_PER_KEY +from ..internal.defaults import MAX_BYTES_PER_KEY, MAX_MESSAGES_PER_KEY from ..internal.str_ops import to_bytes - -from . import EncryptionMaterialsRequest -from .base import CryptoMaterialsManager -from .default import DefaultCryptoMaterialsManager +from ..key_providers.base import MasterKeyProvider _LOGGER = logging.getLogger(__name__) @@ -64,6 +76,7 @@ class CachingCryptoMaterialsManager(CryptoMaterialsManager): to process (optional) :param bytes partition_name: Partition name to use for this instance (optional) """ + cache = attr.ib(validator=attr.validators.instance_of(CryptoMaterialsCache)) max_age = attr.ib(validator=attr.validators.instance_of(float)) max_messages_encrypted = attr.ib( @@ -89,6 +102,7 @@ class CachingCryptoMaterialsManager(CryptoMaterialsManager): ) def __attrs_post_init__(self): + """Applies post-processing which cannot be handled by attrs.""" if self.max_messages_encrypted < 1: raise ValueError('max_messages_encrypted cannot be less than 1') diff --git a/aws_encryption_sdk/materials_managers/default.py b/src/aws_encryption_sdk/materials_managers/default.py similarity index 89% rename from aws_encryption_sdk/materials_managers/default.py rename to src/aws_encryption_sdk/materials_managers/default.py index 060ac635b..6ea453169 100644 --- a/aws_encryption_sdk/materials_managers/default.py +++ b/src/aws_encryption_sdk/materials_managers/default.py @@ -1,16 +1,29 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Default crypto material manager class.""" import logging import attr +from . import DecryptionMaterials, EncryptionMaterials +from .base import CryptoMaterialsManager from ..exceptions import MasterKeyProviderError, SerializationError -from ..key_providers.base import MasterKeyProvider -from ..internal.crypto import generate_ecc_signing_key, Signer, Verifier +from ..internal.crypto.authentication import Signer, Verifier +from ..internal.crypto.elliptic_curve import generate_ecc_signing_key from ..internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY from ..internal.str_ops import to_str from ..internal.utils import prepare_data_keys -from . import EncryptionMaterials, DecryptionMaterials -from .base import CryptoMaterialsManager +from ..key_providers.base import MasterKeyProvider _LOGGER = logging.getLogger(__name__) @@ -23,9 +36,8 @@ class DefaultCryptoMaterialsManager(CryptoMaterialsManager): :param master_key_provider: Master key provider to use :type master_key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param algorithm: Algorithm suite to prefer - :type algorithm: aws_encryption_sdk.identifiers.Algorithm """ + algorithm = ALGORITHM master_key_provider = attr.ib(validator=attr.validators.instance_of(MasterKeyProvider)) diff --git a/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py similarity index 89% rename from aws_encryption_sdk/streaming_client.py rename to src/aws_encryption_sdk/streaming_client.py index 2eb6e1944..2797e120d 100644 --- a/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """High level AWS Encryption SDK client for streaming objects.""" from __future__ import division import abc @@ -9,23 +21,24 @@ import six from aws_encryption_sdk.exceptions import ( - AWSEncryptionSDKClientError, SerializationError, CustomMaximumValueExceeded, - NotSupportedError, ActionNotAllowedError + ActionNotAllowedError, AWSEncryptionSDKClientError, CustomMaximumValueExceeded, + NotSupportedError, SerializationError ) from aws_encryption_sdk.identifiers import Algorithm, ContentType -from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.materials_managers import EncryptionMaterialsRequest, DecryptionMaterialsRequest -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager -from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager -from aws_encryption_sdk.structures import MessageHeader - -import aws_encryption_sdk.internal.crypto +from aws_encryption_sdk.internal.crypto.authentication import Signer, Verifier +from aws_encryption_sdk.internal.crypto.data_keys import derive_data_encryption_key +from aws_encryption_sdk.internal.crypto.encryption import decrypt, Decryptor, Encryptor from aws_encryption_sdk.internal.crypto.iv import non_framed_body_iv -from aws_encryption_sdk.internal.defaults import LINE_LENGTH, FRAME_LENGTH, MAX_NON_FRAMED_SIZE, VERSION, TYPE +from aws_encryption_sdk.internal.defaults import FRAME_LENGTH, LINE_LENGTH, MAX_NON_FRAMED_SIZE, TYPE, VERSION import aws_encryption_sdk.internal.formatting.deserialize import aws_encryption_sdk.internal.formatting.encryption_context import aws_encryption_sdk.internal.formatting.serialize import aws_encryption_sdk.internal.utils +from aws_encryption_sdk.key_providers.base import MasterKeyProvider +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager +from aws_encryption_sdk.structures import MessageHeader _LOGGER = logging.getLogger(__name__) @@ -49,6 +62,7 @@ class _ClientConfig(object): If source_length is not provided and unframed message is being written or read() is called, will attempt to seek() to the end of the stream and tell() to find the length of source data. """ + source = attr.ib(hash=True, convert=aws_encryption_sdk.internal.utils.prep_stream_data) materials_manager = attr.ib( hash=True, @@ -89,50 +103,49 @@ class _EncryptionStream(io.IOBase): :type config: aws_encryption_sdk.streaming_client._ClientConfig """ - """ - abc.ABCMeta does not behave properly for defining abstractmethods in children of io.IOBase - due to complexities in how __new__ is called (or not called) with C-module objects. - Leaving this here as an explanation of what is going on in __new__ - - @abc.abstractmethod - def _read_bytes(self, b): - Reads the requested number of bytes from the source stream. - - :param int b: Number of bytes to read - :returns: Processed (encrypted or decrypted) bytes from source stream - :rtype: str - - @abc.abstractmethod - def _prep_message(self): - Performs initial message setup. + # abc.ABCMeta does not behave properly for defining abstractmethods in children of io.IOBase + # due to complexities in how __new__ is called (or not called) with C-module objects. + # Leaving this here as an explanation of what is going on in __new__ + # + # @abc.abstractmethod + # def _read_bytes(self, b): + # Reads the requested number of bytes from the source stream. + # + # :param int b: Number of bytes to read + # :returns: Processed (encrypted or decrypted) bytes from source stream + # :rtype: bytes + # + # @abc.abstractmethod + # def _prep_message(self): + # Performs initial message setup. + # + # @abc.abstractproperty + # def _config_class(self): + # Configuration class for this class - @abc.abstractproperty - def _config_class(self): - Configuration class for this class - """ line_length = LINE_LENGTH def __new__(cls, **kwargs): """Patch for abstractmethod-like enforcement in io.IOBase grandchildren.""" if ( - not (hasattr(cls, '_read_bytes') and callable(cls._read_bytes)) - or not (hasattr(cls, '_prep_message') and callable(cls._read_bytes)) - or not hasattr(cls, '_config_class') + not (hasattr(cls, '_read_bytes') and callable(cls._read_bytes)) + or not (hasattr(cls, '_prep_message') and callable(cls._read_bytes)) + or not hasattr(cls, '_config_class') ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) instance = super(_EncryptionStream, cls).__new__(cls) config = kwargs.pop('config', None) - if not isinstance(config, instance._config_class): - config = instance._config_class(**kwargs) + if not isinstance(config, instance._config_class): # pylint: disable=protected-access + config = instance._config_class(**kwargs) # pylint: disable=protected-access instance.config = config instance.bytes_read = 0 instance.output_buffer = b'' - instance._message_prepped = False + instance._message_prepped = False # pylint: disable=protected-access instance.source_stream = instance.config.source - instance._stream_length = instance.config.source_length + instance._stream_length = instance.config.source_length # pylint: disable=protected-access return instance @@ -145,9 +158,9 @@ def stream_length(self): self.source_stream.seek(0, 2) self._stream_length = self.source_stream.tell() self.source_stream.seek(current_position, 0) - except Exception as e: + except Exception as error: # Catch-all for unknown issues encountered trying to seek for stream length - raise NotSupportedError(e) + raise NotSupportedError(error) return self._stream_length @property @@ -180,7 +193,7 @@ def read(self, b=None): :param int b: Number of bytes to read :returns: Processed (encrypted or decrypted) bytes from source stream - :rtype: str + :rtype: bytes """ _LOGGER.debug('Stream read called, requesting %s bytes', b) if not self._message_prepped: @@ -283,6 +296,7 @@ class EncryptorConfig(_ClientConfig): :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param int frame_length: Frame length in bytes (optional) """ + encryption_context = attr.ib( hash=False, # dictionaries are not hashable default=attr.Factory(dict), @@ -300,7 +314,7 @@ class EncryptorConfig(_ClientConfig): ) -class StreamEncryptor(_EncryptionStream): +class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-attributes """Provides a streaming encryptor for encrypting a stream source. Behaves as a standard file-like object. @@ -339,20 +353,22 @@ class StreamEncryptor(_EncryptionStream): :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param int frame_length: Frame length in bytes """ + _config_class = EncryptorConfig - def __init__(self, **kwargs): + def __init__(self, **kwargs): # pylint: disable=unused-argument,super-init-not-called + """Prepares necessary initial values.""" self.sequence_number = 1 self.content_type = aws_encryption_sdk.internal.utils.content_type(self.config.frame_length) self._bytes_encrypted = 0 if ( - self.config.frame_length == 0 - and ( - self.config.source_length is not None - and self.config.source_length > MAX_NON_FRAMED_SIZE - ) + self.config.frame_length == 0 + and ( + self.config.source_length is not None + and self.config.source_length > MAX_NON_FRAMED_SIZE + ) ): raise SerializationError('Source too large for non-framed message') @@ -405,7 +421,7 @@ def _prep_message(self): if self._encryption_materials.signing_key is None: self.signer = None else: - self.signer = aws_encryption_sdk.internal.crypto.Signer.from_key_bytes( + self.signer = Signer.from_key_bytes( algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key ) @@ -414,7 +430,7 @@ def _prep_message(self): algorithm=self._encryption_materials.algorithm ) - self._derived_data_key = aws_encryption_sdk.internal.crypto.derive_data_encryption_key( + self._derived_data_key = derive_data_encryption_key( source_key=self._encryption_materials.data_encryption_key.data_key, algorithm=self._encryption_materials.algorithm, message_id=message_id @@ -462,7 +478,7 @@ def _prep_non_framed(self): seq_num=1, length=self.stream_length ) - self.encryptor = aws_encryption_sdk.internal.crypto.Encryptor( + self.encryptor = Encryptor( algorithm=self._encryption_materials.algorithm, key=self._derived_data_key, associated_data=associated_data, @@ -480,7 +496,7 @@ def _read_bytes_to_non_framed_body(self, b): :param int b: Number of bytes to read :returns: Encrypted bytes from source stream - :rtype: str + :rtype: bytes """ _LOGGER.debug('Reading %s bytes', b) plaintext = self.source_stream.read(b) @@ -528,8 +544,10 @@ def _read_bytes_to_framed_body(self, b): final_frame_written = False while ( - (not finalize and plaintext) # If not finalizing on this pass, exit when plaintext is exhausted - or (finalize and not final_frame_written) # If finalizing on this pass, wait until final frame is written + # If not finalizing on this pass, exit when plaintext is exhausted + (not finalize and plaintext) + # If finalizing on this pass, wait until final frame is written + or (finalize and not final_frame_written) ): is_final_frame = finalize and len(plaintext) < self.config.frame_length bytes_in_frame = min(len(plaintext), self.config.frame_length) @@ -620,6 +638,7 @@ class DecryptorConfig(_ClientConfig): :param int max_body_length: Maximum frame size (or content length for non-framed messages) in bytes to read from ciphertext message. """ + max_body_length = attr.ib( hash=True, default=None, @@ -627,7 +646,7 @@ class DecryptorConfig(_ClientConfig): ) -class StreamDecryptor(_EncryptionStream): +class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-attributes """Provides a streaming encryptor for encrypting a stream source. Behaves as a standard file-like object. @@ -657,9 +676,11 @@ class StreamDecryptor(_EncryptionStream): :param int max_body_length: Maximum frame size (or content length for non-framed messages) in bytes to read from ciphertext message. """ + _config_class = DecryptorConfig - def __init__(self, **kwargs): + def __init__(self, **kwargs): # pylint: disable=unused-argument,super-init-not-called + """Prepares necessary initial values.""" self.last_sequence_number = 0 def _prep_message(self): @@ -681,9 +702,9 @@ def _read_header(self): header = aws_encryption_sdk.internal.formatting.deserialize.deserialize_header(self.source_stream) if ( - self.config.max_body_length is not None - and header.content_type == ContentType.FRAMED_DATA - and header.frame_length > self.config.max_body_length + self.config.max_body_length is not None + and header.content_type == ContentType.FRAMED_DATA + and header.frame_length > self.config.max_body_length ): raise CustomMaximumValueExceeded( 'Frame Size in header found larger than custom value: {found} > {custom}'.format( @@ -702,7 +723,7 @@ def _read_header(self): if decryption_materials.verification_key is None: self.verifier = None else: - self.verifier = aws_encryption_sdk.internal.crypto.Verifier.from_key_bytes( + self.verifier = Verifier.from_key_bytes( algorithm=header.algorithm, key_bytes=decryption_materials.verification_key ) @@ -715,7 +736,7 @@ def _read_header(self): algorithm=header.algorithm, verifier=self.verifier ) - self._derived_data_key = aws_encryption_sdk.internal.crypto.derive_data_encryption_key( + self._derived_data_key = derive_data_encryption_key( source_key=decryption_materials.data_key.data_key, algorithm=header.algorithm, message_id=header.message_id @@ -756,7 +777,7 @@ def _prep_non_framed(self): seq_num=1, length=self.body_length ) - self.decryptor = aws_encryption_sdk.internal.crypto.Decryptor( + self.decryptor = Decryptor( algorithm=self._header.algorithm, key=self._derived_data_key, associated_data=associated_data, @@ -771,7 +792,7 @@ def _read_bytes_from_non_framed_body(self, b): :param int b: Number of bytes to read :returns: Decrypted bytes from source stream - :rtype: str + :rtype: bytes """ _LOGGER.debug('starting non-framed body read') # Always read the entire message for non-framed message bodies. @@ -801,7 +822,7 @@ def _read_bytes_from_framed_body(self, b): :param int b: Number of bytes to read :returns: Bytes read from source stream and decrypted - :rtype: str + :rtype: bytes """ plaintext = b'' final_frame = False @@ -827,7 +848,7 @@ def _read_bytes_from_framed_body(self, b): seq_num=frame_data.sequence_number, length=len(frame_data.ciphertext) ) - plaintext += aws_encryption_sdk.internal.crypto.decrypt( + plaintext += decrypt( algorithm=self._header.algorithm, key=self._derived_data_key, encrypted_data=frame_data, @@ -874,3 +895,11 @@ def close(self): if not hasattr(self, 'footer'): raise SerializationError('Footer not read') super(StreamDecryptor, self).close() + + +__all__ = ( + 'DecryptorConfig', + 'EncryptorConfig', + 'StreamDecryptor', + 'StreamEncryptor' +) diff --git a/aws_encryption_sdk/structures.py b/src/aws_encryption_sdk/structures.py similarity index 87% rename from aws_encryption_sdk/structures.py rename to src/aws_encryption_sdk/structures.py index ae5fba0f1..77a1c1ed7 100644 --- a/aws_encryption_sdk/structures.py +++ b/src/aws_encryption_sdk/structures.py @@ -1,9 +1,21 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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 attr import six import aws_encryption_sdk.identifiers -from aws_encryption_sdk.internal.str_ops import to_str, to_bytes +from aws_encryption_sdk.internal.str_ops import to_bytes, to_str @attr.s(hash=True) @@ -26,6 +38,7 @@ class MessageHeader(object): :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 )) @@ -53,6 +66,7 @@ class MasterKeyInfo(object): :param str provider_id: MasterKey provider_id value :param bytes key_info: MasterKey key_info value """ + provider_id = attr.ib( hash=True, validator=attr.validators.instance_of((six.string_types, bytes)), @@ -73,6 +87,7 @@ class RawDataKey(object): :type key_provider: aws_encryption_sdk.structures.MasterKeyInfo :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, validator=attr.validators.instance_of(bytes)) @@ -86,6 +101,7 @@ class DataKey(object): :param bytes data_key: Plaintext data key :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, validator=attr.validators.instance_of(bytes)) encrypted_data_key = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) @@ -99,5 +115,6 @@ class EncryptedDataKey(object): :type key_provider: aws_encryption_sdk.structures.MasterKeyInfo :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)) diff --git a/test/functional/__init__.py b/test/functional/__init__.py index e69de29bb..53a960891 100644 --- a/test/functional/__init__.py +++ b/test/functional/__init__.py @@ -0,0 +1,12 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. diff --git a/test/functional/test_f_aws_encryption_sdk_client.py b/test/functional/test_f_aws_encryption_sdk_client.py index c84cca6a6..bd2437e50 100644 --- a/test/functional/test_f_aws_encryption_sdk_client.py +++ b/test/functional/test_f_aws_encryption_sdk_client.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Functional test suite for aws_encryption_sdk.kms_thick_client""" import io @@ -14,13 +26,13 @@ import aws_encryption_sdk from aws_encryption_sdk import KMSMasterKeyProvider from aws_encryption_sdk.caches import build_decryption_materials_cache_key, build_encryption_materials_cache_key -from aws_encryption_sdk.materials_managers import EncryptionMaterialsRequest, DecryptionMaterialsRequest -from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.exceptions import CustomMaximumValueExceeded +from aws_encryption_sdk.identifiers import Algorithm, EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.internal.formatting.encryption_context import serialize_encryption_context -from aws_encryption_sdk.identifiers import WrappingAlgorithm, EncryptionKeyType, Algorithm from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider -from aws_encryption_sdk.exceptions import CustomMaximumValueExceeded +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest VALUES = { 'frame_lengths': ( # Assuming 1280 byte plaintext: diff --git a/test/functional/test_f_crypto.py b/test/functional/test_f_crypto.py index 42c8a4058..095257d30 100644 --- a/test/functional/test_f_crypto.py +++ b/test/functional/test_f_crypto.py @@ -1,12 +1,24 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Functional test suite for Elliptic Curve static length signature calculation.""" -import pytest - +from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.backends import default_backend +import pytest import aws_encryption_sdk -from aws_encryption_sdk.internal.crypto import _ecc_static_length_signature, Signer +from aws_encryption_sdk.internal.crypto.authentication import Signer +from aws_encryption_sdk.internal.crypto.elliptic_curve import _ecc_static_length_signature # Run several of each type to make get a high probability of forcing signature length correction diff --git a/test/functional/test_f_crypto_iv.py b/test/functional/test_f_crypto_iv.py index 8137d5790..63eefdeff 100644 --- a/test/functional/test_f_crypto_iv.py +++ b/test/functional/test_f_crypto_iv.py @@ -1,9 +1,21 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite for IV generation helper functions.""" import pytest -from aws_encryption_sdk.internal.crypto.iv import frame_iv, non_framed_body_iv, header_auth_iv -from aws_encryption_sdk.internal.defaults import ALGORITHM, MAX_FRAME_COUNT from aws_encryption_sdk.exceptions import ActionNotAllowedError +from aws_encryption_sdk.internal.crypto.iv import frame_iv, header_auth_iv, non_framed_body_iv +from aws_encryption_sdk.internal.defaults import ALGORITHM, MAX_FRAME_COUNT VALUES = { 'ivs': { diff --git a/test/functional/test_f_xcompat.py b/test/functional/test_f_xcompat.py index 72f7d14fd..3bf0ba6fd 100644 --- a/test/functional/test_f_xcompat.py +++ b/test/functional/test_f_xcompat.py @@ -1,7 +1,19 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Functional test suite testing decryption of known good test files encrypted using static RawMasterKeyProvider.""" from __future__ import print_function -from collections import defaultdict import base64 +from collections import defaultdict import json import logging import os @@ -13,8 +25,8 @@ import aws_encryption_sdk from aws_encryption_sdk.exceptions import InvalidKeyIdError -from aws_encryption_sdk.identifiers import WrappingAlgorithm, EncryptionKeyType -from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.internal.str_ops import to_bytes from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider @@ -22,6 +34,8 @@ # Environment-specific test file locator. May not always exist. def _file_root(): return '.' + + try: from .aws_test_file_finder import file_root except ImportError: @@ -58,7 +72,7 @@ class StaticStoredMasterKeyProvider(RawMasterKeyProvider): provider_id = 'static-aws-xcompat' def _get_raw_key(self, key_id): - """""" + """Finds a loaded raw key.""" try: algorithm, key_bits, padding_algorithm, padding_hash = key_id.upper().split(b'.', 3) key_bits = int(key_bits) @@ -72,13 +86,13 @@ def _get_raw_key(self, key_id): wrapping_key_type=key_type ) except KeyError: - _LOGGER.exception('Unknown Key ID: {}'.format(key_id)) + _LOGGER.exception('Unknown Key ID: %s', key_id) raise InvalidKeyIdError('Unknown Key ID: {}'.format(key_id)) @attr.s class RawKeyDescription(object): - """""" + """Customer raw key descriptor used by StaticStoredMasterKeyProvider.""" encryption_algorithm = attr.ib(validator=attr.validators.instance_of(six.string_types)) key_bits = attr.ib(validator=attr.validators.instance_of(int)) padding_algorithm = attr.ib(validator=attr.validators.instance_of(six.string_types)) @@ -86,7 +100,7 @@ class RawKeyDescription(object): @property def key_id(self): - """""" + """Build a key ID from instance parameters.""" return '.'.join([self.encryption_algorithm, str(self.key_bits), self.padding_algorithm, self.padding_hash]) @@ -100,8 +114,14 @@ class Scenario(object): def _generate_test_cases(): + try: + root_dir = os.path.abspath(file_root()) + except Exception: # pylint: disable=broad-except + root_dir = os.getcwd() + if not os.path.isdir(root_dir): + root_dir = os.getcwd() base_dir = os.path.join( - os.path.abspath(file_root()), + root_dir, 'aws_encryption_sdk_resources' ) ciphertext_manifest_path = os.path.join( diff --git a/test/integration/__init__.py b/test/integration/__init__.py index e69de29bb..53a960891 100644 --- a/test/integration/__init__.py +++ b/test/integration/__init__.py @@ -0,0 +1,12 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. diff --git a/test/integration/integration_test_utils.py b/test/integration/integration_test_utils.py new file mode 100644 index 000000000..541d06b85 --- /dev/null +++ b/test/integration/integration_test_utils.py @@ -0,0 +1,73 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Utility functions to handle configuration, credentials setup, and test skip +decision making for integration tests.""" +import os + +import botocore.session +from six.moves.configparser import ConfigParser +from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider + +SKIP_MESSAGE = 'Skipping tests due to blocking environment variable' + + +def skip_tests(): + blocker_var_name = 'AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_CONTROL' + blocker_val = os.environ.get(blocker_var_name, None) + if blocker_val != 'RUN': + return True + return False + + +def read_test_config(): + """Reads the test_values config file.""" + config = ConfigParser() + config_file = os.sep.join([os.path.dirname(__file__), 'test_values.conf']) + config_readme = os.sep.join([os.path.dirname(__file__), 'README']) + if not os.path.isfile(config_file): + raise Exception('Integration test config file missing. See setup instructions in {}'.format(config_readme)) + config.read(config_file) + return config + + +def get_cmk_arn(config): + """Retrieves the target CMK ARN from the received config.""" + return config.get('TestKMSThickClientIntegration', 'cmk_arn') + + +def setup_botocore_session(config): + """Configures a botocore session based on the received config.""" + aws_params = {} + for key in ['aws_access_key_id', 'aws_secret_access_key', 'aws_session_token']: + try: + aws_params[key] = config.get('TestKMSThickClientIntegration', key) + except Exception: + pass + botocore_session = botocore.session.Session() + if aws_params: + botocore_session.set_credentials( + access_key=aws_params['aws_access_key_id'], + secret_key=aws_params['aws_secret_access_key'], + token=aws_params['aws_session_token'] + ) + return botocore_session + + +def setup_kms_master_key_provider(): + """Reads the test_values config file and builds the requested KMS Master Key Provider.""" + config = read_test_config() + cmk_arn = get_cmk_arn(config) + botocore_session = setup_botocore_session(config) + kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore_session) + kms_master_key_provider.add_master_key(cmk_arn) + return kms_master_key_provider diff --git a/test/integration/test_i_aws_encrytion_sdk_client.py b/test/integration/test_i_aws_encrytion_sdk_client.py index 2ac94d4d1..a0032ddb7 100644 --- a/test/integration/test_i_aws_encrytion_sdk_client.py +++ b/test/integration/test_i_aws_encrytion_sdk_client.py @@ -1,28 +1,27 @@ -""" - Integration test suite for aws_encryption_sdk.kms_thick_client -""" +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Integration test suite for `aws_encryption_sdk`.""" import io -import os import unittest -import botocore.session import six -from six.moves.configparser import ConfigParser import aws_encryption_sdk from aws_encryption_sdk.identifiers import Algorithm -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider -SKIP_MESSAGE = 'Skipping tests due to blocking environment variable' +from .integration_test_utils import setup_kms_master_key_provider, SKIP_MESSAGE, skip_tests -def skip_tests(): - blocker_var_name = 'AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_CONTROL' - blocker_val = os.environ.get(blocker_var_name, None) - if blocker_val != 'RUN': - return True - return False - VALUES = { 'plaintext_128': six.b( '\xa3\xf6\xbc\x89\x95\x15(\xc8}\\\x8d=zu^{JA\xc1\xe9\xf0&m\xe6TD\x03' @@ -40,50 +39,6 @@ def skip_tests(): } -def read_test_config(): - """Reads the test_values config file.""" - config = ConfigParser() - config_file = os.sep.join([os.path.dirname(__file__), 'test_values.conf']) - config_readme = os.sep.join([os.path.dirname(__file__), 'README']) - if not os.path.isfile(config_file): - raise Exception('Integration test config file missing. See setup instructions in {}'.format(config_readme)) - config.read(config_file) - return config - - -def get_cmk_arn(config): - """Retrieves the target CMK ARN from the received config.""" - return config.get('TestKMSThickClientIntegration', 'cmk_arn') - - -def setup_botocore_session(config): - """Configures a botocore session based on the received config.""" - aws_params = {} - for key in ['aws_access_key_id', 'aws_secret_access_key', 'aws_session_token']: - try: - aws_params[key] = config.get('TestKMSThickClientIntegration', key) - except Exception: - pass - botocore_session = botocore.session.Session() - if aws_params: - botocore_session.set_credentials( - access_key=aws_params['aws_access_key_id'], - secret_key=aws_params['aws_secret_access_key'], - token=aws_params['aws_session_token'] - ) - return botocore_session - - -def setup_kms_master_key_provider(): - """Reads the test_values config file and builds the requested KMS Master Key Provider.""" - config = read_test_config() - cmk_arn = get_cmk_arn(config) - botocore_session = setup_botocore_session(config) - kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore_session) - kms_master_key_provider.add_master_key(cmk_arn) - return kms_master_key_provider - - class TestKMSThickClientIntegration(unittest.TestCase): def setUp(self): diff --git a/test/integration/test_i_docs_examples_bytes.py b/test/integration/test_i_docs_examples_bytes.py deleted file mode 100644 index 5e37e706c..000000000 --- a/test/integration/test_i_docs_examples_bytes.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Unit test suite for the Bytes Streams examples in the AWS-hosted documentation. - -.. note:: - These tests rely on discoverable AWS credentials existing. -""" -import os -import tempfile - -import pytest - -from .test_i_aws_encrytion_sdk_client import skip_tests, SKIP_MESSAGE -from .docs_examples_bytes import cycle_file - - -@pytest.mark.skipif(skip_tests(), reason=SKIP_MESSAGE) -def test_cycle_file(): - _handle, filename = tempfile.mkstemp() - with open(filename, 'wb') as f: - f.write(os.urandom(1024)) - try: - new_files = cycle_file(source_plaintext_filename=filename) - for f in new_files: - os.remove(f) - finally: - os.remove(filename) diff --git a/test/integration/test_i_docs_examples_multiple_providers.py b/test/integration/test_i_docs_examples_multiple_providers.py deleted file mode 100644 index 6fb2aea53..000000000 --- a/test/integration/test_i_docs_examples_multiple_providers.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Unit test suite for the Bytes Streams Multiple Providers examples in the AWS-hosted documentation. - -.. note:: - These tests rely on discoverable AWS credentials existing. -""" -import os -import tempfile - -import pytest - -from .test_i_aws_encrytion_sdk_client import ( - read_test_config, get_cmk_arn, setup_botocore_session, skip_tests, SKIP_MESSAGE -) -from .docs_examples_multiple_providers import cycle_file - - -@pytest.mark.skipif(skip_tests(), reason=SKIP_MESSAGE) -def test_cycle_file(): - config = read_test_config() - cmk_arn = get_cmk_arn(config) - botocore_session = setup_botocore_session(config) - _handle, filename = tempfile.mkstemp() - with open(filename, 'wb') as f: - f.write(os.urandom(1024)) - try: - new_files = cycle_file( - key_arn=cmk_arn, - source_plaintext_filename=filename, - botocore_session=botocore_session - ) - for f in new_files: - os.remove(f) - finally: - os.remove(filename) diff --git a/test/integration/test_i_docs_examples_strings.py b/test/integration/test_i_docs_examples_strings.py deleted file mode 100644 index a2420b15c..000000000 --- a/test/integration/test_i_docs_examples_strings.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Unit test suite for the Strings examples in the AWS-hosted documentation. - -.. note:: - These tests rely on discoverable AWS credentials existing. -""" -import os - -import pytest - -from .test_i_aws_encrytion_sdk_client import ( - read_test_config, get_cmk_arn, setup_botocore_session, skip_tests, SKIP_MESSAGE -) -from .docs_examples_strings import cycle_string - - -@pytest.mark.skipif(skip_tests(), reason=SKIP_MESSAGE) -def test_cycle_string(): - plaintext = os.urandom(1024) - config = read_test_config() - cmk_arn = get_cmk_arn(config) - botocore_session = setup_botocore_session(config) - cycle_string( - key_arn=cmk_arn, - source_plaintext=plaintext, - botocore_session=botocore_session - ) diff --git a/test/integration/test_i_xcompat_kms.py b/test/integration/test_i_xcompat_kms.py index 16ce7df42..fa634ebb0 100644 --- a/test/integration/test_i_xcompat_kms.py +++ b/test/integration/test_i_xcompat_kms.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Integration test suite testing decryption of known good test files encrypted using KMSMasterKeyProvider.""" import json import os @@ -6,12 +18,14 @@ import aws_encryption_sdk -from .test_i_aws_encrytion_sdk_client import setup_kms_master_key_provider, skip_tests, SKIP_MESSAGE +from .integration_test_utils import setup_kms_master_key_provider, skip_tests, SKIP_MESSAGE # Environment-specific test file locator. May not always exist. def _file_root(): return '.' + + try: from .aws_test_file_finder import file_root except ImportError: @@ -23,8 +37,14 @@ def _generate_test_cases(): return [] kms_key_provider = setup_kms_master_key_provider() + try: + root_dir = os.path.abspath(file_root()) + except Exception: # pylint: disable=broad-except + root_dir = os.getcwd() + if not os.path.isdir(root_dir): + root_dir = os.getcwd() base_dir = os.path.join( - os.path.abspath(file_root()), + root_dir, 'aws_encryption_sdk_resources' ) ciphertext_manifest_path = os.path.join( diff --git a/test/pylintrc b/test/pylintrc new file mode 100644 index 000000000..cd71da068 --- /dev/null +++ b/test/pylintrc @@ -0,0 +1,33 @@ +[MESSAGES CONTROL] +# Disabling messages that we either don't care about +# for tests or are necessary to break for tests. +# +# C0103 : invalid-name (we prefer long, descriptive, names for tests) +# C0111 : missing-docstring (we don't write docstrings for tests) +# E0110 : abstract-class-instantiated (we do this on purpose to test that they are enforced) +# E1101 : no-member (raised on patched objects with mock checks) +# R0201 : no-self-use (common pattern when using unittest: can be enabled once all tests are refactored to pytest) +# R0801 : duplicate-code (unit tests for similar things tend to be similar) +# R0902 : too-many-instance-attributes (common pattern when using unittest: can be enabled once all tests are refactored to pytest) +# R0903 : too-few-public-methods (common when setting up mock classes) +# R0904 : too-many-public-methods (common pattern when using unittest: can be enabled once all tests are refactored to pytest) +# R0914 : too-many-locals (common pattern when using unittest: can be enabled once all tests are refactored to pytest) +# R0915 : too-many-statements (common pattern when using unittest: can be enabled once all tests are refactored to pytest) +# W0201 : attribute-defined-outside-init (broken by some unittest monkeypatching: can be enabled once all tests are refactored to pytest) +# W0212 : protected-access (raised when calling _ methods) +# W0223 : abstract-method (we do this on purpose to test that they are enforced) +# W0621 : redefined-outer-name (raised when using pytest-mock) +# W0613 : unused-argument (raised when patches are needed but not called) +disable = C0103, C0111, E0110, E1101, R0201, R0801, R0902, R0903, R0904, R0914, R0915, W0201, W0212, W0223, W0621, W0613 + +[VARIABLES] +additional-builtins = raw_input + +[DESIGN] +max-args = 10 + +[FORMAT] +max-line-length = 120 + +[REPORTS] +msg-template = {path}:{line}: [{msg_id}({symbol}), {obj}] {msg} diff --git a/test/unit/__init__.py b/test/unit/__init__.py index e69de29bb..53a960891 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -0,0 +1,12 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. diff --git a/test/unit/test_aws_encryption_sdk.py b/test/unit/test_aws_encryption_sdk.py index 366719431..c5b1695c3 100644 --- a/test/unit/test_aws_encryption_sdk.py +++ b/test/unit/test_aws_encryption_sdk.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite for high-level functions in aws_encryption_sdk module""" import unittest diff --git a/test/unit/test_caches.py b/test/unit/test_caches.py index 64eec565a..20ae3b950 100644 --- a/test/unit/test_caches.py +++ b/test/unit/test_caches.py @@ -1,17 +1,29 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.caches common functions.""" from base64 import b64decode import struct -from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes import pytest -from aws_encryption_sdk.identifiers import Algorithm from aws_encryption_sdk.caches import ( - _encryption_context_hash, build_encryption_materials_cache_key, - _encrypted_data_keys_hash, _512_BIT_PAD, build_decryption_materials_cache_key + _512_BIT_PAD, _encrypted_data_keys_hash, _encryption_context_hash, + build_decryption_materials_cache_key, build_encryption_materials_cache_key ) -from aws_encryption_sdk.materials_managers import EncryptionMaterialsRequest, DecryptionMaterialsRequest +from aws_encryption_sdk.identifiers import Algorithm +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.structures import DataKey, MasterKeyInfo diff --git a/test/unit/test_caches_base.py b/test/unit/test_caches_base.py index a7803e1ef..a8c98cce4 100644 --- a/test/unit/test_caches_base.py +++ b/test/unit/test_caches_base.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite for CryptoMaterialsCache""" import pytest diff --git a/test/unit/test_caches_crypto_cache_entry.py b/test/unit/test_caches_crypto_cache_entry.py index ca83577b4..9c038a269 100644 --- a/test/unit/test_caches_crypto_cache_entry.py +++ b/test/unit/test_caches_crypto_cache_entry.py @@ -1,14 +1,26 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite for CryptoMaterialsCacheEntry and CryptoMaterialsCacheEntryHints""" from mock import MagicMock import pytest -from pytest_mock import mocker +from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.caches from aws_encryption_sdk.caches import ( - CryptoMaterialsCacheEntryHints, CryptoMaterialsCacheEntry + CryptoMaterialsCacheEntry, CryptoMaterialsCacheEntryHints ) from aws_encryption_sdk.exceptions import NotSupportedError -from aws_encryption_sdk.materials_managers import EncryptionMaterials, DecryptionMaterials +from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials _VALID_KWARGS = { 'CryptoMaterialsCacheEntryHints': dict(lifetime=5.0), diff --git a/test/unit/test_caches_local.py b/test/unit/test_caches_local.py index e56b75bee..b6e6229f1 100644 --- a/test/unit/test_caches_local.py +++ b/test/unit/test_caches_local.py @@ -1,14 +1,26 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit testing suite for LocalCryptoMaterialsCache""" from collections import deque, OrderedDict import weakref -from mock import MagicMock, sentinel, call +from mock import call, MagicMock, sentinel import pytest -from pytest_mock import mocker +from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.caches.local -from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache, _OPPORTUNISTIC_EVICTION_ROUNDS -from aws_encryption_sdk.exceptions import NotSupportedError, CacheKeyError +from aws_encryption_sdk.caches.local import _OPPORTUNISTIC_EVICTION_ROUNDS, LocalCryptoMaterialsCache +from aws_encryption_sdk.exceptions import CacheKeyError, NotSupportedError def build_lcmc(**custom_kwargs): @@ -302,7 +314,9 @@ def test_get_encryption_materials(patch_get_single_entry): ) patch_get_single_entry.assert_called_once_with(sentinel.cache_key) - patch_get_single_entry.return_value._update_with_message_bytes_encrypted.assert_called_once_with(sentinel.plaintext_length) + patch_get_single_entry.return_value._update_with_message_bytes_encrypted.assert_called_once_with( + sentinel.plaintext_length + ) assert test is patch_get_single_entry.return_value diff --git a/test/unit/test_caches_null.py b/test/unit/test_caches_null.py index cf429a3af..c2f960615 100644 --- a/test/unit/test_caches_null.py +++ b/test/unit/test_caches_null.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit testing suite for NullCryptoMaterialsCache""" from mock import MagicMock import pytest @@ -5,7 +17,7 @@ from aws_encryption_sdk.caches import CryptoMaterialsCacheEntry from aws_encryption_sdk.caches.null import NullCryptoMaterialsCache from aws_encryption_sdk.exceptions import CacheKeyError -from aws_encryption_sdk.materials_managers import EncryptionMaterials, DecryptionMaterials +from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials def test_put_encryption_materials(): diff --git a/test/unit/test_crypto.py b/test/unit/test_crypto.py index 40b22ab90..0fc8a8d98 100644 --- a/test/unit/test_crypto.py +++ b/test/unit/test_crypto.py @@ -1,21 +1,18 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto""" -import unittest - from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.utils import InterfaceNotImplemented -from mock import MagicMock, patch, sentinel -import pytest -from pytest_mock import mocker -import six - -from aws_encryption_sdk.exceptions import ( - NotSupportedError, InvalidDataKeyError, IncorrectMasterKeyError, SerializationError -) -import aws_encryption_sdk.internal.crypto -from aws_encryption_sdk.internal.defaults import ENCODED_SIGNER_KEY, ALGORITHM -from aws_encryption_sdk.identifiers import Algorithm, EncryptionType, EncryptionKeyType -from aws_encryption_sdk.internal.structures import EncryptedData VALUES = { 'iv': b'asdfzxcvqwer', @@ -30,10 +27,10 @@ 'finalize': b'ex_finalize_plaintext' }, 'ecc_private_key_prime': ec.EllipticCurvePrivateNumbers( - private_value=17889917494901019016174171250566479258605401433636341402964733440624721474929058596523395852088194487740674876114796, + private_value=17889917494901019016174171250566479258605401433636341402964733440624721474929058596523395852088194487740674876114796, # noqa pylint: disable=line-too-long public_numbers=ec.EllipticCurvePublicNumbers( - x=9007459108199787568878509110290896090564999412935334592925575746287962476803074379865243742719141579140901207554948, - y=1574487057865803742065434835341798147751257167933485863820054382900062216413864643113244902766112081885540347590369, + x=9007459108199787568878509110290896090564999412935334592925575746287962476803074379865243742719141579140901207554948, # noqa pylint: disable=line-too-long + y=1574487057865803742065434835341798147751257167933485863820054382900062216413864643113244902766112081885540347590369, # noqa pylint: disable=line-too-long curve=ec.SECP384R1() ) ).private_key(default_backend()), @@ -56,939 +53,13 @@ b'\xa5\xe1S\xb2Gw\x15(\xb6\xe1' ), 'ecc_private_key_char2': ec.EllipticCurvePrivateNumbers( - private_value=131512833187976200862897177240257889476359607892474090119002870596121284569326171944650239612201181144875264734209664973820, + private_value=131512833187976200862897177240257889476359607892474090119002870596121284569326171944650239612201181144875264734209664973820, # noqa pylint: disable=line-too-long public_numbers=ec.EllipticCurvePublicNumbers( - x=783372629152728216190118671643020486604880277607267246139026062120084499867233383227220456289236528291350315438332972681898, - y=657053766035459398820670308946963262342583342616783849689721971058264156234178067988487273332138651529574836305189297847674, + x=783372629152728216190118671643020486604880277607267246139026062120084499867233383227220456289236528291350315438332972681898, # noqa pylint: disable=line-too-long + y=657053766035459398820670308946963262342583342616783849689721971058264156234178067988487273332138651529574836305189297847674, # noqa pylint: disable=line-too-long curve=ec.SECT409K1() ) ).private_key(default_backend()) } VALUES['ciphertext'] = VALUES['encryptor']['update'] + VALUES['encryptor']['finalize'] VALUES['plaintext'] = VALUES['decryptor']['update'] + VALUES['decryptor']['finalize'] - - -def test_verifier_from_key_bytes(): - check = aws_encryption_sdk.internal.crypto.Verifier( - algorithm=ALGORITHM, - public_key=VALUES['ecc_private_key_prime'].public_key() - ) - test = aws_encryption_sdk.internal.crypto.Verifier.from_key_bytes( - algorithm=ALGORITHM, - key_bytes=VALUES['ecc_private_key_prime_public_bytes'] - ) - assert check.key.public_numbers() == test.key.public_numbers() - - -def test_verifier_key_bytes(): - test = aws_encryption_sdk.internal.crypto.Verifier( - algorithm=ALGORITHM, - public_key=VALUES['ecc_private_key_prime'].public_key() - ) - assert test.key_bytes() == VALUES['ecc_private_key_prime_public_bytes'] - - -def test_signer_from_key_bytes(): - check = aws_encryption_sdk.internal.crypto.Signer( - algorithm=ALGORITHM, - key=VALUES['ecc_private_key_prime'] - ) - test = aws_encryption_sdk.internal.crypto.Signer.from_key_bytes( - algorithm=ALGORITHM, - key_bytes=VALUES['ecc_private_key_prime_private_bytes'] - ) - assert check.key.private_numbers().private_value == test.key.private_numbers().private_value - - -def test_signer_key_bytes(): - test = aws_encryption_sdk.internal.crypto.Signer( - algorithm=ALGORITHM, - key=VALUES['ecc_private_key_prime'] - ) - assert test.key_bytes() == VALUES['ecc_private_key_prime_private_bytes'] - - -class TestCrypto(unittest.TestCase): - - def setUp(self): - # Set up mock algorithm for tests - self.mock_algorithm = MagicMock() - self.mock_encryption_algorithm = MagicMock() - self.mock_encryption_algorithm.return_value = sentinel.encryption_algorithm - self.mock_algorithm.encryption_algorithm = self.mock_encryption_algorithm - self.mock_encryption_mode = MagicMock() - self.mock_encryption_mode.return_value = sentinel.encryption_mode - self.mock_algorithm.encryption_mode = self.mock_encryption_mode - self.mock_algorithm.iv_len = sentinel.iv_len - self.mock_algorithm.data_key_len = sentinel.data_key_len - self.mock_algorithm.algorithm_id = sentinel.algorithm_id - self.mock_kdf_hash_type = MagicMock() - self.mock_kdf_hash_type.return_value = sentinel.hash_instance - self.mock_algorithm.kdf_hash_type = self.mock_kdf_hash_type - self.mock_signing_algorithm_info = MagicMock() - self.mock_signing_algorithm_info.return_value = sentinel.curve_instance - self.mock_algorithm.signing_algorithm_info = self.mock_signing_algorithm_info - self.mock_kdf_type_instance = MagicMock() - self.mock_kdf_type_instance.derive.return_value = sentinel.derived_key - self.mock_kdf_type = MagicMock() - self.mock_kdf_type.return_value = self.mock_kdf_type_instance - self.mock_algorithm.kdf_type = self.mock_kdf_type - self.mock_algorithm.kdf_input_len = sentinel.kdf_input_len - # Set up mock wrapping algorithm for tests - self.mock_wrapping_algorithm = MagicMock() - self.mock_wrapping_algorithm.padding = sentinel.padding - self.mock_wrapping_algorithm.algorithm = sentinel.algorithm - self.mock_wrapping_key = MagicMock() - self.mock_wrapping_rsa_private_key = MagicMock() - self.mock_wrapping_rsa_public_key = MagicMock() - self.mock_wrapping_rsa_private_key.public_key.return_value = self.mock_wrapping_rsa_public_key - self.mock_encrypted_data = EncryptedData( - iv=VALUES['iv'], - ciphertext=VALUES['ciphertext'], - tag=VALUES['tag'] - ) - # Set up cryptography backend patch - self.mock_cryptography_backend_patcher = patch('aws_encryption_sdk.internal.crypto.default_backend') - self.mock_cryptography_backend = self.mock_cryptography_backend_patcher.start() - self.mock_cryptography_backend.return_value = sentinel.crypto_backend - # Set up cryptography Cipher patch - self.mock_cryptography_cipher_patcher = patch('aws_encryption_sdk.internal.crypto.Cipher') - self.mock_cryptography_cipher = self.mock_cryptography_cipher_patcher.start() - self.mock_cryptography_cipher_instance = MagicMock() - self.mock_cryptography_cipher.return_value = self.mock_cryptography_cipher_instance - self.mock_encryptor = MagicMock() - self.mock_encryptor.update.return_value = VALUES['encryptor']['update'] - self.mock_encryptor.finalize.return_value = VALUES['encryptor']['finalize'] - self.mock_encryptor.tag = VALUES['tag'] - self.mock_cryptography_cipher_instance.encryptor.return_value = self.mock_encryptor - self.mock_decryptor = MagicMock() - self.mock_decryptor.update.return_value = VALUES['decryptor']['update'] - self.mock_decryptor.finalize.return_value = VALUES['decryptor']['finalize'] - self.mock_cryptography_cipher_instance.decryptor.return_value = self.mock_decryptor - # Set up mock ec patch - self.mock_cryptography_ec_patcher = patch('aws_encryption_sdk.internal.crypto.ec') - self.mock_cryptography_ec = self.mock_cryptography_ec_patcher.start() - self.mock_cryptography_ec.ECDSA.return_value = sentinel.ecdsa_instance - self.mock_signer_private_key = MagicMock() - self.mock_signer_private_key.signer.return_value = sentinel.signer_instance - self.mock_cryptography_ec.generate_private_key.return_value = self.mock_signer_private_key - self.mock_hasher = MagicMock() - self.mock_hasher.finalize.return_value = sentinel.signature - self.mock_verifier_public_key = MagicMock() - self.mock_verifier_instance = MagicMock() - self.mock_verifier_public_key.verifier.return_value = self.mock_verifier_instance - # Set up mock load cryptography serialization patch - self.mock_crypto_serialization_patcher = patch('aws_encryption_sdk.internal.crypto.serialization') - self.mock_crypto_serialization = self.mock_crypto_serialization_patcher.start() - self.mock_crypto_serialization.load_pem_private_key.return_value = self.mock_wrapping_rsa_private_key - self.mock_crypto_serialization.load_pem_public_key.return_value = self.mock_wrapping_rsa_public_key - # Set up mock cryptography interface verification patch - self.mock_crypto_verify_interface_patcher = patch( - 'aws_encryption_sdk.internal.crypto.cryptography.utils.verify_interface' - ) - self.mock_crypto_verify_interface = self.mock_crypto_verify_interface_patcher.start() - - def tearDown(self): - self.mock_cryptography_backend_patcher.stop() - self.mock_cryptography_cipher_patcher.stop() - self.mock_cryptography_ec_patcher.stop() - self.mock_crypto_serialization_patcher.stop() - self.mock_crypto_verify_interface_patcher.stop() - - def test_encrypt(self): - test = aws_encryption_sdk.internal.crypto.encrypt( - algorithm=self.mock_algorithm, - key=sentinel.key, - plaintext=sentinel.plaintext, - associated_data=sentinel.aad, - iv=VALUES['random'] - ) - self.mock_algorithm.encryption_algorithm.assert_called_once_with(sentinel.key) - self.mock_algorithm.encryption_mode.assert_called_once_with(VALUES['random']) - self.mock_cryptography_cipher.assert_called_with( - sentinel.encryption_algorithm, - sentinel.encryption_mode, - backend=sentinel.crypto_backend - ) - assert self.mock_cryptography_cipher_instance.encryptor.called - self.mock_encryptor.authenticate_additional_data.assert_called_with( - sentinel.aad - ) - self.mock_encryptor.update.assert_called_with(sentinel.plaintext) - assert self.mock_encryptor.finalize.called - assert test == EncryptedData( - VALUES['random'], - VALUES['ciphertext'], - VALUES['tag'] - ) - - def test_decrypt(self): - test = aws_encryption_sdk.internal.crypto.decrypt( - algorithm=self.mock_algorithm, - key=sentinel.key, - encrypted_data=EncryptedData( - VALUES['iv'], - VALUES['ciphertext'], - VALUES['tag'] - ), - associated_data=sentinel.aad - ) - self.mock_cryptography_cipher.assert_called_with( - sentinel.encryption_algorithm, - sentinel.encryption_mode, - backend=sentinel.crypto_backend - ) - assert self.mock_cryptography_cipher_instance.decryptor.called - self.mock_decryptor.authenticate_additional_data.assert_called_with( - sentinel.aad - ) - self.mock_decryptor.update.assert_called_with(VALUES['ciphertext']) - assert self.mock_decryptor.finalize.called - assert test, VALUES['plaintext'] - - @patch('aws_encryption_sdk.internal.crypto.struct.pack') - def test_derive_data_encryption_key_with_hkdf(self, mock_pack): - """Validate that the derive_data_encryption_key - function works as expected for algorithms with - a defined HKDF hash function. - """ - mock_pack.return_value = sentinel.packed_info - self.mock_algorithm.kdf_hash_type.return_value = sentinel.kdf_hash_type - test = aws_encryption_sdk.internal.crypto.derive_data_encryption_key( - source_key=sentinel.source_key, - algorithm=self.mock_algorithm, - message_id=sentinel.message_id - ) - mock_pack.assert_called_with( - '>H16s', - sentinel.algorithm_id, - sentinel.message_id - ) - self.mock_kdf_type.assert_called_with( - algorithm=sentinel.kdf_hash_type, - length=sentinel.data_key_len, - salt=None, - info=sentinel.packed_info, - backend=sentinel.crypto_backend - ) - self.mock_kdf_type_instance.derive.assert_called_with( - sentinel.source_key - ) - assert test == sentinel.derived_key - - def test_derive_data_encryption_key_no_hkdf(self): - """Validate that the derive_data_encryption_key - function works as expected for algorithms with - no defined HKDF hash function. - """ - self.mock_algorithm.kdf_type = None - test = aws_encryption_sdk.internal.crypto.derive_data_encryption_key( - source_key=sentinel.source_key, - algorithm=self.mock_algorithm, - message_id=sentinel.message_id - ) - assert not self.mock_kdf_type.called - assert test == sentinel.source_key - - @patch('aws_encryption_sdk.internal.crypto.encode_dss_signature') - @patch('aws_encryption_sdk.internal.crypto.decode_dss_signature') - @patch('aws_encryption_sdk.internal.crypto.Prehashed') - def test_ecc_static_length_signature_first_try(self, mock_prehashed, mock_decode, mock_encode): - self.mock_algorithm.signature_len = 55 - self.mock_signer_private_key.sign.return_value = b'a' * 55 - test_signature = aws_encryption_sdk.internal.crypto._ecc_static_length_signature( - key=self.mock_signer_private_key, - algorithm=self.mock_algorithm, - digest=sentinel.digest - ) - mock_prehashed.assert_called_once_with(self.mock_algorithm.signing_hash_type.return_value) - self.mock_cryptography_ec.ECDSA.assert_called_once_with(mock_prehashed.return_value) - self.mock_signer_private_key.sign.assert_called_once_with( - sentinel.digest, - self.mock_cryptography_ec.ECDSA.return_value - ) - assert not mock_decode.called - assert not mock_encode.called - assert test_signature is self.mock_signer_private_key.sign.return_value - - @patch('aws_encryption_sdk.internal.crypto.encode_dss_signature') - @patch('aws_encryption_sdk.internal.crypto.decode_dss_signature') - @patch('aws_encryption_sdk.internal.crypto.Prehashed') - def test_ecc_static_length_signature_single_negation(self, mock_prehashed, mock_decode, mock_encode): - self.mock_algorithm.signature_len = 55 - self.mock_algorithm.signing_algorithm_info.name = 'secp256r1' - self.mock_signer_private_key.sign.return_value = b'a' - mock_decode.return_value = sentinel.r, 100 - mock_encode.return_value = 'a' * 55 - test_signature = aws_encryption_sdk.internal.crypto._ecc_static_length_signature( - key=self.mock_signer_private_key, - algorithm=self.mock_algorithm, - digest=sentinel.digest - ) - assert len(self.mock_signer_private_key.sign.mock_calls) == 1 - mock_decode.assert_called_once_with(b'a') - mock_encode.assert_called_once_with( - sentinel.r, - aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp256r1'].order - 100 - ) - assert test_signature is mock_encode.return_value - - @patch('aws_encryption_sdk.internal.crypto.encode_dss_signature') - @patch('aws_encryption_sdk.internal.crypto.decode_dss_signature') - @patch('aws_encryption_sdk.internal.crypto.Prehashed') - def test_ecc_static_length_signature_recalculate(self, mock_prehashed, mock_decode, mock_encode): - self.mock_algorithm.signature_len = 55 - self.mock_algorithm.signing_algorithm_info.name = 'secp256r1' - self.mock_signer_private_key.sign.side_effect = (b'a', b'b' * 55) - mock_decode.return_value = sentinel.r, 100 - mock_encode.return_value = 'a' * 100 - test_signature = aws_encryption_sdk.internal.crypto._ecc_static_length_signature( - key=self.mock_signer_private_key, - algorithm=self.mock_algorithm, - digest=sentinel.digest - ) - assert len(self.mock_signer_private_key.sign.mock_calls) == 2 - assert len(mock_decode.mock_calls) == 1 - assert len(mock_encode.mock_calls) == 1 - assert test_signature == b'b' * 55 - - def test_ecc_encode_compressed_point_prime(self): - """Validate that the _ecc_encode_compressed_point function - works as expected for prime field curves. - """ - compressed_point = aws_encryption_sdk.internal.crypto._ecc_encode_compressed_point( - private_key=VALUES['ecc_private_key_prime'] - ) - assert compressed_point == VALUES['ecc_compressed_point'] - - def test_ecc_encode_compressed_point_characteristic_two(self): - """Validate that the _ecc_encode_compressed_point function - works as expected for characteristic 2 field curves. - """ - with six.assertRaisesRegex(self, NotSupportedError, 'Non-prime curves are not supported at this time'): - aws_encryption_sdk.internal.crypto._ecc_encode_compressed_point(VALUES['ecc_private_key_char2']) - - def test_ecc_decode_compressed_point_infinity(self): - with six.assertRaisesRegex(self, NotSupportedError, 'Points at infinity are not allowed'): - aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point( - curve=ec.SECP384R1(), - compressed_point=b'' - ) - - def test_ecc_decode_compressed_point_prime(self): - """Validate that the _ecc_decode_compressed_point function - works as expected for prime field curves. - """ - x, y = aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point( - curve=ec.SECP384R1(), - compressed_point=VALUES['ecc_compressed_point'] - ) - numbers = VALUES['ecc_private_key_prime'].public_key().public_numbers() - assert x == numbers.x - assert y == numbers.y - - @patch('aws_encryption_sdk.internal.crypto.pow') - def test_ecc_decode_compressed_point_prime_a(self, mock_pow): - """Validate that the _ecc_decode_compressed_point function - works as expected for prime field curves when beta % 2 == yp. - """ - mock_pow.return_value = 1 - _, y = aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point( - curve=ec.SECP384R1(), - compressed_point=VALUES['ecc_compressed_point'] - ) - assert y == 1 - - @patch('aws_encryption_sdk.internal.crypto.pow') - def test_ecc_decode_compressed_point_prime_b(self, mock_pow): - """Validate that the _ecc_decode_compressed_point function - works as expected for prime field curves when beta % 2 != yp. - """ - mock_pow.return_value = 0 - _, y = aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point( - curve=ec.SECP384R1(), - compressed_point=VALUES['ecc_compressed_point'] - ) - assert y == aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp384r1'].p - - def test_ecc_decode_compressed_point_prime_unsupported(self): - """Validate that the _ecc_decode_compressed_point function - works as expected for unsupported prime field curves. - """ - with six.assertRaisesRegex(self, NotSupportedError, 'Curve secp192r1 is not supported at this time'): - aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point( - curve=ec.SECP192R1(), - compressed_point='\x02skdgaiuhgijudflkjsdgfkjsdflgjhsd' - ) - - @patch('aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS') - def test_ecc_decode_compressed_point_prime_complex(self, mock_curve_parameters): - """Validate that the _ecc_decode_compressed_point function - works as expected for prime field curves with p % 4 != 3. - """ - mock_curve_parameters.__getitem__.return_value = aws_encryption_sdk.internal.crypto._ECCCurveParameters( - p=5, - a=5, - b=5, - order=5 - ) - mock_curve = MagicMock() - mock_curve.name = 'secp_mock_curve' - with six.assertRaisesRegex(self, NotSupportedError, 'S not 1 :: Curve not supported at this time'): - aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point( - curve=mock_curve, - compressed_point=VALUES['ecc_compressed_point'] - ) - - def test_ecc_decode_compressed_point_characteristic_two(self): - """Validate that the _ecc_decode_compressed_point function - works as expected for characteristic 2 field curves. - """ - with six.assertRaisesRegex(self, NotSupportedError, 'Non-prime curves are not supported at this time'): - aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point( - curve=ec.SECT409K1(), - compressed_point='\x02skdgaiuhgijudflkjsdgfkjsdflgjhsd' - ) - - @patch('aws_encryption_sdk.internal.crypto._ecc_decode_compressed_point') - def test_ecc_public_numbers_from_compressed_point(self, mock_decode): - """Validate that the _ecc_public_numbers_from_compressed_point - function works as expected. - """ - mock_decode.return_value = sentinel.x, sentinel.y - self.mock_cryptography_ec.EllipticCurvePublicNumbers.return_value = sentinel.public_numbers_instance - test = aws_encryption_sdk.internal.crypto._ecc_public_numbers_from_compressed_point( - curve=sentinel.curve_instance, - compressed_point=sentinel.compressed_point - ) - mock_decode.assert_called_once_with(sentinel.curve_instance, sentinel.compressed_point) - self.mock_cryptography_ec.EllipticCurvePublicNumbers.assert_called_once_with( - x=sentinel.x, - y=sentinel.y, - curve=sentinel.curve_instance - ) - assert test == sentinel.public_numbers_instance - - @patch('aws_encryption_sdk.internal.crypto.Signer._set_signature_type') - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_init(self, mock_hasher, mock_signature_type): - """Validate that the Signer __init__ function works - as expected when a key is provided. - """ - signer = aws_encryption_sdk.internal.crypto.Signer( - algorithm=self.mock_algorithm, - key=sentinel.existing_signer_key - ) - mock_hasher.assert_called_once_with() - mock_signature_type.assert_called_once_with() - assert not self.mock_cryptography_ec.generate_private_key.called - assert signer.algorithm is self.mock_algorithm - assert signer._signature_type is mock_signature_type.return_value - assert signer.key is sentinel.existing_signer_key - assert signer._hasher is mock_hasher.return_value - - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_set_signature_type_elliptic_curve(self, mock_hasher): - with patch('aws_encryption_sdk.internal.crypto.Signer._set_signature_type'): - signer = aws_encryption_sdk.internal.crypto.Signer(self.mock_algorithm, key=self.mock_signer_private_key) - test_signature_type = signer._set_signature_type() - - self.mock_crypto_verify_interface.assert_called_once_with( - self.mock_cryptography_ec.EllipticCurve, - self.mock_algorithm.signing_algorithm_info - ) - assert test_signature_type is self.mock_cryptography_ec.EllipticCurve - - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_from_key_bytes(self, mock_hasher): - signer = aws_encryption_sdk.internal.crypto.Signer.from_key_bytes( - algorithm=self.mock_algorithm, - key_bytes=sentinel.key_bytes - ) - - self.mock_crypto_serialization.load_der_private_key.assert_called_once_with( - data=sentinel.key_bytes, - password=None, - backend=self.mock_cryptography_backend.return_value - ) - assert isinstance(signer, aws_encryption_sdk.internal.crypto.Signer) - assert signer.algorithm is self.mock_algorithm - assert signer.key is self.mock_crypto_serialization.load_der_private_key.return_value - - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_key_bytes(self, mock_hasher): - signer = aws_encryption_sdk.internal.crypto.Signer(self.mock_algorithm, key=self.mock_signer_private_key) - - test = signer.key_bytes() - - assert test is self.mock_signer_private_key.private_bytes.return_value - self.mock_signer_private_key.private_bytes.assert_called_once_with( - encoding=self.mock_crypto_serialization.Encoding.DER, - format=self.mock_crypto_serialization.PrivateFormat.PKCS8, - encryption_algorithm=self.mock_crypto_serialization.NoEncryption.return_value - ) - - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_set_signature_type_unknown(self, mock_hasher): - self.mock_crypto_verify_interface.side_effect = InterfaceNotImplemented - with patch('aws_encryption_sdk.internal.crypto.Signer._set_signature_type'): - signer = aws_encryption_sdk.internal.crypto.Signer(self.mock_algorithm, key=self.mock_signer_private_key) - - with six.assertRaisesRegex(self, NotSupportedError, 'Unsupported signing algorithm info'): - signer._set_signature_type() - - @patch('aws_encryption_sdk.internal.crypto.hashes.Hash') - @patch('aws_encryption_sdk.internal.crypto.default_backend') - def test_signer_build_hasher(self, mock_default_backend, mock_hash): - with patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher'): - signer = aws_encryption_sdk.internal.crypto.Signer(self.mock_algorithm, key=self.mock_signer_private_key) - test_hasher = signer._build_hasher() - - self.mock_algorithm.signing_hash_type.assert_called_once_with() - mock_default_backend.assert_called_once_with() - mock_hash.assert_called_once_with( - self.mock_algorithm.signing_hash_type.return_value, - backend=mock_default_backend.return_value - ) - assert test_hasher is mock_hash.return_value - - @patch('aws_encryption_sdk.internal.crypto.base64') - @patch('aws_encryption_sdk.internal.crypto._ecc_encode_compressed_point') - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_encoded_public_key(self, mock_hasher, mock_encoder, mock_base64): - """Validate that the Signer.encoded_public_key function works as expected.""" - mock_encoder.return_value = sentinel.compressed_point - mock_base64.b64encode.return_value = sentinel.encoded_point - signer = aws_encryption_sdk.internal.crypto.Signer(self.mock_algorithm, key=self.mock_signer_private_key) - test_key = signer.encoded_public_key() - mock_encoder.assert_called_once_with(self.mock_signer_private_key) - mock_base64.b64encode.assert_called_once_with(sentinel.compressed_point) - assert test_key == sentinel.encoded_point - - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_update(self, mock_hasher): - """Validate that the Signer.update function works as expected.""" - mock_hasher.return_value = self.mock_hasher - signer = aws_encryption_sdk.internal.crypto.Signer(self.mock_algorithm, key=self.mock_signer_private_key) - signer.update(sentinel.data) - self.mock_hasher.update.assert_called_once_with(sentinel.data) - - @patch('aws_encryption_sdk.internal.crypto._ecc_static_length_signature') - @patch('aws_encryption_sdk.internal.crypto.Signer._build_hasher') - def test_signer_finalize(self, mock_hasher, mock_ecc_signature): - signer = aws_encryption_sdk.internal.crypto.Signer(self.mock_algorithm, key=self.mock_signer_private_key) - test_signature = signer.finalize() - - mock_hasher.return_value.finalize.assert_called_once_with() - mock_ecc_signature.assert_called_once_with( - key=self.mock_signer_private_key, - algorithm=self.mock_algorithm, - digest=mock_hasher.return_value.finalize.return_value - ) - assert test_signature is mock_ecc_signature.return_value - - @patch('aws_encryption_sdk.internal.crypto.Verifier._verifier') - def test_verifier_init(self, mock_verifier): - """Validate that the Verifier __init__ function works - as expected when a signature is provided. - """ - mock_verifier.return_value = sentinel.verifier - verifier = aws_encryption_sdk.internal.crypto.Verifier( - algorithm=self.mock_algorithm, - public_key=sentinel.public_key, - signature=sentinel.signature - ) - mock_verifier.assert_called_once_with(sentinel.signature) - assert verifier.algorithm == self.mock_algorithm - assert verifier.key == sentinel.public_key - assert verifier.verifier == sentinel.verifier - - @patch('aws_encryption_sdk.internal.crypto.Verifier._verifier') - def test_verifier_init_no_signature(self, mock_verifier): - """Validate that the Verifier __init__ function works - as expected when no signature is provided. - """ - mock_verifier.return_value = sentinel.verifier - aws_encryption_sdk.internal.crypto.Verifier( - algorithm=self.mock_algorithm, - public_key=sentinel.public_key - ) - mock_verifier.assert_called_once_with(b'') - - @patch('aws_encryption_sdk.internal.crypto.Verifier._verifier') - @patch('aws_encryption_sdk.internal.crypto.base64') - @patch('aws_encryption_sdk.internal.crypto._ecc_public_numbers_from_compressed_point') - def test_verifier_from_encoded_point(self, mock_decode, mock_base64, mock_verifier): - """Validate that the Verifier.from_encoded_point function works as expected.""" - mock_point_instance = MagicMock() - mock_point_instance.public_key.return_value = sentinel.public_key - mock_decode.return_value = mock_point_instance - mock_base64.b64decode.return_value = sentinel.compressed_point - mock_verifier.return_value = sentinel.verifier - verifier = aws_encryption_sdk.internal.crypto.Verifier.from_encoded_point( - algorithm=self.mock_algorithm, - encoded_point=sentinel.encoded_point, - signature=sentinel.signature - ) - mock_base64.b64decode.assert_called_once_with(sentinel.encoded_point) - self.mock_algorithm.signing_algorithm_info.assert_called_once_with() - mock_decode.assert_called_once_with( - curve=sentinel.curve_instance, - compressed_point=sentinel.compressed_point - ) - mock_point_instance.public_key.assert_called_once_with(sentinel.crypto_backend) - assert isinstance(verifier, aws_encryption_sdk.internal.crypto.Verifier) - - def test_verifier_verifier(self): - """Validate that the Verifier._verifier function works as expected.""" - verifier = aws_encryption_sdk.internal.crypto.Verifier( - algorithm=self.mock_algorithm, - public_key=self.mock_verifier_public_key, - signature=sentinel.signature - ) - self.mock_algorithm.signing_hash_type.assert_called_once_with() - self.mock_cryptography_ec.ECDSA.assert_called_once_with(self.mock_algorithm.signing_hash_type.return_value) - self.mock_verifier_public_key.verifier.assert_called_once_with( - signature=sentinel.signature, - signature_algorithm=sentinel.ecdsa_instance - ) - assert verifier.verifier == self.mock_verifier_instance - - def test_verifier_set_signature(self): - """Validate that the Verifier.set_signature function works as expected.""" - self.mock_verifier_instance._signature = b'' - verifier = aws_encryption_sdk.internal.crypto.Verifier( - algorithm=self.mock_algorithm, - public_key=self.mock_verifier_public_key - ) - assert verifier.verifier._signature == b'' - verifier.set_signature(sentinel.signature) - assert verifier.verifier._signature == sentinel.signature - - def test_verifier_update(self): - """Validate that the Verifier.update function works as expected.""" - verifier = aws_encryption_sdk.internal.crypto.Verifier( - algorithm=self.mock_algorithm, - public_key=self.mock_verifier_public_key - ) - verifier.update(sentinel.data) - self.mock_verifier_instance.update.assert_called_once_with(sentinel.data) - - def test_verifier_verify(self): - """Validate that the Verifier.verify function works as expected.""" - verifier = aws_encryption_sdk.internal.crypto.Verifier( - algorithm=self.mock_algorithm, - public_key=self.mock_verifier_public_key - ) - verifier.verify() - self.mock_verifier_instance.verify.assert_called_once_with() - - def test_ecc_curve_parameters_secp256r1(self): - """Verify values from http://www.secg.org/sec2-v2.pdf""" - p = pow(2, 224) * (pow(2, 32) - 1) + pow(2, 192) + pow(2, 96) - 1 - a = int(( - 'FFFFFFFF' '00000001' '00000000' '00000000' '00000000' 'FFFFFFFF' 'FFFFFFFF' - 'FFFFFFFC' - ), 16) - b = int(( - '5AC635D8' 'AA3A93E7' 'B3EBBD55' '769886BC' '651D06B0' 'CC53B0F6' '3BCE3C3E' - '27D2604B' - ), 16) - order = int(( - 'FFFFFFFF' '00000000' 'FFFFFFFF' 'FFFFFFFF' 'BCE6FAAD' 'A7179E84' 'F3B9CAC2' - 'FC632551' - ), 16) - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp256r1'].p == p - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp256r1'].a == a - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp256r1'].b == b - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp256r1'].order == order - - def test_ecc_curve_parameters_secp384r1(self): - """Verify values from http://www.secg.org/sec2-v2.pdf""" - p = pow(2, 384) - pow(2, 128) - pow(2, 96) + pow(2, 32) - 1 - a = int(( - 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' - 'FFFFFFFE' 'FFFFFFFF' '00000000' '00000000' 'FFFFFFFC' - ), 16) - b = int(( - 'B3312FA7' 'E23EE7E4' '988E056B' 'E3F82D19' '181D9C6E' 'FE814112' '0314088F' - '5013875A' 'C656398D' '8A2ED19D' '2A85C8ED' 'D3EC2AEF' - ), 16) - order = int(( - 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'C7634D81' - 'F4372DDF' '581A0DB2' '48B0A77A' 'ECEC196A' 'CCC52973' - ), 16) - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp384r1'].p == p - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp384r1'].a == a - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp384r1'].b == b - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp384r1'].order == order - - def test_ecc_curve_parameters_secp521r1(self): - """Verify values from http://www.secg.org/sec2-v2.pdf""" - p = pow(2, 521) - 1 - a = int(( - '01FF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' - 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' - 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFC' - ), 16) - b = int(( - '0051' '953EB961' '8E1C9A1F' '929A21A0' 'B68540EE' 'A2DA725B' '99B315F3' - 'B8B48991' '8EF109E1' '56193951' 'EC7E937B' '1652C0BD' '3BB1BF07' '3573DF88' - '3D2C34F1' 'EF451FD4' '6B503F00' - ), 16) - order = int(( - '01FF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' - 'FFFFFFFF' 'FFFFFFFA' '51868783' 'BF2F966B' '7FCC0148' 'F709A5D0' '3BB5C9B8' - '899C47AE' 'BB6FB71E' '91386409' - ), 16) - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp521r1'].p == p - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp521r1'].a == a - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp521r1'].b == b - assert aws_encryption_sdk.internal.crypto._ECC_CURVE_PARAMETERS['secp521r1'].order == order - - def test_ecc_curve_not_in_cryptography(self): - """If this test fails, then this pull or similar has gone through - and this library should be updated to use the ECC curve - parameters from cryptography. - https://github.com/pyca/cryptography/pull/2499 - """ - assert not hasattr(ec.SECP384R1, 'a') - - def test_wrapping_key_init_private(self): - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.PRIVATE - ) - assert test_wrapping_key.wrapping_algorithm is self.mock_wrapping_algorithm - assert test_wrapping_key.wrapping_key_type is EncryptionKeyType.PRIVATE - self.mock_crypto_serialization.load_pem_private_key.assert_called_once_with( - data=self.mock_wrapping_key, - password=None, - backend=sentinel.crypto_backend - ) - assert not self.mock_crypto_serialization.load_pem_public_key.called - assert test_wrapping_key._wrapping_key is self.mock_wrapping_rsa_private_key - - def test_wrapping_key_init_private_with_password(self): - aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.PRIVATE, - password=sentinel.password - ) - self.mock_crypto_serialization.load_pem_private_key.assert_called_once_with( - data=self.mock_wrapping_key, - password=sentinel.password, - backend=sentinel.crypto_backend - ) - - def test_wrapping_key_init_public(self): - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.PUBLIC - ) - self.mock_crypto_serialization.load_pem_public_key.assert_called_once_with( - data=self.mock_wrapping_key, - backend=sentinel.crypto_backend - ) - assert not self.mock_crypto_serialization.load_pem_private_key.called - assert test_wrapping_key._wrapping_key is self.mock_wrapping_rsa_public_key - - @patch('aws_encryption_sdk.internal.crypto.derive_data_encryption_key') - def test_wrapping_key_init_symmetric(self, mock_derive_datakey): - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.SYMMETRIC - ) - assert not self.mock_crypto_serialization.load_pem_private_key.called - assert not self.mock_crypto_serialization.load_pem_public_key.called - assert test_wrapping_key._wrapping_key is self.mock_wrapping_key - mock_derive_datakey.assert_called_once_with( - source_key=self.mock_wrapping_key, - algorithm=self.mock_wrapping_algorithm.algorithm, - message_id=None - ) - assert test_wrapping_key._derived_wrapping_key is mock_derive_datakey.return_value - - def test_wrapping_key_init_invalid_key_type(self): - with six.assertRaisesRegex(self, InvalidDataKeyError, 'Invalid wrapping_key_type: *'): - aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=sentinel.key_type - ) - - @patch('aws_encryption_sdk.internal.crypto.os.urandom') - @patch('aws_encryption_sdk.internal.crypto.derive_data_encryption_key') - @patch('aws_encryption_sdk.internal.crypto.serialize_encryption_context', return_value=sentinel.serialized_ec) - @patch('aws_encryption_sdk.internal.crypto.encrypt', return_value=sentinel.encrypted_data) - def test_wrapping_key_encrypt_symmetric(self, mock_encrypt, mock_serialize_ec, mock_derive_datakey, mock_urandom): - self.mock_wrapping_algorithm.algorithm = MagicMock(iv_len=sentinel.iv_len) - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.SYMMETRIC - ) - test = test_wrapping_key.encrypt( - plaintext_data_key=sentinel.plaintext_data_key, - encryption_context=sentinel.encryption_context - ) - assert not self.mock_wrapping_rsa_private_key.public_key.called - assert not self.mock_wrapping_rsa_public_key.encrypt.called - mock_serialize_ec.assert_called_once_with( - encryption_context=sentinel.encryption_context - ) - mock_urandom.assert_called_once_with(sentinel.iv_len) - mock_encrypt.assert_called_once_with( - algorithm=self.mock_wrapping_algorithm.algorithm, - key=mock_derive_datakey.return_value, - plaintext=sentinel.plaintext_data_key, - associated_data=sentinel.serialized_ec, - iv=mock_urandom.return_value - ) - assert test is sentinel.encrypted_data - - @patch('aws_encryption_sdk.internal.crypto.serialize_encryption_context', return_value=sentinel.serialized_ec) - @patch('aws_encryption_sdk.internal.crypto.encrypt') - def test_wrapping_key_encrypt_private(self, mock_encrypt, mock_serialize_ec): - self.mock_wrapping_rsa_public_key.encrypt.return_value = VALUES['ciphertext'] - self.mock_wrapping_algorithm.encryption_type = EncryptionType.ASYMMETRIC - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.PRIVATE - ) - test = test_wrapping_key.encrypt( - plaintext_data_key=sentinel.plaintext_data_key, - encryption_context=sentinel.encryption_context - ) - self.mock_wrapping_rsa_private_key.public_key.assert_called_once_with() - self.mock_wrapping_rsa_public_key.encrypt.assert_called_once_with( - plaintext=sentinel.plaintext_data_key, - padding=sentinel.padding - ) - assert not mock_serialize_ec.called - assert not mock_encrypt.called - assert test == EncryptedData( - iv=None, - ciphertext=VALUES['ciphertext'], - tag=None - ) - - @patch('aws_encryption_sdk.internal.crypto.serialize_encryption_context', return_value=sentinel.serialized_ec) - @patch('aws_encryption_sdk.internal.crypto.encrypt') - def test_wrapping_key_encrypt_public(self, mock_encrypt, mock_serialize_ec): - self.mock_wrapping_rsa_public_key.encrypt.return_value = VALUES['ciphertext'] - self.mock_wrapping_algorithm.encryption_type = EncryptionType.ASYMMETRIC - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.PUBLIC - ) - test = test_wrapping_key.encrypt( - plaintext_data_key=sentinel.plaintext_data_key, - encryption_context=sentinel.encryption_context - ) - assert not self.mock_wrapping_rsa_private_key.public_key.called - self.mock_wrapping_rsa_public_key.encrypt.assert_called_once_with( - plaintext=sentinel.plaintext_data_key, - padding=sentinel.padding - ) - assert not mock_serialize_ec.called - assert not mock_encrypt.called - assert test == EncryptedData( - iv=None, - ciphertext=VALUES['ciphertext'], - tag=None - ) - - @patch('aws_encryption_sdk.internal.crypto.derive_data_encryption_key') - @patch('aws_encryption_sdk.internal.crypto.serialize_encryption_context', return_value=sentinel.serialized_ec) - @patch('aws_encryption_sdk.internal.crypto.decrypt', return_value=sentinel.plaintext_data) - def test_wrapping_key_decrypt_symmetric(self, mock_decrypt, mock_serialize_ec, mock_derive_datakey): - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.SYMMETRIC - ) - test = test_wrapping_key.decrypt( - encrypted_wrapped_data_key=VALUES['ciphertext'], - encryption_context=sentinel.encryption_context - ) - assert not self.mock_wrapping_rsa_private_key.decrypt.called - mock_serialize_ec.assert_called_once_with( - encryption_context=sentinel.encryption_context - ) - mock_decrypt.assert_called_once_with( - algorithm=sentinel.algorithm, - key=mock_derive_datakey.return_value, - encrypted_data=VALUES['ciphertext'], - associated_data=sentinel.serialized_ec - ) - assert test is sentinel.plaintext_data - - @patch('aws_encryption_sdk.internal.crypto.serialize_encryption_context') - @patch('aws_encryption_sdk.internal.crypto.decrypt') - def test_wrapping_key_decrypt_private(self, mock_decrypt, mock_serialize_ec): - self.mock_wrapping_rsa_private_key.decrypt.return_value = sentinel.plaintext_data - self.mock_wrapping_algorithm.encryption_type = EncryptionType.ASYMMETRIC - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.PRIVATE - ) - test = test_wrapping_key.decrypt( - encrypted_wrapped_data_key=self.mock_encrypted_data, - encryption_context=sentinel.encryption_context - ) - self.mock_wrapping_rsa_private_key.decrypt.assert_called_once_with( - ciphertext=VALUES['ciphertext'], - padding=sentinel.padding - ) - assert not mock_serialize_ec.called - assert not mock_decrypt.called - assert test is sentinel.plaintext_data - - def test_wrapping_key_decrypt_public(self): - self.mock_wrapping_algorithm.encryption_type = EncryptionType.ASYMMETRIC - test_wrapping_key = aws_encryption_sdk.internal.crypto.WrappingKey( - wrapping_algorithm=self.mock_wrapping_algorithm, - wrapping_key=self.mock_wrapping_key, - wrapping_key_type=EncryptionKeyType.PUBLIC - ) - with six.assertRaisesRegex(self, IncorrectMasterKeyError, 'Public key cannot decrypt'): - test_wrapping_key.decrypt( - encrypted_wrapped_data_key=self.mock_encrypted_data, - encryption_context=sentinel.encryption_context - ) - - def test_generate_ecc_signing_key_supported(self): - self.mock_cryptography_ec.generate_private_key.return_value = sentinel.raw_signing_key - mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info) - mock_algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) - - test_signing_key = aws_encryption_sdk.internal.crypto.generate_ecc_signing_key(algorithm=mock_algorithm) - - self.mock_crypto_verify_interface.assert_called_once_with( - self.mock_cryptography_ec.EllipticCurve, - mock_algorithm_info - ) - self.mock_cryptography_ec.generate_private_key.assert_called_once_with( - curve=sentinel.algorithm_info, - backend=self.mock_cryptography_backend.return_value - ) - assert test_signing_key is sentinel.raw_signing_key - - def test_generate_ecc_signing_key_unsupported(self): - self.mock_crypto_verify_interface.side_effect = InterfaceNotImplemented - mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info) - mock_algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) - - with six.assertRaisesRegex(self, NotSupportedError, 'Unsupported signing algorithm info'): - aws_encryption_sdk.internal.crypto.generate_ecc_signing_key(algorithm=mock_algorithm) - - assert not self.mock_cryptography_ec.generate_private_key.called - assert not self.mock_cryptography_backend.called diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py new file mode 100644 index 000000000..486e73c6a --- /dev/null +++ b/test/unit/test_crypto_authentication_signer.py @@ -0,0 +1,155 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto.authentication.Signer``.""" +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +import aws_encryption_sdk.internal.crypto.authentication +from aws_encryption_sdk.internal.crypto.authentication import Signer +from aws_encryption_sdk.internal.defaults import ALGORITHM + +from .test_crypto import VALUES + + +@pytest.yield_fixture +def patch_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'default_backend') + yield aws_encryption_sdk.internal.crypto.authentication.default_backend + + +@pytest.yield_fixture +def patch_serialization(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'serialization') + yield aws_encryption_sdk.internal.crypto.authentication.serialization + + +@pytest.yield_fixture +def patch_ecc_encode_compressed_point(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, '_ecc_encode_compressed_point') + yield aws_encryption_sdk.internal.crypto.authentication._ecc_encode_compressed_point + + +@pytest.yield_fixture +def patch_ecc_static_length_signature(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, '_ecc_static_length_signature') + yield aws_encryption_sdk.internal.crypto.authentication._ecc_static_length_signature + + +@pytest.yield_fixture +def patch_base64(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'base64') + yield aws_encryption_sdk.internal.crypto.authentication.base64 + + +@pytest.yield_fixture +def patch_build_hasher(mocker): + mocker.patch.object(Signer, '_build_hasher') + yield Signer._build_hasher + + +def test_f_signer_from_key_bytes(): + check = Signer( + algorithm=ALGORITHM, + key=VALUES['ecc_private_key_prime'] + ) + test = Signer.from_key_bytes( + algorithm=ALGORITHM, + key_bytes=VALUES['ecc_private_key_prime_private_bytes'] + ) + assert check.key.private_numbers().private_value == test.key.private_numbers().private_value + + +def test_f_signer_key_bytes(): + test = Signer( + algorithm=ALGORITHM, + key=VALUES['ecc_private_key_prime'] + ) + assert test.key_bytes() == VALUES['ecc_private_key_prime_private_bytes'] + + +def test_signer_from_key_bytes(patch_default_backend, patch_serialization, patch_build_hasher): + _algorithm = MagicMock() + signer = Signer.from_key_bytes( + algorithm=_algorithm, + key_bytes=sentinel.key_bytes + ) + + patch_serialization.load_der_private_key.assert_called_once_with( + data=sentinel.key_bytes, + password=None, + backend=patch_default_backend.return_value + ) + assert isinstance(signer, Signer) + assert signer.algorithm is _algorithm + assert signer.key is patch_serialization.load_der_private_key.return_value + + +def test_signer_key_bytes(patch_default_backend, patch_serialization, patch_build_hasher): + private_key = MagicMock() + signer = Signer(MagicMock(), key=private_key) + + test = signer.key_bytes() + + assert test is private_key.private_bytes.return_value + private_key.private_bytes.assert_called_once_with( + encoding=patch_serialization.Encoding.DER, + format=patch_serialization.PrivateFormat.PKCS8, + encryption_algorithm=patch_serialization.NoEncryption.return_value + ) + + +def test_signer_encoded_public_key( + patch_default_backend, + patch_serialization, + patch_build_hasher, + patch_ecc_encode_compressed_point, + patch_base64 +): + patch_ecc_encode_compressed_point.return_value = sentinel.compressed_point + patch_base64.b64encode.return_value = sentinel.encoded_point + private_key = MagicMock() + + signer = Signer(MagicMock(), key=private_key) + test_key = signer.encoded_public_key() + + patch_ecc_encode_compressed_point.assert_called_once_with(private_key) + patch_base64.b64encode.assert_called_once_with(sentinel.compressed_point) + assert test_key == sentinel.encoded_point + + +def test_signer_update(patch_default_backend, patch_serialization, patch_build_hasher): + signer = Signer(MagicMock(), key=MagicMock()) + signer.update(sentinel.data) + patch_build_hasher.return_value.update.assert_called_once_with(sentinel.data) + + +def test_signer_finalize( + patch_default_backend, + patch_serialization, + patch_build_hasher, + patch_ecc_static_length_signature +): + algorithm = MagicMock() + private_key = MagicMock() + + signer = Signer(algorithm, key=private_key) + test_signature = signer.finalize() + + patch_build_hasher.return_value.finalize.assert_called_once_with() + patch_ecc_static_length_signature.assert_called_once_with( + key=private_key, + algorithm=algorithm, + digest=patch_build_hasher.return_value.finalize.return_value + ) + assert test_signature is patch_ecc_static_length_signature.return_value diff --git a/test/unit/test_crypto_authentication_verifier.py b/test/unit/test_crypto_authentication_verifier.py new file mode 100644 index 000000000..54e4330a2 --- /dev/null +++ b/test/unit/test_crypto_authentication_verifier.py @@ -0,0 +1,146 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto.authentication.Verifier``.""" +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +import aws_encryption_sdk.internal.crypto.authentication +from aws_encryption_sdk.internal.crypto.authentication import Verifier +from aws_encryption_sdk.internal.defaults import ALGORITHM + +from .test_crypto import VALUES + + +@pytest.yield_fixture +def patch_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'default_backend') + yield aws_encryption_sdk.internal.crypto.authentication.default_backend + + +@pytest.yield_fixture +def patch_serialization(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'serialization') + yield aws_encryption_sdk.internal.crypto.authentication.serialization + + +@pytest.yield_fixture +def patch_ecc_public_numbers_from_compressed_point(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, '_ecc_public_numbers_from_compressed_point') + yield aws_encryption_sdk.internal.crypto.authentication._ecc_public_numbers_from_compressed_point + + +@pytest.yield_fixture +def patch_ec(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'ec') + yield aws_encryption_sdk.internal.crypto.authentication.ec + + +@pytest.yield_fixture +def patch_prehashed(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'Prehashed') + yield aws_encryption_sdk.internal.crypto.authentication.Prehashed + + +@pytest.yield_fixture +def patch_base64(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'base64') + yield aws_encryption_sdk.internal.crypto.authentication.base64 + + +@pytest.yield_fixture +def patch_build_hasher(mocker): + mocker.patch.object(Verifier, '_build_hasher') + yield Verifier._build_hasher + + +@pytest.yield_fixture +def patch_set_signature_type(mocker): + mocker.patch.object(Verifier, '_set_signature_type') + yield Verifier._set_signature_type + + +def test_f_verifier_from_key_bytes(): + check = Verifier( + algorithm=ALGORITHM, + key=VALUES['ecc_private_key_prime'].public_key() + ) + test = Verifier.from_key_bytes( + algorithm=ALGORITHM, + key_bytes=VALUES['ecc_private_key_prime_public_bytes'] + ) + assert check.key.public_numbers() == test.key.public_numbers() + + +def test_f_verifier_key_bytes(): + test = Verifier( + algorithm=ALGORITHM, + key=VALUES['ecc_private_key_prime'].public_key() + ) + assert test.key_bytes() == VALUES['ecc_private_key_prime_public_bytes'] + + +def test_verifier_from_encoded_point( + patch_default_backend, + patch_serialization, + patch_ecc_public_numbers_from_compressed_point, + patch_base64, + patch_build_hasher +): + mock_point_instance = MagicMock() + mock_point_instance.public_key.return_value = sentinel.public_key + patch_ecc_public_numbers_from_compressed_point.return_value = mock_point_instance + patch_base64.b64decode.return_value = sentinel.compressed_point + algorithm = MagicMock() + + verifier = Verifier.from_encoded_point(algorithm=algorithm, encoded_point=sentinel.encoded_point) + + patch_base64.b64decode.assert_called_once_with(sentinel.encoded_point) + algorithm.signing_algorithm_info.assert_called_once_with() + patch_ecc_public_numbers_from_compressed_point.assert_called_once_with( + curve=algorithm.signing_algorithm_info.return_value, + compressed_point=sentinel.compressed_point + ) + mock_point_instance.public_key.assert_called_once_with(patch_default_backend.return_value) + assert isinstance(verifier, Verifier) + + +def test_verifier_update(patch_default_backend, patch_serialization, patch_build_hasher): + verifier = Verifier(algorithm=MagicMock(), key=MagicMock()) + verifier.update(sentinel.data) + verifier._hasher.update.assert_called_once_with(sentinel.data) + + +def test_verifier_verify( + patch_default_backend, + patch_serialization, + patch_ec, + patch_prehashed, + patch_build_hasher, + patch_set_signature_type +): + algorithm = MagicMock() + public_key = MagicMock() + + verifier = Verifier(algorithm=algorithm, key=public_key) + verifier.verify(sentinel.signature) + + verifier._hasher.finalize.assert_called_once_with() + algorithm.signing_hash_type.assert_called_once_with() + patch_prehashed.assert_called_once_with(algorithm.signing_hash_type.return_value) + patch_ec.ECDSA.assert_called_once_with(patch_prehashed.return_value) + public_key.verify.assert_called_once_with( + signature=sentinel.signature, + data=verifier._hasher.finalize.return_value, + signature_algorithm=patch_ec.ECDSA.return_value + ) diff --git a/test/unit/test_crypto_data_keys.py b/test/unit/test_crypto_data_keys.py new file mode 100644 index 000000000..f9701be2b --- /dev/null +++ b/test/unit/test_crypto_data_keys.py @@ -0,0 +1,67 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto.data_keys``.""" +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +import aws_encryption_sdk.internal.crypto.data_keys +from aws_encryption_sdk.internal.crypto.data_keys import derive_data_encryption_key + + +@pytest.yield_fixture +def patch_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.data_keys, 'default_backend') + yield aws_encryption_sdk.internal.crypto.data_keys.default_backend + + +@pytest.yield_fixture +def patch_struct(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.data_keys, 'struct') + yield aws_encryption_sdk.internal.crypto.data_keys.struct + + +def test_derive_data_encryption_key_with_hkdf(patch_default_backend, patch_struct): + algorithm = MagicMock() + algorithm.kdf_hash_type.return_value = sentinel.kdf_hash_type + test = derive_data_encryption_key( + source_key=sentinel.source_key, + algorithm=algorithm, + message_id=sentinel.message_id + ) + patch_struct.pack.assert_called_with( + '>H16s', + algorithm.algorithm_id, + sentinel.message_id + ) + algorithm.kdf_type.assert_called_with( + algorithm=sentinel.kdf_hash_type, + length=algorithm.data_key_len, + salt=None, + info=patch_struct.pack.return_value, + backend=patch_default_backend.return_value + ) + algorithm.kdf_type.return_value.derive.assert_called_with( + sentinel.source_key + ) + assert test == algorithm.kdf_type.return_value.derive.return_value + + +def test_derive_data_encryption_key_no_hkdf(patch_default_backend): + algorithm = MagicMock(kdf_type=None) + test = derive_data_encryption_key( + source_key=sentinel.source_key, + algorithm=algorithm, + message_id=sentinel.message_id + ) + assert test == sentinel.source_key diff --git a/test/unit/test_crypto_elliptic_curve.py b/test/unit/test_crypto_elliptic_curve.py new file mode 100644 index 000000000..d52d3c02a --- /dev/null +++ b/test/unit/test_crypto_elliptic_curve.py @@ -0,0 +1,381 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto.elliptic_curve``.""" +import sys + +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.utils import InterfaceNotImplemented +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +from aws_encryption_sdk.exceptions import NotSupportedError +import aws_encryption_sdk.internal.crypto.elliptic_curve +from aws_encryption_sdk.internal.crypto.elliptic_curve import ( + _ECC_CURVE_PARAMETERS, _ecc_decode_compressed_point, _ecc_encode_compressed_point, + _ecc_public_numbers_from_compressed_point, _ecc_static_length_signature, + _ECCCurveParameters, generate_ecc_signing_key +) + +from .test_crypto import VALUES + + +@pytest.yield_fixture +def patch_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, 'default_backend') + yield aws_encryption_sdk.internal.crypto.elliptic_curve.default_backend + + +@pytest.yield_fixture +def patch_ec(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, 'ec') + yield aws_encryption_sdk.internal.crypto.elliptic_curve.ec + + +@pytest.yield_fixture +def patch_pow(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, 'pow') + yield aws_encryption_sdk.internal.crypto.elliptic_curve.pow + + +@pytest.yield_fixture +def patch_encode_dss_signature(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, 'encode_dss_signature') + yield aws_encryption_sdk.internal.crypto.elliptic_curve.encode_dss_signature + + +@pytest.yield_fixture +def patch_decode_dss_signature(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, 'decode_dss_signature') + yield aws_encryption_sdk.internal.crypto.elliptic_curve.decode_dss_signature + + +@pytest.yield_fixture +def patch_ecc_decode_compressed_point(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, '_ecc_decode_compressed_point') + yield aws_encryption_sdk.internal.crypto.elliptic_curve._ecc_decode_compressed_point + + +@pytest.yield_fixture +def patch_verify_interface(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, 'verify_interface') + yield aws_encryption_sdk.internal.crypto.elliptic_curve.verify_interface + + +@pytest.yield_fixture +def patch_ecc_curve_parameters(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, '_ECC_CURVE_PARAMETERS') + yield aws_encryption_sdk.internal.crypto.elliptic_curve._ECC_CURVE_PARAMETERS + + +@pytest.yield_fixture +def patch_prehashed(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.elliptic_curve, 'Prehashed') + yield aws_encryption_sdk.internal.crypto.elliptic_curve.Prehashed + + +def test_ecc_curve_not_in_cryptography(): + """If this test fails, then this pull or similar has gone through + and this library should be updated to use the ECC curve + parameters from cryptography. + https://github.com/pyca/cryptography/pull/2499 + """ + assert not hasattr(ec.SECP384R1, 'a') + + +def test_ecc_curve_parameters_secp256r1(): + """Verify values from http://www.secg.org/sec2-v2.pdf""" + p = pow(2, 224) * (pow(2, 32) - 1) + pow(2, 192) + pow(2, 96) - 1 + a = int(( + 'FFFFFFFF' '00000001' '00000000' '00000000' '00000000' 'FFFFFFFF' 'FFFFFFFF' + 'FFFFFFFC' + ), 16) + b = int(( + '5AC635D8' 'AA3A93E7' 'B3EBBD55' '769886BC' '651D06B0' 'CC53B0F6' '3BCE3C3E' + '27D2604B' + ), 16) + order = int(( + 'FFFFFFFF' '00000000' 'FFFFFFFF' 'FFFFFFFF' 'BCE6FAAD' 'A7179E84' 'F3B9CAC2' + 'FC632551' + ), 16) + assert _ECC_CURVE_PARAMETERS['secp256r1'].p == p + assert _ECC_CURVE_PARAMETERS['secp256r1'].a == a + assert _ECC_CURVE_PARAMETERS['secp256r1'].b == b + assert _ECC_CURVE_PARAMETERS['secp256r1'].order == order + + +def test_ecc_curve_parameters_secp384r1(): + """Verify values from http://www.secg.org/sec2-v2.pdf""" + p = pow(2, 384) - pow(2, 128) - pow(2, 96) + pow(2, 32) - 1 + a = int(( + 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' + 'FFFFFFFE' 'FFFFFFFF' '00000000' '00000000' 'FFFFFFFC' + ), 16) + b = int(( + 'B3312FA7' 'E23EE7E4' '988E056B' 'E3F82D19' '181D9C6E' 'FE814112' '0314088F' + '5013875A' 'C656398D' '8A2ED19D' '2A85C8ED' 'D3EC2AEF' + ), 16) + order = int(( + 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'C7634D81' + 'F4372DDF' '581A0DB2' '48B0A77A' 'ECEC196A' 'CCC52973' + ), 16) + assert _ECC_CURVE_PARAMETERS['secp384r1'].p == p + assert _ECC_CURVE_PARAMETERS['secp384r1'].a == a + assert _ECC_CURVE_PARAMETERS['secp384r1'].b == b + assert _ECC_CURVE_PARAMETERS['secp384r1'].order == order + + +def test_ecc_curve_parameters_secp521r1(): + """Verify values from http://www.secg.org/sec2-v2.pdf""" + p = pow(2, 521) - 1 + a = int(( + '01FF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' + 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' + 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFC' + ), 16) + b = int(( + '0051' '953EB961' '8E1C9A1F' '929A21A0' 'B68540EE' 'A2DA725B' '99B315F3' + 'B8B48991' '8EF109E1' '56193951' 'EC7E937B' '1652C0BD' '3BB1BF07' '3573DF88' + '3D2C34F1' 'EF451FD4' '6B503F00' + ), 16) + order = int(( + '01FF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' 'FFFFFFFF' + 'FFFFFFFF' 'FFFFFFFA' '51868783' 'BF2F966B' '7FCC0148' 'F709A5D0' '3BB5C9B8' + '899C47AE' 'BB6FB71E' '91386409' + ), 16) + assert _ECC_CURVE_PARAMETERS['secp521r1'].p == p + assert _ECC_CURVE_PARAMETERS['secp521r1'].a == a + assert _ECC_CURVE_PARAMETERS['secp521r1'].b == b + assert _ECC_CURVE_PARAMETERS['secp521r1'].order == order + + +def test_ecc_static_length_signature_first_try( + patch_default_backend, + patch_ec, + patch_encode_dss_signature, + patch_decode_dss_signature, + patch_prehashed +): + algorithm = MagicMock(signature_len=55) + private_key = MagicMock() + private_key.sign.return_value = b'a' * 55 + test_signature = _ecc_static_length_signature( + key=private_key, + algorithm=algorithm, + digest=sentinel.digest + ) + patch_prehashed.assert_called_once_with(algorithm.signing_hash_type.return_value) + patch_ec.ECDSA.assert_called_once_with(patch_prehashed.return_value) + private_key.sign.assert_called_once_with( + sentinel.digest, + patch_ec.ECDSA.return_value + ) + assert not patch_encode_dss_signature.called + assert not patch_decode_dss_signature.called + assert test_signature is private_key.sign.return_value + + +def test_ecc_static_length_signature_single_negation( + patch_default_backend, + patch_ec, + patch_encode_dss_signature, + patch_decode_dss_signature, + patch_prehashed +): + algorithm = MagicMock(signature_len=55) + algorithm.signing_algorithm_info.name = 'secp256r1' + private_key = MagicMock() + private_key.sign.return_value = b'a' + patch_decode_dss_signature.return_value = sentinel.r, 100 + patch_encode_dss_signature.return_value = 'a' * 55 + test_signature = _ecc_static_length_signature( + key=private_key, + algorithm=algorithm, + digest=sentinel.digest + ) + assert len(private_key.sign.mock_calls) == 1 + patch_decode_dss_signature.assert_called_once_with(b'a') + patch_encode_dss_signature.assert_called_once_with( + sentinel.r, + _ECC_CURVE_PARAMETERS['secp256r1'].order - 100 + ) + assert test_signature is patch_encode_dss_signature.return_value + + +def test_ecc_static_length_signature_recalculate( + patch_default_backend, + patch_ec, + patch_encode_dss_signature, + patch_decode_dss_signature, + patch_prehashed +): + algorithm = MagicMock(signature_len=55) + algorithm.signing_algorithm_info.name = 'secp256r1' + private_key = MagicMock() + private_key.sign.side_effect = (b'a', b'b' * 55) + patch_decode_dss_signature.return_value = sentinel.r, 100 + patch_encode_dss_signature.return_value = 'a' * 100 + test_signature = _ecc_static_length_signature( + key=private_key, + algorithm=algorithm, + digest=sentinel.digest + ) + assert len(private_key.sign.mock_calls) == 2 + assert len(patch_decode_dss_signature.mock_calls) == 1 + assert len(patch_encode_dss_signature.mock_calls) == 1 + assert test_signature == b'b' * 55 + + +def test_ecc_encode_compressed_point_prime(): + compressed_point = _ecc_encode_compressed_point( + private_key=VALUES['ecc_private_key_prime'] + ) + assert compressed_point == VALUES['ecc_compressed_point'] + + +def test_ecc_encode_compressed_point_characteristic_two(): + with pytest.raises(NotSupportedError) as excinfo: + _ecc_encode_compressed_point(VALUES['ecc_private_key_char2']) + + excinfo.match(r'Non-prime curves are not supported at this time') + + +def test_ecc_decode_compressed_point_infinity(): + with pytest.raises(NotSupportedError) as excinfo: + _ecc_decode_compressed_point( + curve=ec.SECP384R1(), + compressed_point=b'' + ) + + excinfo.match(r'Points at infinity are not allowed') + + +def test_ecc_decode_compressed_point_prime(): + x, y = _ecc_decode_compressed_point( + curve=ec.SECP384R1(), + compressed_point=VALUES['ecc_compressed_point'] + ) + numbers = VALUES['ecc_private_key_prime'].public_key().public_numbers() + assert x == numbers.x + assert y == numbers.y + + +@pytest.mark.skipif( + sys.version_info.major == 3 and sys.version_info.minor == 4, + reason='Patching builtin "pow" fails in Python3.4' +) +def test_ecc_decode_compressed_point_prime_characteristic_two(patch_pow): + patch_pow.return_value = 1 + _, y = _ecc_decode_compressed_point( + curve=ec.SECP384R1(), + compressed_point=VALUES['ecc_compressed_point'] + ) + assert y == 1 + + +@pytest.mark.skipif( + sys.version_info.major == 3 and sys.version_info.minor == 4, + reason='Patching builtin "pow" fails in Python3.4' +) +def test_ecc_decode_compressed_point_prime_not_characteristic_two(patch_pow): + patch_pow.return_value = 0 + _, y = _ecc_decode_compressed_point( + curve=ec.SECP384R1(), + compressed_point=VALUES['ecc_compressed_point'] + ) + assert y == _ECC_CURVE_PARAMETERS['secp384r1'].p + + +def test_ecc_decode_compressed_point_prime_unsupported(): + with pytest.raises(NotSupportedError) as excinfo: + _ecc_decode_compressed_point( + curve=ec.SECP192R1(), + compressed_point='\x02skdgaiuhgijudflkjsdgfkjsdflgjhsd' + ) + + excinfo.match(r'Curve secp192r1 is not supported at this time') + + +def test_ecc_decode_compressed_point_prime_complex(patch_ecc_curve_parameters): + patch_ecc_curve_parameters.__getitem__.return_value = _ECCCurveParameters( + p=5, + a=5, + b=5, + order=5 + ) + mock_curve = MagicMock() + mock_curve.name = 'secp_mock_curve' + with pytest.raises(NotSupportedError) as excinfo: + _ecc_decode_compressed_point( + curve=mock_curve, + compressed_point=VALUES['ecc_compressed_point'] + ) + + excinfo.match(r'S not 1 :: Curve not supported at this time') + + +def test_ecc_decode_compressed_point_nonprime_characteristic_two(): + with pytest.raises(NotSupportedError) as excinfo: + _ecc_decode_compressed_point( + curve=ec.SECT409K1(), + compressed_point='\x02skdgaiuhgijudflkjsdgfkjsdflgjhsd' + ) + + excinfo.match(r'Non-prime curves are not supported at this time') + + +def test_ecc_public_numbers_from_compressed_point(patch_ec, patch_ecc_decode_compressed_point): + patch_ecc_decode_compressed_point.return_value = sentinel.x, sentinel.y + patch_ec.EllipticCurvePublicNumbers.return_value = sentinel.public_numbers_instance + test = _ecc_public_numbers_from_compressed_point( + curve=sentinel.curve_instance, + compressed_point=sentinel.compressed_point + ) + patch_ecc_decode_compressed_point.assert_called_once_with(sentinel.curve_instance, sentinel.compressed_point) + patch_ec.EllipticCurvePublicNumbers.assert_called_once_with( + x=sentinel.x, + y=sentinel.y, + curve=sentinel.curve_instance + ) + assert test == sentinel.public_numbers_instance + + +def test_generate_ecc_signing_key_supported(patch_default_backend, patch_ec, patch_verify_interface): + patch_ec.generate_private_key.return_value = sentinel.raw_signing_key + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info) + mock_algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + + test_signing_key = generate_ecc_signing_key(algorithm=mock_algorithm) + + patch_verify_interface.assert_called_once_with( + patch_ec.EllipticCurve, + mock_algorithm_info + ) + patch_ec.generate_private_key.assert_called_once_with( + curve=sentinel.algorithm_info, + backend=patch_default_backend.return_value + ) + assert test_signing_key is sentinel.raw_signing_key + + +def test_generate_ecc_signing_key_unsupported(patch_default_backend, patch_ec, patch_verify_interface): + patch_verify_interface.side_effect = InterfaceNotImplemented + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info) + mock_algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + + with pytest.raises(NotSupportedError) as excinfo: + generate_ecc_signing_key(algorithm=mock_algorithm) + + excinfo.match(r'Unsupported signing algorithm info') + assert not patch_ec.generate_private_key.called + assert not patch_default_backend.called diff --git a/test/unit/test_crypto_encryption_decryptor.py b/test/unit/test_crypto_encryption_decryptor.py new file mode 100644 index 000000000..811b7836f --- /dev/null +++ b/test/unit/test_crypto_encryption_decryptor.py @@ -0,0 +1,118 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto.encryption.Decryptor``.""" +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +import aws_encryption_sdk.internal.crypto.encryption +from aws_encryption_sdk.internal.crypto.encryption import decrypt, Decryptor + + +@pytest.yield_fixture +def patch_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.encryption, 'default_backend') + yield aws_encryption_sdk.internal.crypto.encryption.default_backend + + +@pytest.yield_fixture +def patch_cipher(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.encryption, 'Cipher') + yield aws_encryption_sdk.internal.crypto.encryption.Cipher + + +@pytest.yield_fixture +def patch_decryptor(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.encryption, 'Decryptor') + yield aws_encryption_sdk.internal.crypto.encryption.Decryptor + + +def test_decryptor_init(patch_default_backend, patch_cipher): + mock_algorithm = MagicMock() + tester = Decryptor( + algorithm=mock_algorithm, + key=sentinel.key, + associated_data=sentinel.aad, + iv=sentinel.iv, + tag=sentinel.tag + ) + + assert tester.source_key is sentinel.key + mock_algorithm.encryption_algorithm.assert_called_once_with(sentinel.key) + mock_algorithm.encryption_mode.assert_called_once_with(sentinel.iv, sentinel.tag) + patch_default_backend.assert_called_once_with() + patch_cipher.assert_called_once_with( + mock_algorithm.encryption_algorithm.return_value, + mock_algorithm.encryption_mode.return_value, + backend=patch_default_backend.return_value + ) + patch_cipher.return_value.decryptor.assert_called_once_with() + assert tester._decryptor is patch_cipher.return_value.decryptor.return_value + tester._decryptor.authenticate_additional_data.assert_called_once_with(sentinel.aad) + + +def test_decryptor_update(patch_default_backend, patch_cipher): + tester = Decryptor( + algorithm=MagicMock(), + key=sentinel.key, + associated_data=sentinel.aad, + iv=sentinel.iv, + tag=sentinel.tag + ) + + test = tester.update(sentinel.ciphertext) + + tester._decryptor.update.assert_called_once_with(sentinel.ciphertext) + assert test is tester._decryptor.update.return_value + + +def test_decryptor_finalize(patch_default_backend, patch_cipher): + tester = Decryptor( + algorithm=MagicMock(), + key=sentinel.key, + associated_data=sentinel.aad, + iv=sentinel.iv, + tag=sentinel.tag + ) + + test = tester.finalize() + + tester._decryptor.finalize.assert_called_once_with() + assert test is tester._decryptor.finalize.return_value + + +def test_decrypt(patch_decryptor): + patch_decryptor.return_value.update.return_value = b'some data-' + patch_decryptor.return_value.finalize.return_value = b'some more data' + + test = decrypt( + algorithm=sentinel.algorithm, + key=sentinel.key, + encrypted_data=MagicMock( + iv=sentinel.iv, + tag=sentinel.tag, + ciphertext=sentinel.ciphertext + ), + associated_data=sentinel.aad + ) + + patch_decryptor.assert_called_once_with( + sentinel.algorithm, + sentinel.key, + sentinel.aad, + sentinel.iv, + sentinel.tag + ) + patch_decryptor.return_value.update.assert_called_once_with(sentinel.ciphertext) + patch_decryptor.return_value.finalize.assert_called_once_with() + assert test == b'some data-some more data' diff --git a/test/unit/test_crypto_encryption_encryptor.py b/test/unit/test_crypto_encryption_encryptor.py new file mode 100644 index 000000000..414055a30 --- /dev/null +++ b/test/unit/test_crypto_encryption_encryptor.py @@ -0,0 +1,130 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto.encryption.Encryptor``.""" +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +import aws_encryption_sdk.internal.crypto.encryption +from aws_encryption_sdk.internal.crypto.encryption import encrypt, Encryptor +from aws_encryption_sdk.internal.structures import EncryptedData + + +@pytest.yield_fixture +def patch_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.encryption, 'default_backend') + yield aws_encryption_sdk.internal.crypto.encryption.default_backend + + +@pytest.yield_fixture +def patch_cipher(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.encryption, 'Cipher') + yield aws_encryption_sdk.internal.crypto.encryption.Cipher + + +@pytest.yield_fixture +def patch_encryptor(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.encryption, 'Encryptor') + yield aws_encryption_sdk.internal.crypto.encryption.Encryptor + + +def test_encryptor_init(patch_default_backend, patch_cipher): + mock_algorithm = MagicMock() + tester = Encryptor( + algorithm=mock_algorithm, + key=sentinel.key, + associated_data=sentinel.aad, + iv=sentinel.iv + ) + + assert tester.source_key is sentinel.key + mock_algorithm.encryption_algorithm.assert_called_once_with(sentinel.key) + mock_algorithm.encryption_mode.assert_called_once_with(sentinel.iv) + patch_default_backend.assert_called_once_with() + patch_cipher.assert_called_once_with( + mock_algorithm.encryption_algorithm.return_value, + mock_algorithm.encryption_mode.return_value, + backend=patch_default_backend.return_value + ) + patch_cipher.return_value.encryptor.assert_called_once_with() + assert tester._encryptor is patch_cipher.return_value.encryptor.return_value + tester._encryptor.authenticate_additional_data.assert_called_once_with(sentinel.aad) + + +def test_encryptor_update(patch_default_backend, patch_cipher): + tester = Encryptor( + algorithm=MagicMock(), + key=sentinel.key, + associated_data=sentinel.aad, + iv=sentinel.iv + ) + + test = tester.update(sentinel.plaintext) + + tester._encryptor.update.assert_called_once_with(sentinel.plaintext) + assert test is tester._encryptor.update.return_value + + +def test_encryptor_finalize(patch_default_backend, patch_cipher): + tester = Encryptor( + algorithm=MagicMock(), + key=sentinel.key, + associated_data=sentinel.aad, + iv=sentinel.iv + ) + + test = tester.finalize() + + tester._encryptor.finalize.assert_called_once_with() + assert test is tester._encryptor.finalize.return_value + + +def test_encryptor_tag(patch_default_backend, patch_cipher): + tester = Encryptor( + algorithm=MagicMock(), + key=sentinel.key, + associated_data=sentinel.aad, + iv=sentinel.iv + ) + + test = tester.tag + + assert test is tester._encryptor.tag + + +def test_encrypt(patch_encryptor): + patch_encryptor.return_value.update.return_value = b'some data-' + patch_encryptor.return_value.finalize.return_value = b'some more data' + patch_encryptor.return_value.iv = b'ex iv' + patch_encryptor.return_value.tag = b'ex tag' + test = encrypt( + algorithm=sentinel.algorithm, + key=sentinel.key, + plaintext=sentinel.plaintext, + associated_data=sentinel.aad, + iv=sentinel.iv + ) + + patch_encryptor.assert_called_once_with( + sentinel.algorithm, + sentinel.key, + sentinel.aad, + sentinel.iv + ) + patch_encryptor.return_value.update.assert_called_once_with(sentinel.plaintext) + patch_encryptor.return_value.finalize.assert_called_once_with() + assert test == EncryptedData( + iv=b'ex iv', + ciphertext=b'some data-some more data', + tag=b'ex tag' + ) diff --git a/test/unit/test_crypto_prehashing_authenticator.py b/test/unit/test_crypto_prehashing_authenticator.py new file mode 100644 index 000000000..6dc99aa45 --- /dev/null +++ b/test/unit/test_crypto_prehashing_authenticator.py @@ -0,0 +1,118 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto._PrehashingAuthenticater``.""" +from cryptography.utils import InterfaceNotImplemented +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +from aws_encryption_sdk.exceptions import NotSupportedError +import aws_encryption_sdk.internal.crypto.authentication +from aws_encryption_sdk.internal.crypto.authentication import _PrehashingAuthenticator + + +@pytest.yield_fixture +def patch_set_signature_type(mocker): + mocker.patch.object(_PrehashingAuthenticator, '_set_signature_type') + yield _PrehashingAuthenticator._set_signature_type + + +@pytest.yield_fixture +def patch_build_hasher(mocker): + mocker.patch.object(_PrehashingAuthenticator, '_build_hasher') + yield _PrehashingAuthenticator._build_hasher + + +@pytest.yield_fixture +def patch_cryptography_utils_verify_interface(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'verify_interface') + yield aws_encryption_sdk.internal.crypto.authentication.verify_interface + + +@pytest.yield_fixture +def patch_cryptography_ec(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'ec') + yield aws_encryption_sdk.internal.crypto.authentication.ec + + +@pytest.yield_fixture +def patch_cryptography_hashes(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'hashes') + yield aws_encryption_sdk.internal.crypto.authentication.hashes + + +@pytest.yield_fixture +def patch_cryptography_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, 'default_backend') + yield aws_encryption_sdk.internal.crypto.authentication.default_backend + + +def test_init(patch_set_signature_type, patch_build_hasher): + test = _PrehashingAuthenticator( + algorithm=sentinel.algorithm, + key=sentinel.key + ) + + assert test.algorithm is sentinel.algorithm + patch_set_signature_type.assert_called_once_with() + assert test._signature_type is patch_set_signature_type.return_value + assert test.key is sentinel.key + patch_build_hasher.assert_called_once_with() + assert test._hasher is patch_build_hasher.return_value + + +def test_set_signature_type_elliptic_curve( + patch_build_hasher, + patch_cryptography_utils_verify_interface, + patch_cryptography_ec +): + mock_algorithm = MagicMock() + test = _PrehashingAuthenticator( + algorithm=mock_algorithm, + key=sentinel.key + ) + + patch_cryptography_utils_verify_interface.assert_called_once_with( + patch_cryptography_ec.EllipticCurve, + mock_algorithm.signing_algorithm_info + ) + assert test._signature_type is patch_cryptography_ec.EllipticCurve + + +def test_set_signature_type_unknown( + patch_build_hasher, + patch_cryptography_utils_verify_interface, + patch_cryptography_ec +): + patch_cryptography_utils_verify_interface.side_effect = InterfaceNotImplemented + with pytest.raises(NotSupportedError) as excinfo: + _PrehashingAuthenticator( + algorithm=MagicMock(), + key=sentinel.key + ) + + excinfo.match(r'Unsupported signing algorithm info') + + +def test_build_hasher(patch_set_signature_type, patch_cryptography_hashes, patch_cryptography_default_backend): + mock_algorithm = MagicMock() + test = _PrehashingAuthenticator( + algorithm=mock_algorithm, + key=sentinel.key + ) + + patch_cryptography_hashes.Hash.assert_called_once_with( + mock_algorithm.signing_hash_type.return_value, + backend=patch_cryptography_default_backend.return_value + ) + assert test._hasher is patch_cryptography_hashes.Hash.return_value diff --git a/test/unit/test_crypto_wrapping_keys.py b/test/unit/test_crypto_wrapping_keys.py new file mode 100644 index 000000000..6cbda6458 --- /dev/null +++ b/test/unit/test_crypto_wrapping_keys.py @@ -0,0 +1,319 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.crypto.wrapping_keys``.""" +from mock import MagicMock, sentinel +import pytest +from pytest_mock import mocker # noqa pylint: disable=unused-import + +from aws_encryption_sdk.exceptions import InvalidDataKeyError +from aws_encryption_sdk.identifiers import EncryptionKeyType, EncryptionType +import aws_encryption_sdk.internal.crypto.wrapping_keys +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey +from aws_encryption_sdk.internal.structures import EncryptedData + +from .test_crypto import VALUES + + +@pytest.yield_fixture +def patch_default_backend(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.wrapping_keys, 'default_backend') + yield aws_encryption_sdk.internal.crypto.wrapping_keys.default_backend + + +@pytest.yield_fixture +def patch_serialization(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.wrapping_keys, 'serialization') + yield aws_encryption_sdk.internal.crypto.wrapping_keys.serialization + + +@pytest.yield_fixture +def patch_derive_data_encryption_key(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.wrapping_keys, 'derive_data_encryption_key') + yield aws_encryption_sdk.internal.crypto.wrapping_keys.derive_data_encryption_key + + +@pytest.yield_fixture +def patch_urandom(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.wrapping_keys.os, 'urandom') + yield aws_encryption_sdk.internal.crypto.wrapping_keys.os.urandom + + +@pytest.yield_fixture +def patch_serialize_encryption_context(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.wrapping_keys, 'serialize_encryption_context') + yield aws_encryption_sdk.internal.crypto.wrapping_keys.serialize_encryption_context + + +@pytest.yield_fixture +def patch_encrypt(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.wrapping_keys, 'encrypt') + yield aws_encryption_sdk.internal.crypto.wrapping_keys.encrypt + + +@pytest.yield_fixture +def patch_decrypt(mocker): + mocker.patch.object(aws_encryption_sdk.internal.crypto.wrapping_keys, 'decrypt') + yield aws_encryption_sdk.internal.crypto.wrapping_keys.decrypt + + +def mock_wrapping_rsa_keys(): + mock_wrapping_rsa_private_key = MagicMock() + mock_wrapping_rsa_public_key = MagicMock() + mock_wrapping_rsa_private_key.public_key.return_value = mock_wrapping_rsa_public_key + return mock_wrapping_rsa_private_key, mock_wrapping_rsa_public_key + + +def mock_encrypted_data(): + return EncryptedData( + iv=VALUES['iv'], + ciphertext=VALUES['ciphertext'], + tag=VALUES['tag'] + ) + + +def test_wrapping_key_init_private(patch_default_backend, patch_serialization): + wrapping_algorithm = MagicMock() + wrapping_key = MagicMock() + test_wrapping_key = WrappingKey( + wrapping_algorithm=wrapping_algorithm, + wrapping_key=wrapping_key, + wrapping_key_type=EncryptionKeyType.PRIVATE + ) + assert test_wrapping_key.wrapping_algorithm is wrapping_algorithm + assert test_wrapping_key.wrapping_key_type is EncryptionKeyType.PRIVATE + patch_serialization.load_pem_private_key.assert_called_once_with( + data=wrapping_key, + password=None, + backend=patch_default_backend.return_value + ) + assert not patch_serialization.load_pem_public_key.called + assert test_wrapping_key._wrapping_key is patch_serialization.load_pem_private_key.return_value + + +def test_wrapping_key_init_private_with_password(patch_default_backend, patch_serialization): + wrapping_algorithm = MagicMock() + wrapping_key = MagicMock() + WrappingKey( + wrapping_algorithm=wrapping_algorithm, + wrapping_key=wrapping_key, + wrapping_key_type=EncryptionKeyType.PRIVATE, + password=sentinel.password + ) + patch_serialization.load_pem_private_key.assert_called_once_with( + data=wrapping_key, + password=sentinel.password, + backend=patch_default_backend.return_value + ) + + +def test_wrapping_key_init_public(patch_default_backend, patch_serialization): + wrapping_algorithm = MagicMock() + wrapping_key = MagicMock() + test_wrapping_key = WrappingKey( + wrapping_algorithm=wrapping_algorithm, + wrapping_key=wrapping_key, + wrapping_key_type=EncryptionKeyType.PUBLIC + ) + patch_serialization.load_pem_public_key.assert_called_once_with( + data=wrapping_key, + backend=patch_default_backend.return_value + ) + assert not patch_serialization.load_pem_private_key.called + assert test_wrapping_key._wrapping_key is patch_serialization.load_pem_public_key.return_value + + +def test_wrapping_key_init_symmetric(patch_default_backend, patch_serialization, patch_derive_data_encryption_key): + wrapping_algorithm = MagicMock() + wrapping_key = MagicMock() + test_wrapping_key = WrappingKey( + wrapping_algorithm=wrapping_algorithm, + wrapping_key=wrapping_key, + wrapping_key_type=EncryptionKeyType.SYMMETRIC + ) + assert not patch_serialization.load_pem_private_key.called + assert not patch_serialization.load_pem_public_key.called + assert test_wrapping_key._wrapping_key is wrapping_key + patch_derive_data_encryption_key.assert_called_once_with( + source_key=wrapping_key, + algorithm=wrapping_algorithm.algorithm, + message_id=None + ) + assert test_wrapping_key._derived_wrapping_key is patch_derive_data_encryption_key.return_value + + +def test_wrapping_key_init_invalid_key_type(): + with pytest.raises(InvalidDataKeyError) as excinfo: + WrappingKey( + wrapping_algorithm=MagicMock(), + wrapping_key=MagicMock(), + wrapping_key_type=sentinel.key_type + ) + + excinfo.match(r'Invalid wrapping_key_type: *') + + +def test_wrapping_key_encrypt_symmetric( + patch_default_backend, + patch_serialization, + patch_serialize_encryption_context, + patch_derive_data_encryption_key, + patch_encrypt, + patch_urandom +): + wrapping_algorithm = MagicMock() + wrapping_key = MagicMock() + test_wrapping_key = WrappingKey( + wrapping_algorithm=wrapping_algorithm, + wrapping_key=wrapping_key, + wrapping_key_type=EncryptionKeyType.SYMMETRIC + ) + + test = test_wrapping_key.encrypt( + plaintext_data_key=sentinel.plaintext_data_key, + encryption_context=sentinel.encryption_context + ) + + assert not patch_serialization.load_pem_private_key.called + assert not patch_serialization.load_pem_public_key.called + patch_serialize_encryption_context.assert_called_once_with(encryption_context=sentinel.encryption_context) + patch_urandom.assert_called_once_with(wrapping_algorithm.algorithm.iv_len) + patch_encrypt.assert_called_once_with( + algorithm=wrapping_algorithm.algorithm, + key=patch_derive_data_encryption_key.return_value, + plaintext=sentinel.plaintext_data_key, + associated_data=patch_serialize_encryption_context.return_value, + iv=patch_urandom.return_value + ) + assert test is patch_encrypt.return_value + + +def test_wrapping_key_encrypt_private( + patch_default_backend, + patch_serialization, + patch_serialize_encryption_context, + patch_encrypt +): + private_key, public_key = mock_wrapping_rsa_keys() + patch_serialization.load_pem_private_key.return_value = private_key + public_key.encrypt.return_value = VALUES['ciphertext'] + mock_wrapping_algorithm = MagicMock(encryption_type=EncryptionType.ASYMMETRIC) + test_wrapping_key = WrappingKey( + wrapping_algorithm=mock_wrapping_algorithm, + wrapping_key=sentinel.wrapping_key, + wrapping_key_type=EncryptionKeyType.PRIVATE + ) + test = test_wrapping_key.encrypt( + plaintext_data_key=sentinel.plaintext_data_key, + encryption_context=sentinel.encryption_context + ) + private_key.public_key.assert_called_once_with() + public_key.encrypt.assert_called_once_with( + plaintext=sentinel.plaintext_data_key, + padding=mock_wrapping_algorithm.padding + ) + assert not patch_serialize_encryption_context.called + assert not patch_encrypt.called + assert test == EncryptedData( + iv=None, + ciphertext=VALUES['ciphertext'], + tag=None + ) + + +def test_wrapping_key_encrypt_public( + patch_default_backend, + patch_serialization, + patch_serialize_encryption_context, + patch_encrypt +): + _, public_key = mock_wrapping_rsa_keys() + patch_serialization.load_pem_public_key.return_value = public_key + public_key.encrypt.return_value = VALUES['ciphertext'] + mock_wrapping_algorithm = MagicMock(encryption_type=EncryptionType.ASYMMETRIC) + test_wrapping_key = WrappingKey( + wrapping_algorithm=mock_wrapping_algorithm, + wrapping_key=sentinel.wrapping_key, + wrapping_key_type=EncryptionKeyType.PUBLIC + ) + test = test_wrapping_key.encrypt( + plaintext_data_key=sentinel.plaintext_data_key, + encryption_context=sentinel.encryption_context + ) + public_key.encrypt.assert_called_once_with( + plaintext=sentinel.plaintext_data_key, + padding=mock_wrapping_algorithm.padding + ) + assert not patch_serialize_encryption_context.called + assert not patch_encrypt.called + assert test == EncryptedData( + iv=None, + ciphertext=VALUES['ciphertext'], + tag=None + ) + + +def test_wrapping_key_decrypt_symmetric( + patch_default_backend, + patch_serialization, + patch_serialize_encryption_context, + patch_derive_data_encryption_key, + patch_decrypt +): + mock_wrapping_algorithm = MagicMock() + test_wrapping_key = WrappingKey( + wrapping_algorithm=mock_wrapping_algorithm, + wrapping_key=sentinel.wrapping_key, + wrapping_key_type=EncryptionKeyType.SYMMETRIC + ) + test = test_wrapping_key.decrypt( + encrypted_wrapped_data_key=VALUES['ciphertext'], + encryption_context=sentinel.encryption_context + ) + patch_serialize_encryption_context.assert_called_once_with( + encryption_context=sentinel.encryption_context + ) + patch_decrypt.assert_called_once_with( + algorithm=mock_wrapping_algorithm.algorithm, + key=patch_derive_data_encryption_key.return_value, + encrypted_data=VALUES['ciphertext'], + associated_data=patch_serialize_encryption_context.return_value + ) + assert test is patch_decrypt.return_value + + +def test_wrapping_key_decrypt_private( + patch_default_backend, + patch_serialization, + patch_serialize_encryption_context, + patch_decrypt +): + private_key, _ = mock_wrapping_rsa_keys() + patch_serialization.load_pem_private_key.return_value = private_key + private_key.decrypt.return_value = sentinel.plaintext_data + mock_wrapping_algorithm = MagicMock(encryption_type=EncryptionType.ASYMMETRIC) + test_wrapping_key = WrappingKey( + wrapping_algorithm=mock_wrapping_algorithm, + wrapping_key=sentinel.wrapping_key, + wrapping_key_type=EncryptionKeyType.PRIVATE + ) + test = test_wrapping_key.decrypt( + encrypted_wrapped_data_key=mock_encrypted_data(), + encryption_context=sentinel.encryption_context + ) + private_key.decrypt.assert_called_once_with( + ciphertext=VALUES['ciphertext'], + padding=mock_wrapping_algorithm.padding + ) + assert not patch_serialize_encryption_context.called + assert not patch_decrypt.called + assert test is sentinel.plaintext_data diff --git a/test/unit/test_defaults.py b/test/unit/test_defaults.py index 32a386264..9f734213b 100644 --- a/test/unit/test_defaults.py +++ b/test/unit/test_defaults.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite to verify calculated values in aws_encryption_sdk.internal.defaults""" import unittest diff --git a/test/unit/test_deserialize.py b/test/unit/test_deserialize.py index ee0c3ac01..50fca8aea 100644 --- a/test/unit/test_deserialize.py +++ b/test/unit/test_deserialize.py @@ -1,12 +1,24 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.deserialize""" import io +import unittest from cryptography.exceptions import InvalidTag -import unittest from mock import MagicMock, patch, sentinel import six -from aws_encryption_sdk.exceptions import SerializationError, UnknownIdentityError, NotSupportedError +from aws_encryption_sdk.exceptions import NotSupportedError, SerializationError, UnknownIdentityError from aws_encryption_sdk.identifiers import Algorithm import aws_encryption_sdk.internal.formatting.deserialize from aws_encryption_sdk.internal.structures import EncryptedData @@ -27,36 +39,30 @@ def setUp(self): # Set up BytesIO patch self.mock_bytesio = MagicMock() # Set up crypto patch - self.mock_crypto_patcher = patch( - 'aws_encryption_sdk.internal.formatting.deserialize.aws_encryption_sdk.internal.crypto' + self.mock_decrypt_patcher = patch( + 'aws_encryption_sdk.internal.formatting.deserialize.decrypt' ) - self.mock_crypto = self.mock_crypto_patcher.start() + self.mock_decrypt = self.mock_decrypt_patcher.start() # Set up encryption_context patch self.mock_deserialize_ec_patcher = patch( 'aws_encryption_sdk.internal.formatting.deserialize.deserialize_encryption_context' ) self.mock_deserialize_ec = self.mock_deserialize_ec_patcher.start() self.mock_deserialize_ec.return_value = VALUES['updated_encryption_context'] - # Set up verifier patch - self.mock_verifier_patcher = patch( - 'aws_encryption_sdk.internal.formatting.deserialize.aws_encryption_sdk.internal.crypto.Verifier' - ) - self.mock_verifier_class = self.mock_verifier_patcher.start() + # Set up mock verifier self.mock_verifier = MagicMock() self.mock_verifier.update.return_value = None - self.mock_verifier_class.from_encoded_point.return_value = self.mock_verifier def tearDown(self): - self.mock_crypto_patcher.stop() + self.mock_decrypt_patcher.stop() self.mock_deserialize_ec_patcher.stop() - self.mock_verifier_patcher.stop() def test_validate_header_valid(self): """Validate that the validate_header function behaves as expected for a valid header. """ self.mock_bytesio.read.return_value = VALUES['header'] - self.mock_crypto.decrypt.return_value = sentinel.decrypted + self.mock_decrypt.return_value = sentinel.decrypted aws_encryption_sdk.internal.formatting.deserialize.validate_header( header=VALUES['deserialized_header_block'], header_auth=VALUES['deserialized_header_auth_block'], @@ -65,7 +71,7 @@ def test_validate_header_valid(self): header_end=len(VALUES['header']), data_key=sentinel.encryption_key ) - self.mock_crypto.decrypt.assert_called_once_with( + self.mock_decrypt.assert_called_once_with( algorithm=VALUES['deserialized_header_block'].algorithm, key=sentinel.encryption_key, encrypted_data=VALUES['header_auth_base'], @@ -76,7 +82,7 @@ def test_validate_header_invalid(self): """Validate that the validate_header function behaves as expected for a valid header. """ - self.mock_crypto.decrypt.side_effect = InvalidTag() + self.mock_decrypt.side_effect = InvalidTag() with six.assertRaisesRegex(self, SerializationError, 'Header authorization failed'): aws_encryption_sdk.internal.formatting.deserialize.validate_header( header=VALUES['deserialized_header_block'], @@ -249,8 +255,7 @@ def test_deserialize_footer(self): """ stream = io.BytesIO(VALUES['serialized_footer']) test = aws_encryption_sdk.internal.formatting.deserialize.deserialize_footer(stream, self.mock_verifier) - self.mock_verifier.set_signature.assert_called_once_with(VALUES['signature']) - self.mock_verifier.verify.assert_called_once_with() + self.mock_verifier.verify.assert_called_once_with(VALUES['signature']) assert test == VALUES['deserialized_footer'] def test_deserialize_footer_verifier_no_footer(self): @@ -334,7 +339,9 @@ def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_incomplete_info(se aws_encryption_sdk.internal.formatting.deserialize.deserialize_wrapped_key( wrapping_algorithm=self.mock_wrapping_algorithm, wrapping_key_id=VALUES['wrapped_keys']['raw']['key_info'], - wrapped_encrypted_key=VALUES['wrapped_keys']['structures']['wrapped_encrypted_data_key_symmetric_incomplete_info'] + wrapped_encrypted_key=VALUES['wrapped_keys']['structures'][ + 'wrapped_encrypted_data_key_symmetric_incomplete_info' + ] ) def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_iv_len_mismatch(self): @@ -342,7 +349,9 @@ def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_iv_len_mismatch(se aws_encryption_sdk.internal.formatting.deserialize.deserialize_wrapped_key( wrapping_algorithm=self.mock_wrapping_algorithm, wrapping_key_id=VALUES['wrapped_keys']['raw']['key_info'], - wrapped_encrypted_key=VALUES['wrapped_keys']['structures']['wrapped_encrypted_data_key_symmetric_bad_iv_len'] + wrapped_encrypted_key=VALUES['wrapped_keys']['structures'][ + 'wrapped_encrypted_data_key_symmetric_bad_iv_len' + ] ) def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_incomplete_iv(self): @@ -350,7 +359,9 @@ def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_incomplete_iv(self aws_encryption_sdk.internal.formatting.deserialize.deserialize_wrapped_key( wrapping_algorithm=self.mock_wrapping_algorithm, wrapping_key_id=VALUES['wrapped_keys']['raw']['key_info'], - wrapped_encrypted_key=VALUES['wrapped_keys']['structures']['wrapped_encrypted_data_key_symmetric_incomplete_iv'] + wrapped_encrypted_key=VALUES['wrapped_keys']['structures'][ + 'wrapped_encrypted_data_key_symmetric_incomplete_iv' + ] ) def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_incomplete_tag(self): @@ -358,7 +369,9 @@ def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_incomplete_tag(sel aws_encryption_sdk.internal.formatting.deserialize.deserialize_wrapped_key( wrapping_algorithm=self.mock_wrapping_algorithm, wrapping_key_id=VALUES['wrapped_keys']['raw']['key_info'], - wrapped_encrypted_key=VALUES['wrapped_keys']['structures']['wrapped_encrypted_data_key_symmetric_incomplete_tag'] + wrapped_encrypted_key=VALUES['wrapped_keys']['structures'][ + 'wrapped_encrypted_data_key_symmetric_incomplete_tag' + ] ) def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_incomplete_tag2(self): @@ -366,5 +379,7 @@ def test_deserialize_wrapped_key_symmetric_wrapping_algorithm_incomplete_tag2(se aws_encryption_sdk.internal.formatting.deserialize.deserialize_wrapped_key( wrapping_algorithm=self.mock_wrapping_algorithm, wrapping_key_id=VALUES['wrapped_keys']['raw']['key_info'], - wrapped_encrypted_key=VALUES['wrapped_keys']['structures']['wrapped_encrypted_data_key_symmetric_incomplete_tag2'] + wrapped_encrypted_key=VALUES['wrapped_keys']['structures'][ + 'wrapped_encrypted_data_key_symmetric_incomplete_tag2' + ] ) diff --git a/test/unit/test_encryption_context.py b/test/unit/test_encryption_context.py index f88ab61eb..c1b2b1892 100644 --- a/test/unit/test_encryption_context.py +++ b/test/unit/test_encryption_context.py @@ -1,12 +1,24 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.formatting.encryption_context""" import unittest import six from aws_encryption_sdk.exceptions import SerializationError +from aws_encryption_sdk.identifiers import ContentAADString import aws_encryption_sdk.internal.defaults import aws_encryption_sdk.internal.formatting.encryption_context -from aws_encryption_sdk.identifiers import ContentAADString from .test_values import VALUES @@ -74,7 +86,9 @@ def test_serialize_encryption_context_unencodable(self): """ for encryption_context in [{'a': b'\xc4'}, {b'\xc4': 'a'}, {b'\xc4': b'\xc4'}]: with six.assertRaisesRegex(self, SerializationError, 'Cannot encode dictionary key or value using *'): - aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context(encryption_context) + aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( + encryption_context + ) def test_serialize_encryption_context_valid(self): """Validate that the serialize_encryption_context diff --git a/test/unit/test_identifiers.py b/test/unit/test_identifiers.py index b123ad39a..9b9b44911 100644 --- a/test/unit/test_identifiers.py +++ b/test/unit/test_identifiers.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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""" import pytest diff --git a/test/unit/test_internal_structures.py b/test/unit/test_internal_structures.py index d03341c88..8e6d5a064 100644 --- a/test/unit/test_internal_structures.py +++ b/test/unit/test_internal_structures.py @@ -1,10 +1,22 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.structures""" import attr import pytest import six from aws_encryption_sdk.internal.structures import ( - EncryptedData, MessageHeaderAuthentication, MessageFrameBody, MessageNoFrameBody, MessageFooter + EncryptedData, MessageFooter, MessageFrameBody, MessageHeaderAuthentication, MessageNoFrameBody ) diff --git a/test/unit/test_material_managers.py b/test/unit/test_material_managers.py index 98297ee7f..d11093932 100644 --- a/test/unit/test_material_managers.py +++ b/test/unit/test_material_managers.py @@ -1,15 +1,27 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.materials_managers""" from mock import MagicMock import pytest -from pytest_mock import mocker +from pytest_mock import mocker # noqa pylint: disable=unused-import from aws_encryption_sdk.identifiers import Algorithm -from aws_encryption_sdk.structures import DataKey from aws_encryption_sdk.internal.utils import ROStream from aws_encryption_sdk.materials_managers import ( - EncryptionMaterialsRequest, EncryptionMaterials, - DecryptionMaterialsRequest, DecryptionMaterials + DecryptionMaterials, DecryptionMaterialsRequest, + EncryptionMaterials, EncryptionMaterialsRequest ) +from aws_encryption_sdk.structures import DataKey _VALID_KWARGS = { diff --git a/test/unit/test_material_managers_base.py b/test/unit/test_material_managers_base.py index 13b2f3176..bd964b774 100644 --- a/test/unit/test_material_managers_base.py +++ b/test/unit/test_material_managers_base.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.materials_managers.base""" import pytest diff --git a/test/unit/test_material_managers_caching.py b/test/unit/test_material_managers_caching.py index 578404e51..29a0c1a55 100644 --- a/test/unit/test_material_managers_caching.py +++ b/test/unit/test_material_managers_caching.py @@ -1,16 +1,28 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite for CachingCryptoMaterialsManager""" from mock import MagicMock, sentinel import pytest -from pytest_mock import mocker +from pytest_mock import mocker # noqa pylint: disable=unused-import from aws_encryption_sdk.caches.base import CryptoMaterialsCache from aws_encryption_sdk.exceptions import CacheKeyError +from aws_encryption_sdk.internal.defaults import MAX_BYTES_PER_KEY, MAX_MESSAGES_PER_KEY +from aws_encryption_sdk.internal.str_ops import to_bytes from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager import aws_encryption_sdk.materials_managers.caching from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager -from aws_encryption_sdk.internal.defaults import MAX_MESSAGES_PER_KEY, MAX_BYTES_PER_KEY -from aws_encryption_sdk.internal.str_ops import to_bytes def build_ccmm(**custom_kwargs): @@ -80,8 +92,8 @@ def test_mkp_to_default_cmm(mocker): master_key_provider=mock_mkp ) - aws_encryption_sdk.materials_managers.caching.DefaultCryptoMaterialsManager.assert_called_once_with(mock_mkp) - assert test.backing_materials_manager is aws_encryption_sdk.materials_managers.caching.DefaultCryptoMaterialsManager.return_value + aws_encryption_sdk.materials_managers.caching.DefaultCryptoMaterialsManager.assert_called_once_with(mock_mkp) # noqa pylint: disable=line-too-long + assert test.backing_materials_manager is aws_encryption_sdk.materials_managers.caching.DefaultCryptoMaterialsManager.return_value # noqa pylint: disable=line-too-long @pytest.mark.parametrize('invalid_kwargs, error_message', ( @@ -253,11 +265,11 @@ def test_get_encryption_materials_do_not_cache(patch_should_cache_encryption_req def test_get_encryption_materials_cache_hit_expired_entry( - patch_encryption_materials_request, - patch_should_cache_encryption_request, - patch_cache_entry_has_exceeded_limits, - patch_build_encryption_materials_cache_key, - patch_crypto_cache_entry_hints + patch_encryption_materials_request, + patch_should_cache_encryption_request, + patch_cache_entry_has_exceeded_limits, + patch_build_encryption_materials_cache_key, + patch_crypto_cache_entry_hints ): patch_cache_entry_has_exceeded_limits.return_value = True mock_request = fake_encryption_request() @@ -287,7 +299,7 @@ def test_get_encryption_materials_cache_hit_expired_entry( ccmm.backing_materials_manager.get_encryption_materials.assert_called_once_with( patch_encryption_materials_request.return_value ) - ccmm.backing_materials_manager.get_encryption_materials.return_value.algorithm.safe_to_cache.assert_called_once_with() + ccmm.backing_materials_manager.get_encryption_materials.return_value.algorithm.safe_to_cache.assert_called_once_with() # noqa pylint: disable=line-too-long patch_crypto_cache_entry_hints.assert_called_once_with(lifetime=ccmm.max_age) ccmm.cache.put_encryption_materials.assert_called_once_with( @@ -301,10 +313,10 @@ def test_get_encryption_materials_cache_hit_expired_entry( def test_get_encryption_materials_cache_hit_good_entry( - patch_encryption_materials_request, - patch_should_cache_encryption_request, - patch_cache_entry_has_exceeded_limits, - patch_build_encryption_materials_cache_key + patch_encryption_materials_request, + patch_should_cache_encryption_request, + patch_cache_entry_has_exceeded_limits, + patch_build_encryption_materials_cache_key ): patch_cache_entry_has_exceeded_limits.return_value = False mock_request = fake_encryption_request() @@ -318,10 +330,10 @@ def test_get_encryption_materials_cache_hit_good_entry( def test_get_encryption_materials_cache_miss( - patch_encryption_materials_request, - patch_should_cache_encryption_request, - patch_cache_entry_has_exceeded_limits, - patch_build_encryption_materials_cache_key + patch_encryption_materials_request, + patch_should_cache_encryption_request, + patch_cache_entry_has_exceeded_limits, + patch_build_encryption_materials_cache_key ): mock_request = fake_encryption_request() mock_request.plaintext_length = 10 @@ -337,10 +349,10 @@ def test_get_encryption_materials_cache_miss( def test_get_encryption_materials_cache_miss_plaintext_too_big_to_cache( - patch_encryption_materials_request, - patch_should_cache_encryption_request, - patch_cache_entry_has_exceeded_limits, - patch_build_encryption_materials_cache_key + patch_encryption_materials_request, + patch_should_cache_encryption_request, + patch_cache_entry_has_exceeded_limits, + patch_build_encryption_materials_cache_key ): mock_request = fake_encryption_request() mock_request.plaintext_length = 100 @@ -354,10 +366,10 @@ def test_get_encryption_materials_cache_miss_plaintext_too_big_to_cache( def test_get_encryption_materials_cache_miss_algorithm_not_safe_to_cache( - patch_encryption_materials_request, - patch_should_cache_encryption_request, - patch_cache_entry_has_exceeded_limits, - patch_build_encryption_materials_cache_key + patch_encryption_materials_request, + patch_should_cache_encryption_request, + patch_cache_entry_has_exceeded_limits, + patch_build_encryption_materials_cache_key ): mock_request = fake_encryption_request() mock_request.plaintext_length = 10 @@ -383,8 +395,8 @@ def patch_cache_entry_is_too_old(mocker): def test_decrypt_materials_cache_hit_good_entry( - patch_build_decryption_materials_cache_key, - patch_cache_entry_is_too_old + patch_build_decryption_materials_cache_key, + patch_cache_entry_is_too_old ): patch_cache_entry_is_too_old.return_value = False ccmm = build_ccmm() @@ -404,8 +416,8 @@ def test_decrypt_materials_cache_hit_good_entry( def test_decrypt_materials_cache_hit_expired_entry( - patch_build_decryption_materials_cache_key, - patch_cache_entry_is_too_old + patch_build_decryption_materials_cache_key, + patch_cache_entry_is_too_old ): patch_cache_entry_is_too_old.return_value = True ccmm = build_ccmm() diff --git a/test/unit/test_material_managers_default.py b/test/unit/test_material_managers_default.py index 7d1ea6093..d0da70584 100644 --- a/test/unit/test_material_managers_default.py +++ b/test/unit/test_material_managers_default.py @@ -1,17 +1,29 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.materials_managers.default""" from mock import MagicMock, sentinel import pytest -from pytest_mock import mocker +from pytest_mock import mocker # noqa pylint: disable=unused-import from aws_encryption_sdk.exceptions import MasterKeyProviderError,\ SerializationError from aws_encryption_sdk.identifiers import Algorithm +from aws_encryption_sdk.internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.materials_managers import EncryptionMaterials import aws_encryption_sdk.materials_managers.default from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import DataKey -from aws_encryption_sdk.internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY @pytest.fixture diff --git a/test/unit/test_providers_base_master_key.py b/test/unit/test_providers_base_master_key.py index 4d5a007cd..0e2cdc711 100644 --- a/test/unit/test_providers_base_master_key.py +++ b/test/unit/test_providers_base_master_key.py @@ -1,15 +1,23 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.key_providers.base.MasterKey""" import unittest import attr -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.utils import InterfaceNotImplemented -from mock import MagicMock, sentinel, patch +from mock import MagicMock, patch, sentinel import six -from aws_encryption_sdk.exceptions import ( - InvalidKeyIdError, IncorrectMasterKeyError, ConfigMismatchError, NotSupportedError -) +from aws_encryption_sdk.exceptions import ConfigMismatchError, IncorrectMasterKeyError, InvalidKeyIdError from aws_encryption_sdk.internal.defaults import ALGORITHM from aws_encryption_sdk.key_providers.base import MasterKey, MasterKeyConfig, MasterKeyProvider from aws_encryption_sdk.structures import MasterKeyInfo @@ -34,7 +42,7 @@ def _generate_data_key(self, algorithm, encryption_context): def _encrypt_data_key(self, data_key, algorithm, encryption_context): return self.config.mock_encrypted_data_key - def _decrypt_data_key(self, encrypted_data_key, encryption_context): + def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): return self.config.mock_decrypted_data_key @@ -58,7 +66,7 @@ def generate_data_key(self, algorithm, encryption_context): def _encrypt_data_key(self, data_key, algorithm, encryption_context): pass - def _decrypt_data_key(self, encrypted_data_key, encryption_context): + def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): pass with six.assertRaisesRegex( self, @@ -74,7 +82,7 @@ class TestMasterKey(MasterKey): def _encrypt_data_key(self, data_key, algorithm, encryption_context): pass - def _decrypt_data_key(self, encrypted_data_key, encryption_context): + def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): pass with six.assertRaisesRegex( self, @@ -90,7 +98,7 @@ class TestMasterKey(MasterKey): def generate_data_key(self, algorithm, encryption_context): pass - def _decrypt_data_key(self, encrypted_data_key, encryption_context): + def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): pass with six.assertRaisesRegex( self, diff --git a/test/unit/test_providers_base_master_key_config.py b/test/unit/test_providers_base_master_key_config.py index e80dddd2d..9a4305e49 100644 --- a/test/unit/test_providers_base_master_key_config.py +++ b/test/unit/test_providers_base_master_key_config.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.key_providers.base.MasterKeyConfig""" import attr from mock import sentinel diff --git a/test/unit/test_providers_base_master_key_provider.py b/test/unit/test_providers_base_master_key_provider.py index 675501fde..bfdd8f188 100644 --- a/test/unit/test_providers_base_master_key_provider.py +++ b/test/unit/test_providers_base_master_key_provider.py @@ -1,12 +1,24 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.key_providers.base.MasterKeyProvider""" import unittest import attr -from mock import MagicMock, sentinel, call, PropertyMock, patch +from mock import call, MagicMock, patch, PropertyMock, sentinel import six from aws_encryption_sdk.exceptions import ( - DecryptKeyError, InvalidKeyIdError, IncorrectMasterKeyError, MasterKeyProviderError + DecryptKeyError, IncorrectMasterKeyError, InvalidKeyIdError, MasterKeyProviderError ) from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig diff --git a/test/unit/test_providers_base_master_key_provider_config.py b/test/unit/test_providers_base_master_key_provider_config.py index 6e0c7cab4..0e21ded80 100644 --- a/test/unit/test_providers_base_master_key_provider_config.py +++ b/test/unit/test_providers_base_master_key_provider_config.py @@ -1,6 +1,17 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.key_providers.base.MasterKeyProviderConfig""" -from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig +from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig # noqa pylint: disable=unused-import -"""Nothing to test at this time, but import will ensure that it exists. - If this MasterKeyProviderConfig has attributes added in the future, they should be tested here. -""" +# Nothing to test at this time, but import will ensure that it exists. +# If this MasterKeyProviderConfig has attributes added in the future, they should be tested here. diff --git a/test/unit/test_providers_kms_master_key.py b/test/unit/test_providers_kms_master_key.py index 5a26abfb7..118ea25da 100644 --- a/test/unit/test_providers_kms_master_key.py +++ b/test/unit/test_providers_kms_master_key.py @@ -1,14 +1,25 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.key_providers.kms.KMSMasterKey""" import unittest -import botocore import botocore.client from botocore.exceptions import ClientError -from mock import MagicMock, sentinel, patch +from mock import MagicMock, patch, sentinel import six -from aws_encryption_sdk.exceptions import GenerateKeyError, EncryptKeyError, DecryptKeyError -from aws_encryption_sdk.identifiers import Algorithm, __version__, user_agent_suffix +from aws_encryption_sdk.exceptions import DecryptKeyError, EncryptKeyError, GenerateKeyError +from aws_encryption_sdk.identifiers import Algorithm, USER_AGENT_SUFFIX from aws_encryption_sdk.key_providers.base import MasterKey from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyConfig from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo @@ -89,7 +100,7 @@ def test_init(self, patch_extend_user_agent_suffix): assert test._key_id == VALUES['arn'].decode('utf-8') patch_extend_user_agent_suffix.assert_called_once_with( user_agent=sentinel.user_agent_extra, - suffix=user_agent_suffix + suffix=USER_AGENT_SUFFIX ) assert self.mock_client.meta.config.user_agent_extra == patch_extend_user_agent_suffix.return_value diff --git a/test/unit/test_providers_kms_master_key_config.py b/test/unit/test_providers_kms_master_key_config.py index a3f1e93b3..9ff75c353 100644 --- a/test/unit/test_providers_kms_master_key_config.py +++ b/test/unit/test_providers_kms_master_key_config.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.key_providers.kms.KMSMasterKeyConfig""" import attr import botocore.client @@ -5,7 +17,7 @@ import pytest from aws_encryption_sdk.key_providers.base import MasterKeyConfig -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyConfig, _PROVIDER_ID +from aws_encryption_sdk.key_providers.kms import _PROVIDER_ID, KMSMasterKeyConfig def test_parent(): diff --git a/test/unit/test_providers_kms_master_key_provider.py b/test/unit/test_providers_kms_master_key_provider.py index 30dab7963..c234e6669 100644 --- a/test/unit/test_providers_kms_master_key_provider.py +++ b/test/unit/test_providers_kms_master_key_provider.py @@ -1,13 +1,25 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite from aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider""" import unittest import botocore.client -from mock import MagicMock, patch, sentinel, call, ANY +from mock import ANY, call, MagicMock, patch, sentinel import six from aws_encryption_sdk.exceptions import UnknownRegionError from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider, KMSMasterKey +from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider class TestKMSMasterKeyProvider(unittest.TestCase): @@ -56,7 +68,11 @@ def test_init_with_region_names(self, mock_add_clients): def test_init_with_default_region_found(self, mock_add_regional_client): test = KMSMasterKeyProvider() assert test.default_region is None - with patch.object(test.config.botocore_session, 'get_config_variable', return_value=sentinel.default_region) as mock_get_config: + with patch.object( + test.config.botocore_session, + 'get_config_variable', + return_value=sentinel.default_region + ) as mock_get_config: test._process_config() mock_get_config.assert_called_once_with('region') assert test.default_region is sentinel.default_region diff --git a/test/unit/test_providers_kms_master_key_provider_config.py b/test/unit/test_providers_kms_master_key_provider_config.py index 20014d9b3..c18de50db 100644 --- a/test/unit/test_providers_kms_master_key_provider_config.py +++ b/test/unit/test_providers_kms_master_key_provider_config.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.key_providers.kms.KMSMasterKeyProviderConfig""" import attr import botocore.session @@ -13,7 +25,12 @@ def test_parent(): @pytest.mark.parametrize('attribute, default, validator_type, convert_function', ( - (KMSMasterKeyProviderConfig.botocore_session, attr.Factory(botocore.session.Session), botocore.session.Session, None), + ( + KMSMasterKeyProviderConfig.botocore_session, + attr.Factory(botocore.session.Session), + botocore.session.Session, + None + ), (KMSMasterKeyProviderConfig.key_ids, attr.Factory(tuple), tuple, tuple), (KMSMasterKeyProviderConfig.region_names, attr.Factory(tuple), tuple, tuple) )) diff --git a/test/unit/test_providers_raw_master_key.py b/test/unit/test_providers_raw_master_key.py index ba01a54a2..a695c412d 100644 --- a/test/unit/test_providers_raw_master_key.py +++ b/test/unit/test_providers_raw_master_key.py @@ -1,13 +1,25 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.key_providers.raw.RawMasterKey""" import unittest -from mock import MagicMock, sentinel, patch +from mock import MagicMock, patch, sentinel -from aws_encryption_sdk.internal.crypto import WrappingKey -from aws_encryption_sdk.identifiers import WrappingAlgorithm, Algorithm +from aws_encryption_sdk.identifiers import Algorithm, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.key_providers.base import MasterKey from aws_encryption_sdk.key_providers.raw import RawMasterKey, RawMasterKeyConfig -from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, RawDataKey, MasterKeyInfo +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey from .test_values import VALUES diff --git a/test/unit/test_providers_raw_master_key_config.py b/test/unit/test_providers_raw_master_key_config.py index 4429eb579..0881ddb53 100644 --- a/test/unit/test_providers_raw_master_key_config.py +++ b/test/unit/test_providers_raw_master_key_config.py @@ -1,10 +1,22 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.key_providers.raw.RawMasterKeyConfig""" import attr from mock import MagicMock import pytest import six -from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.internal.str_ops import to_str from aws_encryption_sdk.key_providers.base import MasterKeyConfig from aws_encryption_sdk.key_providers.raw import RawMasterKeyConfig diff --git a/test/unit/test_providers_raw_master_key_provider.py b/test/unit/test_providers_raw_master_key_provider.py index 4ca47046e..efe972ef7 100644 --- a/test/unit/test_providers_raw_master_key_provider.py +++ b/test/unit/test_providers_raw_master_key_provider.py @@ -1,11 +1,23 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.key_providers.raw.RawMasterKeyProvider""" import unittest import attr -from mock import MagicMock, sentinel, patch +from mock import MagicMock, patch, sentinel import six -from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig, MasterKeyProvider +from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider from .test_values import VALUES @@ -22,7 +34,7 @@ class MockRawMasterKeyProvider(RawMasterKeyProvider): _config_class = MockRawMasterKeyProviderConfig provider_id = VALUES['provider_id'] - def _get_raw_key(self, key_info): + def _get_raw_key(self, key_id): return self.config.mock_wrapping_key diff --git a/test/unit/test_serialize.py b/test/unit/test_serialize.py index 7d00f17bf..9e0254a9c 100644 --- a/test/unit/test_serialize.py +++ b/test/unit/test_serialize.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.internal.formatting.serialize""" import unittest @@ -5,9 +17,9 @@ import pytest from aws_encryption_sdk.exceptions import SerializationError -import aws_encryption_sdk.internal.formatting.serialize from aws_encryption_sdk.identifiers import ContentAADString from aws_encryption_sdk.internal.defaults import MAX_FRAME_COUNT +import aws_encryption_sdk.internal.formatting.serialize from aws_encryption_sdk.internal.structures import EncryptedData from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo from .test_values import VALUES @@ -60,10 +72,10 @@ def setUp(self): self.mock_serialize_acc = self.mock_serialize_acc_patcher.start() self.mock_serialize_acc.serialize_encryption_context.return_value = VALUES['serialized_encryption_context'] # Set up crypto patch - self.mock_crypto_patcher = patch( - 'aws_encryption_sdk.internal.formatting.serialize.aws_encryption_sdk.internal.crypto' + self.mock_encrypt_patcher = patch( + 'aws_encryption_sdk.internal.formatting.serialize.encrypt' ) - self.mock_crypto = self.mock_crypto_patcher.start() + self.mock_encrypt = self.mock_encrypt_patcher.start() # Set up validate_frame_length patch self.mock_valid_frame_length_patcher = patch( 'aws_encryption_sdk.internal.formatting.serialize.aws_encryption_sdk.internal.utils.validate_frame_length' @@ -76,7 +88,7 @@ def setUp(self): def tearDown(self): self.mock_serialize_acc_patcher.stop() - self.mock_crypto_patcher.stop() + self.mock_encrypt_patcher.stop() self.mock_valid_frame_length_patcher.stop() def test_serialize_header(self): @@ -106,14 +118,14 @@ def test_serialize_header_auth(self, mock_header_auth_iv): """Validate that the _create_header_auth function behaves as expected. """ - self.mock_crypto.encrypt.return_value = VALUES['header_auth_base'] + self.mock_encrypt.return_value = VALUES['header_auth_base'] test = aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( algorithm=self.mock_algorithm, header=VALUES['serialized_header'], data_encryption_key=sentinel.encryption_key, signer=self.mock_signer ) - self.mock_crypto.encrypt.assert_called_once_with( + self.mock_encrypt.assert_called_once_with( algorithm=self.mock_algorithm, key=sentinel.encryption_key, plaintext=b'', @@ -127,7 +139,7 @@ def test_serialize_header_auth_no_signer(self): """Validate that the _create_header_auth function behaves as expected when called with no signer. """ - self.mock_crypto.encrypt.return_value = VALUES['header_auth_base'] + self.mock_encrypt.return_value = VALUES['header_auth_base'] aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( algorithm=self.mock_algorithm, header=VALUES['serialized_header'], @@ -184,7 +196,7 @@ def test_encrypt_and_serialize_frame(self, mock_frame_iv): function behaves as expected for a normal frame. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES['frame_aac'] - self.mock_crypto.encrypt.return_value = VALUES['frame_base'] + self.mock_encrypt.return_value = VALUES['frame_base'] source_plaintext = VALUES['data_128'] * 2 test_serialized, test_remainder = aws_encryption_sdk.internal.formatting.serialize.serialize_frame( algorithm=self.mock_algorithm, @@ -203,7 +215,7 @@ def test_encrypt_and_serialize_frame(self, mock_frame_iv): length=VALUES['small_frame_length'] ) mock_frame_iv.assert_called_once_with(self.mock_algorithm, self.mock_valid_sequence_number) - self.mock_crypto.encrypt.assert_called_once_with( + self.mock_encrypt.assert_called_once_with( algorithm=self.mock_algorithm, key=sentinel.encryption_key, plaintext=source_plaintext[:VALUES['small_frame_length']], @@ -220,7 +232,7 @@ def test_encrypt_and_serialize_frame_no_signer(self): when called with no signer. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES['frame_aac'] - self.mock_crypto.encrypt.return_value = VALUES['frame_base'] + self.mock_encrypt.return_value = VALUES['frame_base'] aws_encryption_sdk.internal.formatting.serialize.serialize_frame( algorithm=self.mock_algorithm, plaintext=VALUES['data_128'] * 2, @@ -237,7 +249,7 @@ def test_encrypt_and_serialize_frame_final(self, mock_frame_iv): function behaves as expected for a final frame. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES['final_frame_aac'] - self.mock_crypto.encrypt.return_value = VALUES['final_frame_base'] + self.mock_encrypt.return_value = VALUES['final_frame_base'] test_serialized, test_remainder = aws_encryption_sdk.internal.formatting.serialize.serialize_frame( algorithm=self.mock_algorithm, plaintext=VALUES['data_128'], @@ -255,7 +267,7 @@ def test_encrypt_and_serialize_frame_final(self, mock_frame_iv): length=len(VALUES['data_128']) ) mock_frame_iv.assert_called_once_with(self.mock_algorithm, self.mock_valid_sequence_number) - self.mock_crypto.encrypt.assert_called_once_with( + self.mock_encrypt.assert_called_once_with( algorithm=self.mock_algorithm, key=sentinel.encryption_key, plaintext=VALUES['data_128'], @@ -272,7 +284,7 @@ def test_encrypt_and_serialize_frame_final_no_signer(self): when called with no signer. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES['final_frame_aac'] - self.mock_crypto.encrypt.return_value = VALUES['final_frame_base'] + self.mock_encrypt.return_value = VALUES['final_frame_base'] aws_encryption_sdk.internal.formatting.serialize.serialize_frame( algorithm=self.mock_algorithm, plaintext=VALUES['data_128'], diff --git a/test/unit/test_streaming_client_client_config.py b/test/unit/test_streaming_client_client_config.py index 6c304e422..bb0ae9c97 100644 --- a/test/unit/test_streaming_client_client_config.py +++ b/test/unit/test_streaming_client_client_config.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.streaming_client._ClientConfig""" import io diff --git a/test/unit/test_streaming_client_decryptor_config.py b/test/unit/test_streaming_client_decryptor_config.py index 7b1fb2daa..79917e7fb 100644 --- a/test/unit/test_streaming_client_decryptor_config.py +++ b/test/unit/test_streaming_client_decryptor_config.py @@ -1,11 +1,23 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.streaming_client.DecryptorConfig""" import attr from mock import MagicMock import pytest import six -from aws_encryption_sdk.streaming_client import DecryptorConfig, _ClientConfig from aws_encryption_sdk.key_providers.base import MasterKeyProvider +from aws_encryption_sdk.streaming_client import _ClientConfig, DecryptorConfig def test_parent(): diff --git a/test/unit/test_streaming_client_encryption_stream.py b/test/unit/test_streaming_client_encryption_stream.py index f8a6a0815..e037035d1 100644 --- a/test/unit/test_streaming_client_encryption_stream.py +++ b/test/unit/test_streaming_client_encryption_stream.py @@ -1,14 +1,26 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.streaming_client._EncryptionStream""" import io import unittest import attr -from mock import MagicMock, PropertyMock, sentinel, call, patch +from mock import call, MagicMock, patch, PropertyMock, sentinel import six import aws_encryption_sdk.exceptions from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.streaming_client import _EncryptionStream, _ClientConfig +from aws_encryption_sdk.streaming_client import _ClientConfig, _EncryptionStream from .test_values import VALUES @@ -176,7 +188,7 @@ def test_stream_length_unsupported(self): mock_read_bytes=sentinel.read_bytes ) with six.assertRaisesRegex(self, aws_encryption_sdk.exceptions.NotSupportedError, 'Unexpected exception!'): - mock_stream.stream_length + mock_stream.stream_length # pylint: disable=pointless-statement def test_header_property(self): mock_prep_message = MagicMock() diff --git a/test/unit/test_streaming_client_encryptor_config.py b/test/unit/test_streaming_client_encryptor_config.py index 1168f2d25..9cfbaa087 100644 --- a/test/unit/test_streaming_client_encryptor_config.py +++ b/test/unit/test_streaming_client_encryptor_config.py @@ -1,13 +1,25 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.streaming_client.EncryptorConfig""" import attr from mock import MagicMock import pytest import six -from aws_encryption_sdk.internal.defaults import FRAME_LENGTH from aws_encryption_sdk.identifiers import Algorithm -from aws_encryption_sdk.streaming_client import EncryptorConfig, _ClientConfig +from aws_encryption_sdk.internal.defaults import FRAME_LENGTH from aws_encryption_sdk.key_providers.base import MasterKey +from aws_encryption_sdk.streaming_client import _ClientConfig, EncryptorConfig def test_parent(): diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index bbafe51a3..9753bfeba 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -1,12 +1,24 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.streaming_client.StreamDecryptor""" import io import unittest -from mock import MagicMock, patch, sentinel, call +from mock import call, MagicMock, patch, sentinel import six -from aws_encryption_sdk.exceptions import NotSupportedError, SerializationError, CustomMaximumValueExceeded -from aws_encryption_sdk.identifiers import ContentType, Algorithm +from aws_encryption_sdk.exceptions import CustomMaximumValueExceeded, NotSupportedError, SerializationError +from aws_encryption_sdk.identifiers import Algorithm, ContentType from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.streaming_client import StreamDecryptor @@ -39,7 +51,8 @@ def setUp(self): self.mock_deserialize_header.return_value = self.mock_header # Set up deserialize_header_auth patch self.mock_deserialize_header_auth_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.formatting.deserialize.deserialize_header_auth' + 'aws_encryption_sdk.streaming_client' + '.aws_encryption_sdk.internal.formatting.deserialize.deserialize_header_auth' ) self.mock_deserialize_header_auth = self.mock_deserialize_header_auth_patcher.start() self.mock_deserialize_header_auth.return_value = sentinel.header_auth @@ -50,7 +63,8 @@ def setUp(self): self.mock_validate_header = self.mock_validate_header_patcher.start() # Set up deserialize_non_framed_values patch self.mock_deserialize_non_framed_values_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.formatting.deserialize.deserialize_non_framed_values' + 'aws_encryption_sdk.streaming_client' + '.aws_encryption_sdk.internal.formatting.deserialize.deserialize_non_framed_values' ) self.mock_deserialize_non_framed_values = self.mock_deserialize_non_framed_values_patcher.start() self.mock_deserialize_non_framed_values.return_value = (sentinel.iv, sentinel.tag, len(VALUES['data_128'])) @@ -62,20 +76,22 @@ def setUp(self): self.mock_get_aad_content_string.return_value = sentinel.aad_content_string # Set up assemble_content_aad patch self.mock_assemble_content_aad_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.formatting.encryption_context.assemble_content_aad' + 'aws_encryption_sdk.streaming_client' + '.aws_encryption_sdk.internal.formatting.encryption_context.assemble_content_aad' ) self.mock_assemble_content_aad = self.mock_assemble_content_aad_patcher.start() self.mock_assemble_content_aad.return_value = sentinel.associated_data # Set up Decryptor patch self.mock_decryptor_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.Decryptor' + 'aws_encryption_sdk.streaming_client.Decryptor' ) self.mock_decryptor = self.mock_decryptor_patcher.start() self.mock_decryptor_instance = MagicMock() self.mock_decryptor.return_value = self.mock_decryptor_instance # Set up update_verifier_with_tag patch self.mock_update_verifier_with_tag_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.formatting.deserialize.update_verifier_with_tag' + 'aws_encryption_sdk.streaming_client' + '.aws_encryption_sdk.internal.formatting.deserialize.update_verifier_with_tag' ) self.mock_update_verifier_with_tag = self.mock_update_verifier_with_tag_patcher.start() # Set up deserialize_footer patch @@ -90,7 +106,7 @@ def setUp(self): self.mock_deserialize_frame = self.mock_deserialize_frame_patcher.start() # Set up decrypt patch self.mock_decrypt_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.decrypt' + 'aws_encryption_sdk.streaming_client.decrypt' ) self.mock_decrypt = self.mock_decrypt_patcher.start() @@ -143,9 +159,9 @@ def test_prep_message_non_framed_message(self, mock_read_header, mock_prep_non_f test_decryptor._prep_message() mock_prep_non_framed.assert_called_once_with() - @patch('aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.Verifier') + @patch('aws_encryption_sdk.streaming_client.Verifier') @patch('aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest') - @patch('aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.derive_data_encryption_key') + @patch('aws_encryption_sdk.streaming_client.derive_data_encryption_key') @patch('aws_encryption_sdk.streaming_client.StreamDecryptor.__init__') def test_read_header(self, mock_init, mock_derive_datakey, mock_decrypt_materials_request, mock_verifier): mock_verifier_instance = MagicMock() @@ -197,7 +213,7 @@ def test_read_header(self, mock_init, mock_derive_datakey, mock_decrypt_material assert test_header is self.mock_header assert test_header_auth is sentinel.header_auth - @patch('aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.derive_data_encryption_key') + @patch('aws_encryption_sdk.streaming_client.derive_data_encryption_key') @patch('aws_encryption_sdk.streaming_client.StreamDecryptor.__init__') def test_read_header_frame_too_large(self, mock_init, mock_derive_datakey): self.mock_header.content_type = ContentType.FRAMED_DATA @@ -222,16 +238,16 @@ def test_read_header_frame_too_large(self, mock_init, mock_derive_datakey): ): test_decryptor._read_header() - @patch('aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.Verifier') + @patch('aws_encryption_sdk.streaming_client.Verifier') @patch('aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest') - @patch('aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.derive_data_encryption_key') + @patch('aws_encryption_sdk.streaming_client.derive_data_encryption_key') @patch('aws_encryption_sdk.streaming_client.StreamDecryptor.__init__') def test_read_header_no_verifier( - self, - mock_init, - mock_derive_datakey, - mock_decrypt_materials_request, - mock_verifier + self, + mock_init, + mock_derive_datakey, + mock_decrypt_materials_request, + mock_verifier ): self.mock_materials_manager.decrypt_materials.return_value = MagicMock( data_key=VALUES['data_key_obj'], diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index b43b8f3e7..e5a44f899 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -1,16 +1,28 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.streaming_client.StreamEncryptor""" import io import unittest -from mock import MagicMock, patch, sentinel, call +from mock import call, MagicMock, patch, sentinel import six from aws_encryption_sdk.exceptions import ( - NotSupportedError, SerializationError, MasterKeyProviderError, ActionNotAllowedError + ActionNotAllowedError, MasterKeyProviderError, NotSupportedError, SerializationError ) -import aws_encryption_sdk.internal.defaults from aws_encryption_sdk.identifiers import Algorithm, ContentType -from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKey +import aws_encryption_sdk.internal.defaults +from aws_encryption_sdk.key_providers.base import MasterKey, MasterKeyProvider from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.streaming_client import StreamEncryptor from aws_encryption_sdk.structures import MessageHeader @@ -81,7 +93,7 @@ def setUp(self): self.mock_message_id.return_value = VALUES['message_id'] # Set up signer patch self.mock_signer_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.Signer' + 'aws_encryption_sdk.streaming_client.Signer' ) self.mock_signer = self.mock_signer_patcher.start() self.mock_signer_instance = MagicMock() @@ -112,13 +124,14 @@ def setUp(self): self.mock_get_aad_content_string.return_value = sentinel.aad_content_string # Set up assemble_content_aad patch self.mock_assemble_content_aad_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.formatting.encryption_context.assemble_content_aad' + 'aws_encryption_sdk.streaming_client' + '.aws_encryption_sdk.internal.formatting.encryption_context.assemble_content_aad' ) self.mock_assemble_content_aad = self.mock_assemble_content_aad_patcher.start() self.mock_assemble_content_aad.return_value = sentinel.associated_data # Set up encryptor patch self.mock_encryptor_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.Encryptor' + 'aws_encryption_sdk.streaming_client.Encryptor' ) self.mock_encryptor = self.mock_encryptor_patcher.start() self.mock_encryptor_instance = MagicMock() @@ -126,12 +139,14 @@ def setUp(self): self.mock_encryptor.return_value = self.mock_encryptor_instance # Set up serialize_non_framed_open patch self.mock_serialize_non_framed_open_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_open' + 'aws_encryption_sdk.streaming_client' + '.aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_open' ) self.mock_serialize_non_framed_open = self.mock_serialize_non_framed_open_patcher.start() # Set up serialize_non_framed_close patch self.mock_serialize_non_framed_close_patcher = patch( - 'aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_close' + 'aws_encryption_sdk.streaming_client' + '.aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_close' ) self.mock_serialize_non_framed_close = self.mock_serialize_non_framed_close_patcher.start() # Set up serialize_footer patch @@ -226,17 +241,17 @@ def test_prep_message_algorithm_change(self): test_encryptor._prep_message() @patch('aws_encryption_sdk.streaming_client.EncryptionMaterialsRequest') - @patch('aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.crypto.derive_data_encryption_key') + @patch('aws_encryption_sdk.streaming_client.derive_data_encryption_key') @patch('aws_encryption_sdk.internal.utils.ROStream') @patch('aws_encryption_sdk.streaming_client.StreamEncryptor._prep_non_framed') @patch('aws_encryption_sdk.streaming_client.StreamEncryptor._write_header') def test_prep_message_framed_message( - self, - mock_write_header, - mock_prep_non_framed, - mock_rostream, - mock_derive_datakey, - mock_encryption_materials_request + self, + mock_write_header, + mock_prep_non_framed, + mock_rostream, + mock_derive_datakey, + mock_encryption_materials_request ): mock_rostream.return_value = sentinel.plaintext_rostream test_encryptor = StreamEncryptor( diff --git a/test/unit/test_structures.py b/test/unit/test_structures.py index 76d155794..b08ca63d0 100644 --- a/test/unit/test_structures.py +++ b/test/unit/test_structures.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# 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.structures""" import attr from mock import MagicMock @@ -5,11 +17,11 @@ import six from aws_encryption_sdk.identifiers import ( - SerializationVersion, ObjectType, Algorithm, ContentType + Algorithm, ContentType, ObjectType, SerializationVersion ) -from aws_encryption_sdk.internal.str_ops import to_str, to_bytes +from aws_encryption_sdk.internal.str_ops import to_bytes, to_str from aws_encryption_sdk.structures import ( - MessageHeader, MasterKeyInfo, RawDataKey, DataKey, EncryptedDataKey + DataKey, EncryptedDataKey, MasterKeyInfo, MessageHeader, RawDataKey ) @@ -179,16 +191,16 @@ def test_attributes(attribute, validator_type, convert_function): ) ) def test_message_header_attributes_fails( - version, - message_type, - algorithm, - message_id, - encryption_context, - encrypted_data_keys, - content_type, - content_aad_length, - header_iv_length, - frame_length + version, + message_type, + algorithm, + message_id, + encryption_context, + encrypted_data_keys, + content_type, + content_aad_length, + header_iv_length, + frame_length ): with pytest.raises(TypeError): MessageHeader( diff --git a/test/unit/test_util_str_ops.py b/test/unit/test_util_str_ops.py index 5f9cbc9be..704ba5672 100644 --- a/test/unit/test_util_str_ops.py +++ b/test/unit/test_util_str_ops.py @@ -1,4 +1,16 @@ # -*- coding: utf-8 -*- +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.internal.str_ops""" import codecs import unittest diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index 1ba426300..f649b2ab4 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """ Test suite for aws_encryption_sdk.internal.utils """ @@ -8,13 +20,13 @@ import six from aws_encryption_sdk.exceptions import ( - ActionNotAllowedError, SerializationError, - UnknownIdentityError, InvalidDataKeyError + ActionNotAllowedError, InvalidDataKeyError, + SerializationError, UnknownIdentityError ) -import aws_encryption_sdk.internal.defaults import aws_encryption_sdk.identifiers +from aws_encryption_sdk.internal.defaults import MAX_FRAME_SIZE, MESSAGE_ID_LENGTH import aws_encryption_sdk.internal.utils -from aws_encryption_sdk.structures import RawDataKey, DataKey, EncryptedDataKey, MasterKeyInfo +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey from .test_values import VALUES @@ -182,16 +194,14 @@ def test_validate_frame_length_too_large(self): """ with six.assertRaisesRegex(self, SerializationError, 'Frame size too large: *'): aws_encryption_sdk.internal.utils.validate_frame_length( - frame_length=aws_encryption_sdk.internal.defaults.MAX_FRAME_SIZE + 1, + frame_length=MAX_FRAME_SIZE + 1, algorithm=self.mock_algorithm ) def test_message_id(self): """Validate that the message_id function behaves as expected.""" test = aws_encryption_sdk.internal.utils.message_id() - self.mock_urandom.assert_called_once_with( - aws_encryption_sdk.internal.defaults.MESSAGE_ID_LENGTH - ) + self.mock_urandom.assert_called_once_with(MESSAGE_ID_LENGTH) self.assertEqual(test, sentinel.random) def test_get_aad_content_string_no_framing(self): diff --git a/test/unit/test_values.py b/test/unit/test_values.py index b2b9324d8..72e1450c5 100644 --- a/test/unit/test_values.py +++ b/test/unit/test_values.py @@ -1,3 +1,15 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. """Values to use in various unit test suites.""" import copy import struct @@ -5,22 +17,21 @@ from mock import MagicMock import six -import aws_encryption_sdk.internal.defaults from aws_encryption_sdk.identifiers import ( Algorithm, ContentType, ObjectType, SerializationVersion ) +import aws_encryption_sdk.internal.defaults from aws_encryption_sdk.internal.structures import ( - EncryptedData, MessageHeaderAuthentication, - MessageNoFrameBody, MessageFrameBody, MessageFooter + EncryptedData, MessageFooter, MessageFrameBody, + MessageHeaderAuthentication, MessageNoFrameBody ) -from aws_encryption_sdk.structures import DataKey, MessageHeader, EncryptedDataKey, MasterKeyInfo +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, MessageHeader def array_byte(source): if six.PY2: return six.b(source) - else: - return source + return source VALUES = { @@ -195,7 +206,7 @@ def array_byte(source): ]) VALUES['frame_base'] = EncryptedData( iv=VALUES['final_frame_base'].iv, - ciphertext=VALUES['final_frame_base'].ciphertext[:VALUES['small_frame_length']], + ciphertext=VALUES['final_frame_base'].ciphertext[:VALUES['small_frame_length']], # noqa works but confuses pylint: disable=unsubscriptable-object tag=VALUES['final_frame_base'].tag ) VALUES['serialized_frame'] = b''.join([ diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..2fd5b17a3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,222 @@ +[tox] +envlist = + py27, py34, py35, py36, + bandit, doc8, readme, + flake8, pylint, + flake8-tests, pylint-tests, + examples, + flake8-examples, pylint-examples + +# Additional environments: +# vulture :: Runs vulture. Prone to false-positives. +# linters :: Runs all linters over all source code. +# linters-tests :: Runs all linters over all tests. +# linters-examples :: Runs all linters over all examples and examples tests. + +[testenv] +passenv = AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_CONTROL +sitepackages = False +deps = + mock + pytest + pytest-cov + pytest-mock + coverage +commands = + coverage run -m pytest \ + --cov aws_encryption_sdk \ + {posargs} + +# Examples +[testenv:examples] +basepython = python3 +commands = + coverage run -m pytest \ + examples/test/ + +# Linters +[testenv:flake8] +basepython = python3 +deps = + flake8 + flake8-docstrings + flake8-import-order +commands = + flake8 src/aws_encryption_sdk/ setup.py + +[testenv:flake8-tests] +basepython = {[testenv:flake8]basepython} +deps = + flake8 + flake8-import-order +commands = + flake8 \ + # Ignore F811 redefinition errors in tests (breaks with pytest-mock use) + --ignore F811 \ + test/ + +[testenv:flake8-examples] +basepython = {[testenv:flake8]basepython} +deps = {[testenv:flake8]deps} +commands = + flake8 \ + # Examples should not treat any imports a application-local. + --application-import-names= \ + examples/src/ + flake8 \ + # Examples should not treat any imports a application-local. + --application-import-names= \ + # Ingore D103 missing docstring errors in tests (test names should be self-documenting) + --ignore D103 \ + examples/test/ + +[testenv:pylint] +basepython = python3 +deps = + {[testenv]deps} + pyflakes + pylint +commands = + pylint \ + --rcfile=pylintrc \ + src/aws_encryption_sdk/ \ + setup.py + +[testenv:pylint-examples] +basepython = {[testenv:pylint]basepython} +deps = {[testenv:pylint]deps} +commands = + pylint --rcfile=examples/src/pylintrc examples/src/ + pylint --rcfile=examples/test/pylintrc --disable R0801 examples/test/ + +[testenv:pylint-tests] +basepython = {[testenv:pylint]basepython} +deps = {[testenv:pylint]deps} +commands = + pylint \ + --rcfile=test/pylintrc \ + test/unit/ \ + test/functional/ \ + test/integration/ + +[testenv:doc8] +basepython = python3 +deps = + sphinx + doc8 +commands = doc8 doc/index.rst README.rst CHANGELOG.rst CONTRIBUTING.rst + +[testenv:readme] +basepython = python3 +deps = readme_renderer +commands = python setup.py check -r -s + +[testenv:bandit] +basepython = python3 +deps = + # Pull bandit from github because they haven't published 1.4.1 to pypi yet + git+git://github.com/openstack/bandit.git@master +commands = bandit -r src/aws_encryption_sdk/ + +# Prone to false positives: only run independently +[testenv:vulture] +basepython = python3 +deps = vulture +commands = vulture src/aws_encryption_sdk/ + +[testenv:linters] +basepython = python3 +deps = + {[testenv:flake8]deps} + {[testenv:pylint]deps} + {[testenv:doc8]deps} + {[testenv:readme]deps} + {[testenv:bandit]deps} +commands = + {[testenv:flake8]commands} + {[testenv:pylint]commands} + {[testenv:doc8]commands} + {[testenv:readme]commands} + {[testenv:bandit]commands} + +[testenv:linters-tests] +basepython = python3 +deps = + {[testenv:flake8-tests]deps} + {[testenv:pylint-tests]deps} +commands = + {[testenv:flake8-tests]commands} + {[testenv:pylint-tests]commands} + +[testenv:linters-examples] +basepython = python3 +deps = + {[testenv:flake8-examples]deps} + {[testenv:pylint-examples]deps} +commands = + {[testenv:flake8-examples]commands} + {[testenv:pylint-examples]commands} + +# Documentation +[testenv:docs] +basepython = python3 +deps = -rdoc/requirements.txt +commands = + sphinx-build -E -c doc/ -b html doc/ doc/build/html + +[testenv:serve-docs] +basepython = python3 +skip_install = true +changedir = doc/build/html +deps = +commands = + python -m http.server {posargs} + +# Release tooling +[testenv:build] +basepython = python3 +skip_install = true +deps = + wheel + setuptools +commands = + python setup.py bdist_wheel + +[testenv:test-release] +basepython = python3 +skip_install = true +deps = + {[testenv:build]deps} + twine +commands = + {[testenv:build]commands} + twine upload --skip-existing --repository testpypi dist/* + +[testenv:release] +basepython = python3 +skip_install = true +deps = + {[testenv:build]deps} + twine +commands = + {[testenv:build]commands} + twine upload --skip-existing --repository pypi dist/* + +# Flake8 Configuration +[flake8] +max_complexity = 10 +max_line_length = 120 +import_order_style = google +application_import_names = aws_encryption_sdk +builtins = raw_input +ignore = + # Ignoring D205 and D400 because of false positives + D205, D400, + # Ignoring D401 pending discussion of imperative mood + D401, + # Ignoring W503 : line break before binary operator + W503 + +# Doc8 Configuration +[doc8] +max-line-length = 120 \ No newline at end of file