diff --git a/decrypt_oracle/requirements-actual.txt b/decrypt_oracle/requirements-actual.txt index b1a640978..c2f4d6d6c 100644 --- a/decrypt_oracle/requirements-actual.txt +++ b/decrypt_oracle/requirements-actual.txt @@ -1,3 +1,4 @@ # Requirements for actual package chalice aws-encryption-sdk~=1.7.1 +cryptography>=2.5.0,<3.3.2 diff --git a/dev_requirements/linter-requirements.txt b/dev_requirements/linter-requirements.txt index 6cec339ff..6f22ca043 100644 --- a/dev_requirements/linter-requirements.txt +++ b/dev_requirements/linter-requirements.txt @@ -1,14 +1,14 @@ -bandit==1.7.0 -black==21.12b0 +bandit==1.7.4 +black==22.3.0 doc8==0.10.1 flake8==4.0.1 -flake8-bugbear==21.11.29 +flake8-bugbear==22.1.11 flake8-docstrings==1.6.0 flake8-print==4.0.0 isort==5.10.1 pyflakes==2.4.0 -pylint==2.12.2 -readme_renderer==32.0 +pylint==2.13.5 +readme_renderer==34.0 seed-isort-config==2.2.0 vulture==2.3 sphinx==4.4.0 diff --git a/dev_requirements/release-requirements.txt b/dev_requirements/release-requirements.txt new file mode 100644 index 000000000..1b1d710ca --- /dev/null +++ b/dev_requirements/release-requirements.txt @@ -0,0 +1,4 @@ +pypi-parker==0.1.2 +setuptools==62.0.0 +twine==3.8.0 +wheel==0.37.1 diff --git a/requirements.txt b/requirements.txt index fab293c05..beb06476c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ boto3>=1.10.0 -cryptography>=2.5.0 +cryptography>=2.5.0,<=3.3.2 attrs>=17.4.0 wrapt>=1.10.11 diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index 560fdb2a2..ef60f34ea 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -18,7 +18,8 @@ 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 aws_encryption_sdk.internal.utils import verify_ec_interface from ...exceptions import NotSupportedError from .elliptic_curve import ( @@ -47,11 +48,9 @@ def __init__(self, algorithm, key): 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: + if not verify_ec_interface(self.algorithm): raise NotSupportedError("Unsupported signing algorithm info") + return ec.EllipticCurve def _build_hasher(self): """Builds the hasher instance which will calculate the digest of all passed data. diff --git a/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py b/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py index fd96abf6f..330d9b2a5 100644 --- a/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py +++ b/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py @@ -18,7 +18,9 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import Prehashed, decode_dss_signature, encode_dss_signature -from cryptography.utils import InterfaceNotImplemented, int_from_bytes, int_to_bytes, verify_interface +from cryptography.utils import int_from_bytes, int_to_bytes + +from aws_encryption_sdk.internal.utils import verify_ec_interface from ...exceptions import NotSupportedError from ..str_ops import to_bytes @@ -183,8 +185,6 @@ def generate_ecc_signing_key(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: + if not verify_ec_interface(algorithm): raise NotSupportedError("Unsupported signing algorithm info") + return ec.generate_private_key(curve=algorithm.signing_algorithm_info(), backend=default_backend()) diff --git a/src/aws_encryption_sdk/internal/utils/__init__.py b/src/aws_encryption_sdk/internal/utils/__init__.py index 1e7400c3a..4b1256e3a 100644 --- a/src/aws_encryption_sdk/internal/utils/__init__.py +++ b/src/aws_encryption_sdk/internal/utils/__init__.py @@ -16,6 +16,7 @@ import os import six +from cryptography.hazmat.primitives.asymmetric import ec import aws_encryption_sdk.internal.defaults from aws_encryption_sdk.exceptions import InvalidDataKeyError, SerializationError, UnknownIdentityError @@ -161,3 +162,17 @@ def source_data_key_length_check(source_data_key, algorithm): actual=len(source_data_key.data_key), required=algorithm.kdf_input_len ) ) + + +def verify_ec_interface(algorithm): + """Validates that the provided EllipticCurve Algorithm is correctly implemented by + checking if it implements both of the abstract methods from the EllipticCurve + abstract class. + + :param algorithm: Algorithm object which directs which Elliptic Curve will be used + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + """ + for method in ec.EllipticCurve.__abstractmethods__: + if not hasattr(algorithm.signing_algorithm_info(), method): + return False + return True diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py index eae064130..63d0ab3d1 100644 --- a/test/unit/test_crypto_authentication_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -31,6 +31,12 @@ def patch_default_backend(mocker): @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.fixture def patch_serialization(mocker): mocker.patch.object(aws_encryption_sdk.internal.crypto.authentication, "serialization") yield aws_encryption_sdk.internal.crypto.authentication.serialization @@ -71,8 +77,13 @@ def test_f_signer_key_bytes(): 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() +def test_signer_from_key_bytes(patch_default_backend, patch_serialization, patch_build_hasher, patch_ec): + patch_ec.EllipticCurve.__abstractmethods__ = set() + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + mock_algorithm_info.return_value.name = True + mock_algorithm_info.return_value.key_size = True + _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + signer = Signer.from_key_bytes(algorithm=_algorithm, key_bytes=sentinel.key_bytes) patch_serialization.load_der_private_key.assert_called_once_with( @@ -83,9 +94,12 @@ def test_signer_from_key_bytes(patch_default_backend, patch_serialization, patch 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): +def test_signer_key_bytes(patch_default_backend, patch_serialization, patch_build_hasher, patch_ec): + patch_ec.EllipticCurve.__abstractmethods__ = set() + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) private_key = MagicMock() - signer = Signer(MagicMock(), key=private_key) + signer = Signer(algorithm, key=private_key) test = signer.key_bytes() @@ -98,13 +112,23 @@ def test_signer_key_bytes(patch_default_backend, patch_serialization, patch_buil def test_signer_encoded_public_key( - patch_default_backend, patch_serialization, patch_build_hasher, patch_ecc_encode_compressed_point, patch_base64 + patch_default_backend, + patch_serialization, + patch_build_hasher, + patch_ecc_encode_compressed_point, + patch_base64, + patch_ec ): 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) + patch_ec.EllipticCurve.__abstractmethods__ = set() + + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + + signer = Signer(algorithm, key=private_key) test_key = signer.encoded_public_key() patch_ecc_encode_compressed_point.assert_called_once_with(private_key) @@ -112,16 +136,21 @@ def test_signer_encoded_public_key( assert test_key == sentinel.encoded_point -def test_signer_update(patch_default_backend, patch_serialization, patch_build_hasher): - signer = Signer(MagicMock(), key=MagicMock()) +def test_signer_update(patch_default_backend, patch_serialization, patch_build_hasher, patch_ec): + patch_ec.EllipticCurve.__abstractmethods__ = set() + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + signer = Signer(algorithm, 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 + patch_default_backend, patch_serialization, patch_build_hasher, patch_ecc_static_length_signature, patch_ec ): - algorithm = MagicMock() + patch_ec.EllipticCurve.__abstractmethods__ = set() + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) private_key = MagicMock() signer = Signer(algorithm, key=private_key) diff --git a/test/unit/test_crypto_authentication_verifier.py b/test/unit/test_crypto_authentication_verifier.py index a55e8f517..fe52616ed 100644 --- a/test/unit/test_crypto_authentication_verifier.py +++ b/test/unit/test_crypto_authentication_verifier.py @@ -89,17 +89,22 @@ def test_verifier_from_encoded_point( patch_ecc_public_numbers_from_compressed_point, patch_base64, patch_build_hasher, + patch_ec ): 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() + + patch_ec.EllipticCurve.__abstractmethods__ = set() + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + mock_algorithm_info.return_value.name = True + mock_algorithm_info.return_value.key_size = True + algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) 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 ) diff --git a/test/unit/test_crypto_elliptic_curve.py b/test/unit/test_crypto_elliptic_curve.py index d1c627dc0..f934d2a44 100644 --- a/test/unit/test_crypto_elliptic_curve.py +++ b/test/unit/test_crypto_elliptic_curve.py @@ -15,11 +15,11 @@ import pytest from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.utils import InterfaceNotImplemented from mock import MagicMock, sentinel from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.internal.crypto.elliptic_curve +import aws_encryption_sdk.internal.defaults from aws_encryption_sdk.exceptions import NotSupportedError from aws_encryption_sdk.internal.crypto.elliptic_curve import ( _ECC_CURVE_PARAMETERS, @@ -72,12 +72,6 @@ def patch_ecc_decode_compressed_point(mocker): 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") @@ -374,23 +368,27 @@ def test_ecc_public_numbers_from_compressed_point(patch_ec, patch_ecc_decode_com assert test == sentinel.public_numbers_instance -def test_generate_ecc_signing_key_supported(patch_default_backend, patch_ec, patch_verify_interface): +def test_generate_ecc_signing_key_supported(patch_default_backend, patch_ec): patch_ec.generate_private_key.return_value = sentinel.raw_signing_key - mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info) + patch_ec.EllipticCurve.__abstractmethods__ = {"key_size", "name"} + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + mock_algorithm_info.return_value.name = MagicMock(return_value=True) + mock_algorithm_info.return_value.key_size = MagicMock(return_value=True) 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) +def test_generate_ecc_signing_key_unsupported(patch_default_backend, patch_ec): + patch_ec.generate_private_key.return_value = sentinel.raw_signing_key + mock_algorithm_info = MagicMock(return_value=sentinel.invalid_algorithm_info, spec=patch_ec.EllipticCurve) + mock_algorithm_info.return_value.not_name = MagicMock(return_value=True) + mock_algorithm_info.return_value.not_key_size = MagicMock(return_value=True) mock_algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) with pytest.raises(NotSupportedError) as excinfo: diff --git a/test/unit/test_crypto_prehashing_authenticator.py b/test/unit/test_crypto_prehashing_authenticator.py index 48bdb1220..76ae8ceb2 100644 --- a/test/unit/test_crypto_prehashing_authenticator.py +++ b/test/unit/test_crypto_prehashing_authenticator.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. """Unit test suite for ``aws_encryption_sdk.internal.crypto._PrehashingAuthenticater``.""" import pytest -from cryptography.utils import InterfaceNotImplemented from mock import MagicMock, sentinel from pytest_mock import mocker # noqa pylint: disable=unused-import @@ -35,12 +34,6 @@ def patch_build_hasher(mocker): 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") @@ -71,23 +64,28 @@ def test_init(patch_set_signature_type, patch_build_hasher): def test_set_signature_type_elliptic_curve( - patch_build_hasher, patch_cryptography_utils_verify_interface, patch_cryptography_ec + patch_build_hasher, patch_cryptography_ec ): - mock_algorithm = MagicMock() + patch_cryptography_ec.generate_private_key.return_value = sentinel.raw_signing_key + patch_cryptography_ec.EllipticCurve.__abstractmethods__ = set() + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_cryptography_ec.EllipticCurve) + mock_algorithm_info.return_value.name = MagicMock(return_value=True) + mock_algorithm_info.return_value.key_size = MagicMock(return_value=True) + mock_algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) 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_build_hasher, patch_cryptography_ec ): - patch_cryptography_utils_verify_interface.side_effect = InterfaceNotImplemented + mock_algorithm_info = MagicMock(return_value=sentinel.not_algorithm_info, spec=patch_cryptography_ec.EllipticCurve) + mock_algorithm_info.return_value.not_name = MagicMock(return_value=True) + mock_algorithm_info.return_value.not_key_size = MagicMock(return_value=True) + mock_algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) with pytest.raises(NotSupportedError) as excinfo: - _PrehashingAuthenticator(algorithm=MagicMock(), key=sentinel.key) + _PrehashingAuthenticator(algorithm=mock_algorithm, key=sentinel.key) excinfo.match(r"Unsupported signing algorithm info") diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index 0f2960e07..345ffea32 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -21,6 +21,7 @@ import aws_encryption_sdk.internal.utils from aws_encryption_sdk.exceptions import InvalidDataKeyError, SerializationError, UnknownIdentityError from aws_encryption_sdk.internal.defaults import MAX_FRAME_SIZE, MESSAGE_ID_LENGTH +from aws_encryption_sdk.internal.utils import verify_ec_interface from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey from .test_values import VALUES @@ -29,12 +30,30 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] +@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 + + def test_prep_stream_data_passthrough(): test = aws_encryption_sdk.internal.utils.prep_stream_data(io.BytesIO(b"some data")) assert_prepped_stream_identity(test, io.BytesIO) +def test_verify_ec_interface(patch_ec): + patch_ec.EllipticCurve.__abstractmethods__ = set(("key_size", "name")) + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + mock_algorithm_info.return_value.name = True + mock_algorithm_info.return_value.key_size = True + _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + + implemented = verify_ec_interface(algorithm=_algorithm) + + assert implemented is True + + @pytest.mark.parametrize("source", (u"some unicode data ловие", b"\x00\x01\x02")) def test_prep_stream_data_wrap(source): test = aws_encryption_sdk.internal.utils.prep_stream_data(source) diff --git a/test_vector_handlers/tox.ini b/test_vector_handlers/tox.ini index 8aebe09da..b9caa5d12 100644 --- a/test_vector_handlers/tox.ini +++ b/test_vector_handlers/tox.ini @@ -117,12 +117,7 @@ commands = # Linters [testenv:flake8] basepython = python3 -deps = - .. - flake8 - flake8-docstrings - # https://github.com/JBKahn/flake8-print/pull/30 - flake8-print>=3.1.0 +deps = -r../dev_requirements/linter-requirements.txt commands = flake8 \ src/awses_test_vectors/ \ @@ -132,15 +127,13 @@ commands = [testenv:flake8-tests] basepython = {[testenv:flake8]basepython} -deps = - .. - flake8 +deps = -r../dev_requirements/linter-requirements.txt commands = flake8 \ # Ignore F811 redefinition errors in tests (breaks with pytest-mock use) # E203 is not PEP8 compliant https://github.com/ambv/black#slices # W503 is not PEP8 compliant https://github.com/ambv/black#line-breaks--binary-operators - --ignore F811,E203,W503 \ + --ignore F811,E203,W503,D \ test/ \ {posargs} @@ -148,9 +141,7 @@ commands = basepython = python3 deps = -rtest/requirements.txt - .. - pyflakes - pylint + -r../dev_requirements/linter-requirements.txt commands = pylint \ --rcfile=src/pylintrc \ @@ -169,8 +160,7 @@ commands = [testenv:blacken-src] basepython = python3 -deps = - black +deps = -r../dev_requirements/linter-requirements.txt commands = black --line-length 120 \ src/awses_test_vectors/ \ @@ -196,14 +186,12 @@ commands = [testenv:isort-seed] basepython = python3 -deps = seed-isort-config +deps = -r../dev_requirements/linter-requirements.txt commands = seed-isort-config [testenv:isort] basepython = python3 -deps = - isort - .. +deps = -r../dev_requirements/linter-requirements.txt commands = isort -rc \ src \ test \ @@ -228,29 +216,23 @@ commands = [testenv:doc8] basepython = python3 -deps = - sphinx - doc8 +deps = -r../dev_requirements/linter-requirements.txt commands = doc8 doc/index.rst README.rst CHANGELOG.rst [testenv:readme] basepython = python3 -deps = - .. - readme_renderer +deps = -r../dev_requirements/linter-requirements.txt commands = python setup.py check -r -s [testenv:bandit] basepython = python3 -deps = - .. - bandit>=1.5.1 +deps = -r../dev_requirements/linter-requirements.txt commands = bandit -r src/awses_test_vectors/ # Prone to false positives: only run independently [testenv:vulture] basepython = python3 -deps = vulture +deps = -r../dev_requirements/linter-requirements.txt commands = vulture src/awses_test_vectors/ [testenv:linters] @@ -296,9 +278,7 @@ commands = [testenv:park] basepython = python3 skip_install = true -deps = - pypi-parker - setuptools +deps = -r../dev_requirements/release-requirements.txt commands = python setup.py park [testenv:build] @@ -306,8 +286,7 @@ basepython = python3 skip_install = true deps = {[testenv:docs]deps} - wheel - setuptools + -r../dev_requirements/release-requirements.txt commands = {[testenv:docs]commands} python setup.py sdist bdist_wheel