Skip to content

add minimum key size checks and warnings #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,45 @@ matrix:
include:
# CPython 2.7
- python: 2.7
env: TOXENV=py27-local-slow
env: TOXENV=py27-travis-local-slow
- python: 2.7
env: TOXENV=py27-integ-slow
env: TOXENV=py27-travis-integ-slow
- python: 2.7
env: TOXENV=py27-travis-isolation
# CPython 3.4
- python: 3.4
env: TOXENV=py34-local-slow
env: TOXENV=py34-travis-local-slow
- python: 3.4
env: TOXENV=py34-travis-integ-slow
- python: 3.4
env: TOXENV=py34-integ-slow
env: TOXENV=py34-travis-isolation
# CPython 3.5
- python: 3.5
env: TOXENV=py35-local-slow
env: TOXENV=py35-travis-local-slow
- python: 3.5
env: TOXENV=py35-integ-slow
env: TOXENV=py35-travis-integ-slow
- python: 3.5
env: TOXENV=py35-travis-isolation
# CPython 3.6
- python: 3.6
env: TOXENV=py36-local-slow
env: TOXENV=py36-travis-local-slow
- python: 3.6
env: TOXENV=py36-travis-integ-slow
- python: 3.6
env: TOXENV=py36-integ-slow
env: TOXENV=py36-travis-isolation
# CPython 3.7
# xenial + sudo are currently needed to get 3.7
# https://github.com/travis-ci/travis-ci/issues/9815
- python: 3.7
env: TOXENV=py37-local-slow
env: TOXENV=py37-travis-local-slow
dist: xenial
sudo: true
- python: 3.7
env: TOXENV=py37-travis-integ-slow
dist: xenial
sudo: true
- python: 3.7
env: TOXENV=py37-integ-slow
env: TOXENV=py37-travis-isolation
dist: xenial
sudo: true
# Upstream tests
Expand Down
13 changes: 13 additions & 0 deletions examples/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2018 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 to allow relative imports of examples from tests."""
10 changes: 10 additions & 0 deletions examples/test/examples_test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Helper utilities for use while testing examples.
"""
import os
import sys

os.environ["AWS_ENCRYPTION_SDK_EXAMPLES_TESTING"] = "yes"
sys.path.extend([os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"])])

from integration_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
17 changes: 2 additions & 15 deletions examples/test/test_aws_kms_encrypted_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,10 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Test ``aws_kms_encrypted_*`` examples."""
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 pytest

import aws_kms_encrypted_client # noqa
import aws_kms_encrypted_item # noqa
import aws_kms_encrypted_resource # noqa
import aws_kms_encrypted_table # noqa
from integration_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
from ..src import aws_kms_encrypted_client, aws_kms_encrypted_item, aws_kms_encrypted_resource, aws_kms_encrypted_table
from .examples_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import

pytestmark = [pytest.mark.examples]

Expand Down
14 changes: 3 additions & 11 deletions examples/test/test_most_recent_provider_encrypted_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,15 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Test most recent provider examples."""
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 uuid

import boto3
import pytest

from dynamodb_encryption_sdk.material_providers.store.meta import MetaStore
import most_recent_provider_encrypted_table # noqa
from integration_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import

from ..src import most_recent_provider_encrypted_table
from .examples_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import

pytestmark = [pytest.mark.examples]

Expand Down
16 changes: 3 additions & 13 deletions examples/test/test_wrapped_encrypted_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,12 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Test ``wrapped_*_encrypted_*`` examples."""
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 pytest

from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
import wrapped_rsa_encrypted_table # noqa
import wrapped_symmetric_encrypted_table # noqa
from integration_test_utils import ddb_table_name # noqa pylint: disable=unused-import

from ..src import wrapped_rsa_encrypted_table, wrapped_symmetric_encrypted_table
from .examples_test_utils import ddb_table_name # noqa pylint: disable=unused-import

pytestmark = [pytest.mark.examples]

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ markers =
slow: mark a test as being known to take a long time to complete (order 5s < t < 60s)
veryslow: mark a test as being known to take a very long time to complete (order t > 60s)
nope: mark a test as being so slow that it should only be very infrequently (order t > 30m)
travis_isolation: mark a test that crashes Travis CI when run with other tests
log_level=NOTSET

# Flake8 Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from dynamodb_encryption_sdk.exceptions import InvalidAlgorithmError, SignatureVerificationError, SigningError
from dynamodb_encryption_sdk.identifiers import LOGGER_NAME, EncryptionKeyType, KeyEncodingType
from dynamodb_encryption_sdk.internal.identifiers import MinimumKeySizes
from dynamodb_encryption_sdk.internal.validators import callable_validator

from .primitives import load_rsa_key
Expand All @@ -37,7 +38,6 @@
# We only actually need these imports when running the mypy checks
pass


__all__ = ("JavaAuthenticator", "JavaMac", "JavaSignature", "JAVA_AUTHENTICATOR")
_LOGGER = logging.getLogger(LOGGER_NAME)

Expand Down Expand Up @@ -138,6 +138,9 @@ def load_key(self, key, key_type, key_encoding):
if not (key_type is EncryptionKeyType.SYMMETRIC and key_encoding is KeyEncodingType.RAW):
raise ValueError("Key type must be symmetric and encoding must be raw.")

if len(key) * 8 < MinimumKeySizes.HMAC.value:
_LOGGER.warning("HMAC keys smaller than %d bits are unsafe" % MinimumKeySizes.HMAC.value)

return key

def validate_algorithm(self, algorithm):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
WrappingError,
)
from dynamodb_encryption_sdk.identifiers import LOGGER_NAME, EncryptionKeyType, KeyEncodingType
from dynamodb_encryption_sdk.internal.identifiers import MinimumKeySizes
from dynamodb_encryption_sdk.internal.validators import callable_validator

try: # Python 3.5.0 and 3.5.1 have incompatible typing modules
Expand Down Expand Up @@ -451,7 +452,12 @@ def load_rsa_key(key, key_type, key_encoding):
if key_type is EncryptionKeyType.PRIVATE:
kwargs["password"] = None

return loader(**kwargs)
loaded_key = loader(**kwargs)

if loaded_key.key_size < MinimumKeySizes.RSA.value:
_LOGGER.warning("RSA keys smaller than %d bits are unsafe" % MinimumKeySizes.RSA.value)

return loaded_key


_KEY_LOADERS = {rsa: load_rsa_key}
Expand Down
8 changes: 8 additions & 0 deletions src/dynamodb_encryption_sdk/internal/identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,21 @@
"SignatureValues",
"MaterialDescriptionKeys",
"MaterialDescriptionValues",
"MinimumKeySizes",
)

#: Encoding to use for all text values.
#: This is noted here for consistency but should not be changed.
TEXT_ENCODING = "utf-8"


class MinimumKeySizes(Enum):
"""Minimum safe key sizes for algorithms."""

RSA = 2048
HMAC = 128


class ReservedAttributes(Enum):
"""Item attributes reserved for use by DynamoDBEncryptionClient"""

Expand Down
35 changes: 34 additions & 1 deletion test/functional/delegated_keys/test_jce.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Functional test suite for ``dynamodb_encryption_sdk.delegated_keys.jce``."""
from __future__ import division

import logging

import pytest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
from dynamodb_encryption_sdk.internal.crypto.jce_bridge.authentication import JAVA_AUTHENTICATOR
from dynamodb_encryption_sdk.internal.identifiers import MinimumKeySizes

pytestmark = [pytest.mark.functional, pytest.mark.local]

Expand All @@ -26,7 +32,7 @@ def _find_aes_key_length(key):

def _find_rsa_key_length(key):
loaded_key = serialization.load_der_private_key(data=key, password=None, backend=default_backend())
return loaded_key._key_size
return loaded_key.key_size


@pytest.mark.parametrize(
Expand All @@ -49,3 +55,30 @@ def test_generate_correct_key_length(algorithm, requested_bits, expected_bits, l
test = JceNameLocalDelegatedKey.generate(algorithm, requested_bits)

assert length_finder(test.key) == expected_bits


def build_short_key_cases():
for algorithm in JAVA_AUTHENTICATOR:
if algorithm.upper().startswith("HMAC"):
message = "HMAC keys smaller than {} bits are unsafe".format(MinimumKeySizes.HMAC.value)
yield (algorithm, MinimumKeySizes.HMAC.value, False, message)
yield (algorithm, MinimumKeySizes.HMAC.value - 1, True, message)

elif algorithm.upper().endswith("RSA"):
message = "RSA keys smaller than {} bits are unsafe".format(MinimumKeySizes.RSA.value)
yield (algorithm, MinimumKeySizes.RSA.value, False, message)
yield (algorithm, MinimumKeySizes.RSA.value // 2, True, message)

message = "RSA keys smaller than {} bits are unsafe".format(MinimumKeySizes.RSA.value)
yield ("RSA", MinimumKeySizes.RSA.value, False, message)
yield ("RSA", MinimumKeySizes.RSA.value // 2, True, message)


@pytest.mark.travis_isolation
@pytest.mark.parametrize("algorithm, key_bits, too_short, error_message", build_short_key_cases())
def test_warn_on_short_keys(caplog, algorithm, key_bits, too_short, error_message):
with caplog.at_level(logging.DEBUG):
_test = JceNameLocalDelegatedKey.generate(algorithm, key_bits)

logging_results = caplog.text
assert (too_short and error_message in logging_results) or (not too_short and error_message not in logging_results)
1 change: 1 addition & 0 deletions test/functional/encrypted/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def test_ephemeral_batch_item_cycle_slow(example_table, all_the_cmps, parametriz
_client_cycle_batch_items_check(all_the_cmps, parametrized_actions, parametrized_item)


@pytest.mark.travis_isolation
@pytest.mark.slow
@pytest.mark.hypothesis
@SLOW_SETTINGS
Expand Down
1 change: 1 addition & 0 deletions test/functional/encrypted/test_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def test_ephemeral_batch_item_cycle(example_table, some_cmps, parametrized_actio
_resource_cycle_batch_items_check(some_cmps, parametrized_actions, parametrized_item)


@pytest.mark.travis_isolation
@pytest.mark.slow
def test_ephemeral_batch_item_cycle_slow(example_table, all_the_cmps, parametrized_actions, parametrized_item):
"""Test ALL THE CMPS against a small number of curated items."""
Expand Down
1 change: 1 addition & 0 deletions test/functional/encrypted/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def test_ephemeral_item_cycle_batch_writer_slow(example_table, all_the_cmps, par
table_cycle_batch_writer_check(all_the_cmps, parametrized_actions, parametrized_item, TEST_TABLE_NAME, "us-west-2")


@pytest.mark.travis_isolation
@pytest.mark.slow
@pytest.mark.hypothesis
@SLOW_SETTINGS
Expand Down
6 changes: 5 additions & 1 deletion test/functional/functional_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@

import copy
import itertools
import logging
import os
from collections import defaultdict
from decimal import Decimal

import boto3
import pytest
import six
from boto3.dynamodb.types import Binary
from moto import mock_dynamodb2

Expand All @@ -36,6 +39,7 @@
from dynamodb_encryption_sdk.structures import AttributeActions
from dynamodb_encryption_sdk.transform import ddb_to_dict, dict_to_ddb

RUNNING_IN_TRAVIS = "TRAVIS" in os.environ
_DELEGATED_KEY_CACHE = defaultdict(lambda: defaultdict(dict))
TEST_TABLE_NAME = "my_table"
TEST_INDEX = {
Expand All @@ -44,7 +48,7 @@
}
SECONARY_INDEX = {
"secondary_index_1": {"type": "B", "value": Binary(b"\x00\x01\x02")},
"secondary_index_1": {"type": "S", "value": "another_value"},
"secondary_index_2": {"type": "S", "value": "another_value"},
}
TEST_KEY = {name: value["value"] for name, value in TEST_INDEX.items()}
TEST_BATCH_INDEXES = [
Expand Down
10 changes: 7 additions & 3 deletions test/integration/integration_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider

# convenience imports
from ..functional import functional_test_utils, hypothesis_strategies
try:
from ..functional import functional_test_utils, hypothesis_strategies
except (ImportError, ValueError, SystemError):
if "AWS_ENCRYPTION_SDK_EXAMPLES_TESTING" not in os.environ:
raise

AWS_KMS_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID"
DDB_TABLE_NAME = "DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME"
Expand Down Expand Up @@ -52,7 +56,7 @@ def ddb_table_name():
except KeyError:
raise ValueError(
(
'Environment variable "{}" must be set to the correct DynamoDB table name'
"Environment variable '{}' must be set to the correct DynamoDB table name"
" for integration tests to run"
).format(AWS_KMS_KEY_ID)
).format(DDB_TABLE_NAME)
)
2 changes: 1 addition & 1 deletion test/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
hypothesis==3.63.0
mock
moto>=1.3.3
pytest>=3.3.1
pytest>=3.4.0
pytest-cov
pytest-mock
pytest-xdist
Expand Down
Loading