Skip to content

Commit d46c79d

Browse files
authored
Merge pull request #81 from mattsb42-aws/dev-78
add minimum key size checks and warnings
2 parents 80465a4 + eabde32 commit d46c79d

20 files changed

+166
-96
lines changed

.travis.yml

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,45 @@ matrix:
44
include:
55
# CPython 2.7
66
- python: 2.7
7-
env: TOXENV=py27-local-slow
7+
env: TOXENV=py27-travis-local-slow
88
- python: 2.7
9-
env: TOXENV=py27-integ-slow
9+
env: TOXENV=py27-travis-integ-slow
10+
- python: 2.7
11+
env: TOXENV=py27-travis-isolation
1012
# CPython 3.4
1113
- python: 3.4
12-
env: TOXENV=py34-local-slow
14+
env: TOXENV=py34-travis-local-slow
15+
- python: 3.4
16+
env: TOXENV=py34-travis-integ-slow
1317
- python: 3.4
14-
env: TOXENV=py34-integ-slow
18+
env: TOXENV=py34-travis-isolation
1519
# CPython 3.5
1620
- python: 3.5
17-
env: TOXENV=py35-local-slow
21+
env: TOXENV=py35-travis-local-slow
1822
- python: 3.5
19-
env: TOXENV=py35-integ-slow
23+
env: TOXENV=py35-travis-integ-slow
24+
- python: 3.5
25+
env: TOXENV=py35-travis-isolation
2026
# CPython 3.6
2127
- python: 3.6
22-
env: TOXENV=py36-local-slow
28+
env: TOXENV=py36-travis-local-slow
29+
- python: 3.6
30+
env: TOXENV=py36-travis-integ-slow
2331
- python: 3.6
24-
env: TOXENV=py36-integ-slow
32+
env: TOXENV=py36-travis-isolation
2533
# CPython 3.7
2634
# xenial + sudo are currently needed to get 3.7
2735
# https://github.com/travis-ci/travis-ci/issues/9815
2836
- python: 3.7
29-
env: TOXENV=py37-local-slow
37+
env: TOXENV=py37-travis-local-slow
38+
dist: xenial
39+
sudo: true
40+
- python: 3.7
41+
env: TOXENV=py37-travis-integ-slow
3042
dist: xenial
3143
sudo: true
3244
- python: 3.7
33-
env: TOXENV=py37-integ-slow
45+
env: TOXENV=py37-travis-isolation
3446
dist: xenial
3547
sudo: true
3648
# Upstream tests

examples/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Stub to allow relative imports of examples from tests."""

examples/test/examples_test_utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""
2+
Helper utilities for use while testing examples.
3+
"""
4+
import os
5+
import sys
6+
7+
os.environ["AWS_ENCRYPTION_SDK_EXAMPLES_TESTING"] = "yes"
8+
sys.path.extend([os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"])])
9+
10+
from integration_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import

examples/test/test_aws_kms_encrypted_examples.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,10 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""Test ``aws_kms_encrypted_*`` examples."""
14-
import os
15-
import sys
16-
17-
sys.path.extend(
18-
[ # noqa
19-
os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"]),
20-
os.sep.join([os.path.dirname(__file__), "..", "src"]),
21-
]
22-
)
23-
2414
import pytest
2515

26-
import aws_kms_encrypted_client # noqa
27-
import aws_kms_encrypted_item # noqa
28-
import aws_kms_encrypted_resource # noqa
29-
import aws_kms_encrypted_table # noqa
30-
from integration_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
16+
from ..src import aws_kms_encrypted_client, aws_kms_encrypted_item, aws_kms_encrypted_resource, aws_kms_encrypted_table
17+
from .examples_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
3118

3219
pytestmark = [pytest.mark.examples]
3320

examples/test/test_most_recent_provider_encrypted_examples.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,15 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""Test most recent provider examples."""
14-
import os
15-
import sys
16-
17-
sys.path.extend(
18-
[ # noqa
19-
os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"]),
20-
os.sep.join([os.path.dirname(__file__), "..", "src"]),
21-
]
22-
)
2314
import uuid
2415

2516
import boto3
2617
import pytest
2718

2819
from dynamodb_encryption_sdk.material_providers.store.meta import MetaStore
29-
import most_recent_provider_encrypted_table # noqa
30-
from integration_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
20+
21+
from ..src import most_recent_provider_encrypted_table
22+
from .examples_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
3123

3224
pytestmark = [pytest.mark.examples]
3325

examples/test/test_wrapped_encrypted_examples.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,12 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""Test ``wrapped_*_encrypted_*`` examples."""
14-
import os
15-
import sys
16-
17-
sys.path.extend(
18-
[ # noqa
19-
os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"]),
20-
os.sep.join([os.path.dirname(__file__), "..", "src"]),
21-
]
22-
)
23-
2414
import pytest
2515

2616
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
27-
import wrapped_rsa_encrypted_table # noqa
28-
import wrapped_symmetric_encrypted_table # noqa
29-
from integration_test_utils import ddb_table_name # noqa pylint: disable=unused-import
17+
18+
from ..src import wrapped_rsa_encrypted_table, wrapped_symmetric_encrypted_table
19+
from .examples_test_utils import ddb_table_name # noqa pylint: disable=unused-import
3020

3121
pytestmark = [pytest.mark.examples]
3222

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ markers =
2727
slow: mark a test as being known to take a long time to complete (order 5s < t < 60s)
2828
veryslow: mark a test as being known to take a very long time to complete (order t > 60s)
2929
nope: mark a test as being so slow that it should only be very infrequently (order t > 30m)
30+
travis_isolation: mark a test that crashes Travis CI when run with other tests
3031
log_level=NOTSET
3132

3233
# Flake8 Configuration

src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/authentication.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from dynamodb_encryption_sdk.exceptions import InvalidAlgorithmError, SignatureVerificationError, SigningError
2929
from dynamodb_encryption_sdk.identifiers import LOGGER_NAME, EncryptionKeyType, KeyEncodingType
30+
from dynamodb_encryption_sdk.internal.identifiers import MinimumKeySizes
3031
from dynamodb_encryption_sdk.internal.validators import callable_validator
3132

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

40-
4141
__all__ = ("JavaAuthenticator", "JavaMac", "JavaSignature", "JAVA_AUTHENTICATOR")
4242
_LOGGER = logging.getLogger(LOGGER_NAME)
4343

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

141+
if len(key) * 8 < MinimumKeySizes.HMAC.value:
142+
_LOGGER.warning("HMAC keys smaller than %d bits are unsafe" % MinimumKeySizes.HMAC.value)
143+
141144
return key
142145

143146
def validate_algorithm(self, algorithm):

src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
WrappingError,
3636
)
3737
from dynamodb_encryption_sdk.identifiers import LOGGER_NAME, EncryptionKeyType, KeyEncodingType
38+
from dynamodb_encryption_sdk.internal.identifiers import MinimumKeySizes
3839
from dynamodb_encryption_sdk.internal.validators import callable_validator
3940

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

454-
return loader(**kwargs)
455+
loaded_key = loader(**kwargs)
456+
457+
if loaded_key.key_size < MinimumKeySizes.RSA.value:
458+
_LOGGER.warning("RSA keys smaller than %d bits are unsafe" % MinimumKeySizes.RSA.value)
459+
460+
return loaded_key
455461

456462

457463
_KEY_LOADERS = {rsa: load_rsa_key}

src/dynamodb_encryption_sdk/internal/identifiers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,21 @@
3232
"SignatureValues",
3333
"MaterialDescriptionKeys",
3434
"MaterialDescriptionValues",
35+
"MinimumKeySizes",
3536
)
3637

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

4142

43+
class MinimumKeySizes(Enum):
44+
"""Minimum safe key sizes for algorithms."""
45+
46+
RSA = 2048
47+
HMAC = 128
48+
49+
4250
class ReservedAttributes(Enum):
4351
"""Item attributes reserved for use by DynamoDBEncryptionClient"""
4452

test/functional/delegated_keys/test_jce.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""Functional test suite for ``dynamodb_encryption_sdk.delegated_keys.jce``."""
14+
from __future__ import division
15+
16+
import logging
17+
1418
import pytest
1519
from cryptography.hazmat.backends import default_backend
1620
from cryptography.hazmat.primitives import serialization
1721

1822
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
23+
from dynamodb_encryption_sdk.internal.crypto.jce_bridge.authentication import JAVA_AUTHENTICATOR
24+
from dynamodb_encryption_sdk.internal.identifiers import MinimumKeySizes
1925

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

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

2733
def _find_rsa_key_length(key):
2834
loaded_key = serialization.load_der_private_key(data=key, password=None, backend=default_backend())
29-
return loaded_key._key_size
35+
return loaded_key.key_size
3036

3137

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

5157
assert length_finder(test.key) == expected_bits
58+
59+
60+
def build_short_key_cases():
61+
for algorithm in JAVA_AUTHENTICATOR:
62+
if algorithm.upper().startswith("HMAC"):
63+
message = "HMAC keys smaller than {} bits are unsafe".format(MinimumKeySizes.HMAC.value)
64+
yield (algorithm, MinimumKeySizes.HMAC.value, False, message)
65+
yield (algorithm, MinimumKeySizes.HMAC.value - 1, True, message)
66+
67+
elif algorithm.upper().endswith("RSA"):
68+
message = "RSA keys smaller than {} bits are unsafe".format(MinimumKeySizes.RSA.value)
69+
yield (algorithm, MinimumKeySizes.RSA.value, False, message)
70+
yield (algorithm, MinimumKeySizes.RSA.value // 2, True, message)
71+
72+
message = "RSA keys smaller than {} bits are unsafe".format(MinimumKeySizes.RSA.value)
73+
yield ("RSA", MinimumKeySizes.RSA.value, False, message)
74+
yield ("RSA", MinimumKeySizes.RSA.value // 2, True, message)
75+
76+
77+
@pytest.mark.travis_isolation
78+
@pytest.mark.parametrize("algorithm, key_bits, too_short, error_message", build_short_key_cases())
79+
def test_warn_on_short_keys(caplog, algorithm, key_bits, too_short, error_message):
80+
with caplog.at_level(logging.DEBUG):
81+
_test = JceNameLocalDelegatedKey.generate(algorithm, key_bits)
82+
83+
logging_results = caplog.text
84+
assert (too_short and error_message in logging_results) or (not too_short and error_message not in logging_results)

test/functional/encrypted/test_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def test_ephemeral_batch_item_cycle_slow(example_table, all_the_cmps, parametriz
8080
_client_cycle_batch_items_check(all_the_cmps, parametrized_actions, parametrized_item)
8181

8282

83+
@pytest.mark.travis_isolation
8384
@pytest.mark.slow
8485
@pytest.mark.hypothesis
8586
@SLOW_SETTINGS

test/functional/encrypted/test_resource.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def test_ephemeral_batch_item_cycle(example_table, some_cmps, parametrized_actio
4040
_resource_cycle_batch_items_check(some_cmps, parametrized_actions, parametrized_item)
4141

4242

43+
@pytest.mark.travis_isolation
4344
@pytest.mark.slow
4445
def test_ephemeral_batch_item_cycle_slow(example_table, all_the_cmps, parametrized_actions, parametrized_item):
4546
"""Test ALL THE CMPS against a small number of curated items."""

test/functional/encrypted/test_table.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def test_ephemeral_item_cycle_batch_writer_slow(example_table, all_the_cmps, par
6060
table_cycle_batch_writer_check(all_the_cmps, parametrized_actions, parametrized_item, TEST_TABLE_NAME, "us-west-2")
6161

6262

63+
@pytest.mark.travis_isolation
6364
@pytest.mark.slow
6465
@pytest.mark.hypothesis
6566
@SLOW_SETTINGS

test/functional/functional_test_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515

1616
import copy
1717
import itertools
18+
import logging
19+
import os
1820
from collections import defaultdict
1921
from decimal import Decimal
2022

2123
import boto3
2224
import pytest
25+
import six
2326
from boto3.dynamodb.types import Binary
2427
from moto import mock_dynamodb2
2528

@@ -36,6 +39,7 @@
3639
from dynamodb_encryption_sdk.structures import AttributeActions
3740
from dynamodb_encryption_sdk.transform import ddb_to_dict, dict_to_ddb
3841

42+
RUNNING_IN_TRAVIS = "TRAVIS" in os.environ
3943
_DELEGATED_KEY_CACHE = defaultdict(lambda: defaultdict(dict))
4044
TEST_TABLE_NAME = "my_table"
4145
TEST_INDEX = {
@@ -44,7 +48,7 @@
4448
}
4549
SECONARY_INDEX = {
4650
"secondary_index_1": {"type": "B", "value": Binary(b"\x00\x01\x02")},
47-
"secondary_index_1": {"type": "S", "value": "another_value"},
51+
"secondary_index_2": {"type": "S", "value": "another_value"},
4852
}
4953
TEST_KEY = {name: value["value"] for name, value in TEST_INDEX.items()}
5054
TEST_BATCH_INDEXES = [

test/integration/integration_test_utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider
1919

2020
# convenience imports
21-
from ..functional import functional_test_utils, hypothesis_strategies
21+
try:
22+
from ..functional import functional_test_utils, hypothesis_strategies
23+
except (ImportError, ValueError, SystemError):
24+
if "AWS_ENCRYPTION_SDK_EXAMPLES_TESTING" not in os.environ:
25+
raise
2226

2327
AWS_KMS_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID"
2428
DDB_TABLE_NAME = "DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME"
@@ -52,7 +56,7 @@ def ddb_table_name():
5256
except KeyError:
5357
raise ValueError(
5458
(
55-
'Environment variable "{}" must be set to the correct DynamoDB table name'
59+
"Environment variable '{}' must be set to the correct DynamoDB table name"
5660
" for integration tests to run"
57-
).format(AWS_KMS_KEY_ID)
61+
).format(DDB_TABLE_NAME)
5862
)

test/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
hypothesis==3.63.0
22
mock
33
moto>=1.3.3
4-
pytest>=3.3.1
4+
pytest>=3.4.0
55
pytest-cov
66
pytest-mock
77
pytest-xdist

0 commit comments

Comments
 (0)