diff --git a/src/dynamodb_encryption_sdk/__init__.py b/src/dynamodb_encryption_sdk/__init__.py index a7c66779..82a7c208 100644 --- a/src/dynamodb_encryption_sdk/__init__.py +++ b/src/dynamodb_encryption_sdk/__init__.py @@ -10,19 +10,14 @@ # 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. -"""""" +"""DynamoDB Encryption Client.""" +from dynamodb_encryption_sdk.encrypted.client import EncryptedClient from dynamodb_encryption_sdk.encrypted.item import ( decrypt_dynamodb_item, decrypt_python_item, encrypt_dynamodb_item, encrypt_python_item ) - -# encrypt_item -# encrypt_raw_item -# decrypt_item -# decrypt_raw_item -# EncryptedTable -# EncryptedResource -# EncryptedClient +from dynamodb_encryption_sdk.encrypted.resource import EncryptedResource +from dynamodb_encryption_sdk.encrypted.table import EncryptedTable # TableConfiguration # MaterialDescription @@ -30,5 +25,6 @@ __all__ = ( 'decrypt_dynamodb_item', 'decrypt_python_item', - 'encrypt_dynamodb_item', 'encrypt_python_item' + 'encrypt_dynamodb_item', 'encrypt_python_item', + 'EncryptedClient', 'EncryptedResource', 'EncryptedTable' ) diff --git a/src/dynamodb_encryption_sdk/delegated_keys/__init__.py b/src/dynamodb_encryption_sdk/delegated_keys/__init__.py index 4b27071f..9d750fef 100644 --- a/src/dynamodb_encryption_sdk/delegated_keys/__init__.py +++ b/src/dynamodb_encryption_sdk/delegated_keys/__init__.py @@ -13,18 +13,27 @@ """Delegated keys.""" import abc try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Dict, Text # pylint: disable=unused-import + from typing import Dict, Text # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass import six -from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes +from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes # noqa pylint: disable=unused-import __all__ = ('DelegatedKey',) +def _raise_not_implemented(method_name): + """Raises a standardized ``NotImplementedError`` to report that the specified method + is not supported. + + :raises NotImplementedError: when called + """ + raise NotImplementedError('"{}" is not supported by this DelegatedKey'.format(method_name)) + + @six.add_metaclass(abc.ABCMeta) class DelegatedKey(object): """Delegated keys are black boxes that encrypt, decrypt, sign, and verify data and wrap @@ -33,6 +42,7 @@ class DelegatedKey(object): Unless overridden by a subclass, any method that a delegated key does not implement raises a ``NotImplementedError`` detailing this. """ + #: Most delegated keys should not be used with RawCryptographicMaterials. allowed_for_raw_materials = False @@ -41,16 +51,10 @@ def algorithm(self): # type: () -> Text """Text description of algorithm used by this delegated key.""" - def _raise_not_implemented(self, method_name): - """Raises a standardized ``NotImplementedError`` to report that the specified method - is not supported. - - :raises NotImplementedError: when called - """ - raise NotImplementedError('"{}" is not supported by this DelegatedKey'.format(method_name)) - @classmethod def generate(cls, algorithm, key_length): + # type: (Text, int) -> None + # pylint: disable=unused-argument,no-self-use """Generate an instance of this DelegatedKey using the specified algorithm and key length. :param str algorithm: Text description of algorithm to be used @@ -58,10 +62,11 @@ def generate(cls, algorithm, key_length): :returns: Generated delegated key :rtype: dynamodb_encryption_sdk.delegated_keys.DelegatedKey """ - cls._raise_not_implemented('generate') + _raise_not_implemented('generate') def encrypt(self, algorithm, name, plaintext, additional_associated_data=None): # type: (Text, Text, bytes, Dict[Text, Text]) -> bytes + # pylint: disable=unused-argument,no-self-use """Encrypt data. :param str algorithm: Text description of algorithm to use to encrypt data @@ -72,10 +77,11 @@ def encrypt(self, algorithm, name, plaintext, additional_associated_data=None): :returns: Encrypted ciphertext :rtype: bytes """ - self._raise_not_implemented('encrypt') + _raise_not_implemented('encrypt') def decrypt(self, algorithm, name, ciphertext, additional_associated_data=None): # type: (Text, Text, bytes, Dict[Text, Text]) -> bytes + # pylint: disable=unused-argument,no-self-use """Encrypt data. :param str algorithm: Text description of algorithm to use to decrypt data @@ -86,10 +92,11 @@ def decrypt(self, algorithm, name, ciphertext, additional_associated_data=None): :returns: Decrypted plaintext :rtype: bytes """ - self._raise_not_implemented('decrypt') + _raise_not_implemented('decrypt') def wrap(self, algorithm, content_key, additional_associated_data=None): # type: (Text, bytes, Dict[Text, Text]) -> bytes + # pylint: disable=unused-argument,no-self-use """Wrap content key. :param str algorithm: Text description of algorithm to use to wrap key @@ -99,10 +106,11 @@ def wrap(self, algorithm, content_key, additional_associated_data=None): :returns: Wrapped key :rtype: bytes """ - self._raise_not_implemented('wrap') + _raise_not_implemented('wrap') def unwrap(self, algorithm, wrapped_key, wrapped_key_algorithm, wrapped_key_type, additional_associated_data=None): # type: (Text, bytes, Text, EncryptionKeyTypes, Dict[Text, Text]) -> DelegatedKey + # pylint: disable=unused-argument,no-self-use """Wrap content key. :param str algorithm: Text description of algorithm to use to unwrap key @@ -115,10 +123,11 @@ def unwrap(self, algorithm, wrapped_key, wrapped_key_algorithm, wrapped_key_type :returns: Delegated key using unwrapped key :rtype: dynamodb_encryption_sdk.delegated_keys.DelegatedKey """ - self._raise_not_implemented('unwrap') + _raise_not_implemented('unwrap') def sign(self, algorithm, data): # type: (Text, bytes) -> bytes + # pylint: disable=unused-argument,no-self-use """Sign data. :param str algorithm: Text description of algorithm to use to sign data @@ -126,20 +135,22 @@ def sign(self, algorithm, data): :returns: Signature value :rtype: bytes """ - self._raise_not_implemented('sign') + _raise_not_implemented('sign') def verify(self, algorithm, signature, data): # type: (Text, bytes, bytes) -> None + # pylint: disable=unused-argument,no-self-use """Sign data. :param str algorithm: Text description of algorithm to use to verify signature :param bytes signature: Signature to verify :param bytes data: Data over which to verify signature """ - self._raise_not_implemented('verify') + _raise_not_implemented('verify') def signing_algorithm(self): # type: () -> Text + # pylint: disable=no-self-use """Provides a description that can inform an appropriate cryptographic materials provider about how to build a DelegatedKey for signature verification. If implemented, the return value of this method is included in the material description written to @@ -148,4 +159,4 @@ def signing_algorithm(self): :returns: Signing algorithm identifier :rtype: str """ - self._raise_not_implemented('signing_algorithm') + _raise_not_implemented('signing_algorithm') diff --git a/src/dynamodb_encryption_sdk/delegated_keys/jce.py b/src/dynamodb_encryption_sdk/delegated_keys/jce.py index c37ae98f..dd5c5469 100644 --- a/src/dynamodb_encryption_sdk/delegated_keys/jce.py +++ b/src/dynamodb_encryption_sdk/delegated_keys/jce.py @@ -20,10 +20,10 @@ from cryptography.hazmat.primitives.asymmetric import rsa import six -from . import DelegatedKey from dynamodb_encryption_sdk.exceptions import JceTransformationError, UnwrappingError from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes, KeyEncodingType, LOGGER_NAME from dynamodb_encryption_sdk.internal.crypto.jce_bridge import authentication, encryption, primitives +from . import DelegatedKey __all__ = ('JceNameLocalDelegatedKey',) _LOGGER = logging.getLogger(LOGGER_NAME) @@ -67,6 +67,7 @@ def _generate_rsa_key(key_length): @attr.s class JceNameLocalDelegatedKey(DelegatedKey): + # pylint: disable=too-many-instance-attributes """Delegated key that uses JCE StandardName algorithm values to determine behavior. :param bytes key: Raw key bytes @@ -76,6 +77,7 @@ class JceNameLocalDelegatedKey(DelegatedKey): :param key_encoding: Identifies how the provided key is encoded :type key_encoding: dynamodb_encryption_sdk.identifiers.KeyEncodingTypes """ + key = attr.ib(validator=attr.validators.instance_of(bytes), repr=False) _algorithm = attr.ib(validator=attr.validators.instance_of(six.string_types)) _key_type = attr.ib(validator=attr.validators.instance_of(EncryptionKeyTypes)) @@ -116,7 +118,11 @@ def __attrs_post_init__(self): except KeyError: pass else: - self.__key = key_transformer.load_key(self.key, self._key_type, self._key_encoding) + self.__key = key_transformer.load_key( # attrs confuses pylint: disable=attribute-defined-outside-init + self.key, + self._key_type, + self._key_encoding + ) self._enable_encryption() self._enable_wrap() return @@ -129,7 +135,11 @@ def __attrs_post_init__(self): except KeyError: pass else: - self.__key = key_transformer.load_key(self.key, self._key_type, self._key_encoding) + self.__key = key_transformer.load_key( # attrs confuses pylint: disable=attribute-defined-outside-init + self.key, + self._key_type, + self._key_encoding + ) self._enable_authentication() return @@ -172,6 +182,7 @@ def allowed_for_raw_materials(self): def _encrypt(self, algorithm, name, plaintext, additional_associated_data=None): # type: (Text, Text, bytes, Dict[Text, Text]) -> bytes + # pylint: disable=unused-argument """ Encrypt data. @@ -189,6 +200,7 @@ def _encrypt(self, algorithm, name, plaintext, additional_associated_data=None): def _decrypt(self, algorithm, name, ciphertext, additional_associated_data=None): # type: (Text, Text, bytes, Dict[Text, Text]) -> bytes + # pylint: disable=unused-argument """Encrypt data. :param str algorithm: Java StandardName transformation string of algorithm to use to decrypt data @@ -204,6 +216,7 @@ def _decrypt(self, algorithm, name, ciphertext, additional_associated_data=None) def _wrap(self, algorithm, content_key, additional_associated_data=None): # type: (Text, bytes, Dict[Text, Text]) -> bytes + # pylint: disable=unused-argument """Wrap content key. :param str algorithm: Text description of algorithm to use to wrap key @@ -220,6 +233,7 @@ def _wrap(self, algorithm, content_key, additional_associated_data=None): def _unwrap(self, algorithm, wrapped_key, wrapped_key_algorithm, wrapped_key_type, additional_associated_data=None): # type: (Text, bytes, Text, EncryptionKeyTypes, Dict[Text, Text]) -> DelegatedKey + # pylint: disable=unused-argument """Wrap content key. :param str algorithm: Text description of algorithm to use to unwrap key diff --git a/src/dynamodb_encryption_sdk/encrypted/__init__.py b/src/dynamodb_encryption_sdk/encrypted/__init__.py index ebd440fc..c9b24e7d 100644 --- a/src/dynamodb_encryption_sdk/encrypted/__init__.py +++ b/src/dynamodb_encryption_sdk/encrypted/__init__.py @@ -10,13 +10,21 @@ # 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 attr +"""Resources for encrypting items.""" import copy +import attr + +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Dict # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + from dynamodb_encryption_sdk.exceptions import InvalidArgumentError from dynamodb_encryption_sdk.identifiers import ItemAction from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider -from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials +from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext __all__ = ('CryptoConfig',) @@ -33,11 +41,13 @@ class CryptoConfig(object): :param attribute_actions: Description of what action should be taken for each attribute :type attribute_actions: dynamodb_encryption_sdk.structures.AttributeActions """ + materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) encryption_context = attr.ib(validator=attr.validators.instance_of(EncryptionContext)) attribute_actions = attr.ib(validator=attr.validators.instance_of(AttributeActions)) def __attrs_post_init__(self): + # type: () -> None """Make sure that primary index attributes are not being encrypted.""" if self.encryption_context.partition_key_name is not None: if self.attribute_actions.action(self.encryption_context.partition_key_name) is ItemAction.ENCRYPT_AND_SIGN: @@ -48,6 +58,7 @@ def __attrs_post_init__(self): raise InvalidArgumentError('Cannot encrypt sort key') def decryption_materials(self): + # type: () -> DecryptionMaterials """Load decryption materials from instance resources. :returns: Decryption materials @@ -56,6 +67,7 @@ def decryption_materials(self): return self.materials_provider.decryption_materials(self.encryption_context) def encryption_materials(self): + # type: () -> EncryptionMaterials """Load encryption materials from instance resources. :returns: Encryption materials @@ -64,6 +76,7 @@ def encryption_materials(self): return self.materials_provider.encryption_materials(self.encryption_context) def copy(self): + # type: () -> CryptoConfig """Return a copy of this instance with a copied instance of its encryption context. :returns: New CryptoConfig identical to this one @@ -77,6 +90,7 @@ def copy(self): def validate_get_arguments(kwargs): + # type: (Dict[Text, Any]) -> None """Verify that attribute filtering parameters are not found in the request. :raises InvalidArgumentError: if banned parameters are found diff --git a/src/dynamodb_encryption_sdk/encrypted/client.py b/src/dynamodb_encryption_sdk/encrypted/client.py index 2145fc28..4c61674e 100644 --- a/src/dynamodb_encryption_sdk/encrypted/client.py +++ b/src/dynamodb_encryption_sdk/encrypted/client.py @@ -10,15 +10,15 @@ # 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 helper class to provide a familiar interface to encrypted tables.""" import attr import botocore.client -from . import CryptoConfig, validate_get_arguments -from .item import decrypt_dynamodb_item, encrypt_dynamodb_item from dynamodb_encryption_sdk.internal.utils import TableInfoCache from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext +from . import CryptoConfig, validate_get_arguments +from .item import decrypt_dynamodb_item, encrypt_dynamodb_item __all__ = ('EncryptedClient',) @@ -44,6 +44,7 @@ class EncryptedClient(object): We do not currently support the ``update_item`` method. """ + _client = attr.ib(validator=attr.validators.instance_of(botocore.client.BaseClient)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _attribute_actions = attr.ib( diff --git a/src/dynamodb_encryption_sdk/encrypted/item.py b/src/dynamodb_encryption_sdk/encrypted/item.py index b9cff8c0..bf34d15d 100644 --- a/src/dynamodb_encryption_sdk/encrypted/item.py +++ b/src/dynamodb_encryption_sdk/encrypted/item.py @@ -12,13 +12,11 @@ # language governing permissions and limitations under the License. """Top-level functions for encrypting and decrypting DynamoDB items.""" try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any, Callable, Dict # pylint: disable=unused-import - from dynamodb_encryption_sdk.internal import dynamodb_types # pylint: disable=unused-import + from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from . import CryptoConfig from dynamodb_encryption_sdk.exceptions import DecryptionError, EncryptionError from dynamodb_encryption_sdk.identifiers import ItemAction from dynamodb_encryption_sdk.internal.crypto.authentication import sign_item, verify_item_signature @@ -26,9 +24,11 @@ from dynamodb_encryption_sdk.internal.formatting.material_description import ( deserialize as deserialize_material_description, serialize as serialize_material_description ) -from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys, MaterialDescriptionValues from dynamodb_encryption_sdk.internal.formatting.transform import ddb_to_dict, dict_to_ddb -from dynamodb_encryption_sdk.internal.identifiers import ReservedAttributes +from dynamodb_encryption_sdk.internal.identifiers import ( + MaterialDescriptionKeys, MaterialDescriptionValues, ReservedAttributes +) +from . import CryptoConfig # noqa pylint: disable=unused-import __all__ = ('encrypt_dynamodb_item', 'encrypt_python_item', 'decrypt_dynamodb_item', 'decrypt_python_item') diff --git a/src/dynamodb_encryption_sdk/encrypted/resource.py b/src/dynamodb_encryption_sdk/encrypted/resource.py index 4b0e879f..3e49eb78 100644 --- a/src/dynamodb_encryption_sdk/encrypted/resource.py +++ b/src/dynamodb_encryption_sdk/encrypted/resource.py @@ -10,17 +10,17 @@ # 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 helper class to provide a familiar interface to encrypted tables.""" import attr from boto3.resources.base import ServiceResource from boto3.resources.collection import CollectionManager -from . import CryptoConfig, validate_get_arguments -from .item import decrypt_python_item, encrypt_python_item -from .table import EncryptedTable from dynamodb_encryption_sdk.internal.utils import TableInfoCache from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext +from . import CryptoConfig, validate_get_arguments +from .item import decrypt_python_item, encrypt_python_item +from .table import EncryptedTable __all__ = ('EncryptedResource',) @@ -40,6 +40,7 @@ class EncryptedTablesCollectionManager(object): :param table_info_cache: Local cache from which to obtain TableInfo data :type table_info_cache: dynamodb_encryption_sdk.internal.utils.TableInfoCache """ + _collection = attr.ib(validator=attr.validators.instance_of(CollectionManager)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _attribute_actions = attr.ib(validator=attr.validators.instance_of(AttributeActions)) @@ -124,6 +125,7 @@ class EncryptedResource(object): :param bool auto_refresh_table_indexes: Should we attempt to refresh information about table indexes? Requires ``dynamodb:DescribeTable`` permissions on each table. (default: True) """ + _resource = attr.ib(validator=attr.validators.instance_of(ServiceResource)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _attribute_actions = attr.ib( diff --git a/src/dynamodb_encryption_sdk/encrypted/table.py b/src/dynamodb_encryption_sdk/encrypted/table.py index cf0b9834..366056a9 100644 --- a/src/dynamodb_encryption_sdk/encrypted/table.py +++ b/src/dynamodb_encryption_sdk/encrypted/table.py @@ -14,10 +14,10 @@ import attr from boto3.resources.base import ServiceResource -from . import CryptoConfig, validate_get_arguments -from .item import decrypt_python_item, encrypt_python_item from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext, TableInfo +from . import CryptoConfig, validate_get_arguments +from .item import decrypt_python_item, encrypt_python_item __all__ = ('EncryptedTable',) @@ -52,6 +52,7 @@ class EncryptedTable(object): :param bool auto_refresh_table_indexes: Should we attempt to refresh information about table indexes? Requires ``dynamodb:DescribeTable`` permissions on each table. (default: True) """ + _table = attr.ib(validator=attr.validators.instance_of(ServiceResource)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _table_info = attr.ib( diff --git a/src/dynamodb_encryption_sdk/exceptions.py b/src/dynamodb_encryption_sdk/exceptions.py index 86364247..1191f09f 100644 --- a/src/dynamodb_encryption_sdk/exceptions.py +++ b/src/dynamodb_encryption_sdk/exceptions.py @@ -10,6 +10,7 @@ # 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. +"""Exception classed for use in the DynamoDB Encryption Client.""" class DynamodbEncryptionSdkError(Exception): @@ -17,7 +18,7 @@ class DynamodbEncryptionSdkError(Exception): class InvalidArgumentError(DynamodbEncryptionSdkError): - """""" + """Raised when a general invalid argument is provided.""" class SerializationError(DynamodbEncryptionSdkError): @@ -36,53 +37,53 @@ class InvalidMaterialDescriptionVersionError(DeserializationError): """Raised when a material description is encountered with an invalid version.""" -class InvalidAlgorithmError(DynamodbEncryptionSdkError): +class InvalidAlgorithmError(InvalidArgumentError): """Raised when an invalid algorithm identifier is encountered.""" class JceTransformationError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated errors encountered when attempting to read a JCE transformation.""" class DelegatedKeyError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated errors encountered by a DelegatedKey.""" class DelegatedKeyEncryptionError(DelegatedKeyError): - """""" + """Raised when a DelegatedKey encounters an error during encryption.""" class DelegatedKeyDecryptionError(DelegatedKeyError): - """""" + """Raised when a DelegatedKey encounters an error during decryption.""" class AwsKmsMaterialsProviderError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated errors encountered by the AwsKmsCryptographicMaterialsProvider.""" class UnknownRegionError(AwsKmsMaterialsProviderError): - """""" + """Raised when the AwsKmsCryptographicMaterialsProvider is asked for an unknown region.""" class DecryptionError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated error encountered while decrypting data.""" class UnwrappingError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated error encountered while unwrapping a key.""" class EncryptionError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated error encountered while encrypting data.""" class WrappingError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated error encountered while wrapping a key.""" class SigningError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated error encountered while signing data.""" class SignatureVerificationError(DynamodbEncryptionSdkError): - """""" + """Otherwise undifferentiated error encountered while verifying a signature.""" diff --git a/src/dynamodb_encryption_sdk/identifiers.py b/src/dynamodb_encryption_sdk/identifiers.py index c3538644..48221c3b 100644 --- a/src/dynamodb_encryption_sdk/identifiers.py +++ b/src/dynamodb_encryption_sdk/identifiers.py @@ -10,6 +10,7 @@ # 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. +"""Unique identifiers used by the DynamoDB Encryption Client.""" from enum import Enum __all__ = ('LOGGER_NAME', 'ItemAction', 'EncryptionKeyTypes', 'KeyEncodingType') @@ -20,22 +21,30 @@ class ItemAction(Enum): """Possible actions to take on an item attribute.""" + DO_NOTHING = 0 SIGN_ONLY = 1 ENCRYPT_AND_SIGN = 2 def __gt__(self, other): + # type: (ItemAction) -> bool + """Define ItemAction equality.""" return not self.__lt__(other) and not self.__eq__(other) def __lt__(self, other): + # type: (ItemAction) -> bool + """Define ItemAction equality.""" return self.value < other.value def __eq__(self, other): + # type: (ItemAction) -> bool + """Define ItemAction equality.""" return self.value == other.value class EncryptionKeyTypes(Enum): """Supported types of encryption keys.""" + SYMMETRIC = 0 PRIVATE = 1 PUBLIC = 2 @@ -43,6 +52,7 @@ class EncryptionKeyTypes(Enum): class KeyEncodingType(Enum): """Supported key encoding schemes.""" + RAW = 0 DER = 1 PEM = 2 diff --git a/src/dynamodb_encryption_sdk/internal/crypto/__init__.py b/src/dynamodb_encryption_sdk/internal/crypto/__init__.py index 22070dbc..f8e2c233 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/__init__.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/__init__.py @@ -10,4 +10,4 @@ # 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. -"""""" +"""Inner cryptographic components.""" diff --git a/src/dynamodb_encryption_sdk/internal/crypto/authentication.py b/src/dynamodb_encryption_sdk/internal/crypto/authentication.py index a12db381..b1720704 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/authentication.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/authentication.py @@ -14,12 +14,12 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes -from dynamodb_encryption_sdk.delegated_keys import DelegatedKey -from dynamodb_encryption_sdk.encrypted import CryptoConfig +from dynamodb_encryption_sdk.delegated_keys import DelegatedKey # noqa pylint: disable=unused-import +from dynamodb_encryption_sdk.encrypted import CryptoConfig # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.identifiers import ItemAction from dynamodb_encryption_sdk.internal.formatting.serialize.attribute import serialize_attribute from dynamodb_encryption_sdk.internal.identifiers import SignatureValues, Tag, TEXT_ENCODING -from dynamodb_encryption_sdk.structures import AttributeActions +from dynamodb_encryption_sdk.structures import AttributeActions # noqa pylint: disable=unused-import __all__ = ('sign_item', 'verify_item_signature') diff --git a/src/dynamodb_encryption_sdk/internal/crypto/encryption.py b/src/dynamodb_encryption_sdk/internal/crypto/encryption.py index 31cd2b37..5caf71f6 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/encryption.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/encryption.py @@ -12,13 +12,13 @@ # language governing permissions and limitations under the License. """Functions to handle encrypting and decrypting DynamoDB attributes.""" try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import text # pylint: disable=unused-import - from dynamodb_encryption_sdk.internal import dynamodb_types # pylint: disable=unused-import + from typing import Text # noqa pylint: disable=unused-import + from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.delegated_keys import DelegatedKey +from dynamodb_encryption_sdk.delegated_keys import DelegatedKey # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.internal.formatting.deserialize.attribute import deserialize_attribute from dynamodb_encryption_sdk.internal.formatting.serialize.attribute import serialize_attribute from dynamodb_encryption_sdk.internal.identifiers import Tag diff --git a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/__init__.py b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/__init__.py index 22070dbc..3275667b 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/__init__.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/__init__.py @@ -10,4 +10,6 @@ # 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 to provide cryptographic primitives based on JCE Standard Names. +https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html +""" diff --git a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/authentication.py b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/authentication.py index 7c3bb366..6392345d 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/authentication.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/authentication.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. """Cryptographic authentication resources for JCE bridge.""" import abc +import logging import attr from cryptography.hazmat.backends import default_backend @@ -19,11 +20,19 @@ from cryptography.hazmat.primitives.asymmetric import padding, rsa import six -from .primitives import load_rsa_key +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Text # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + from dynamodb_encryption_sdk.exceptions import InvalidAlgorithmError, SignatureVerificationError, SigningError +from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes, KeyEncodingType, LOGGER_NAME from dynamodb_encryption_sdk.internal.validators import callable_validator +from .primitives import load_rsa_key __all__ = ('JavaAuthenticator', 'JavaMac', 'JavaSignature', 'JAVA_AUTHENTICATOR') +_LOGGER = logging.getLogger(LOGGER_NAME) @six.add_metaclass(abc.ABCMeta) @@ -32,19 +41,50 @@ class JavaAuthenticator(object): @abc.abstractmethod def load_key(self, key, key_type, key_encoding): - """""" + # (bytes, EncryptionKeyTypes, KeyEncodingType) -> Any + # TODO: narrow down the output type + """Load a key from bytes. + + :param bytes key: Raw key bytes to load + :param key_type: Type of key to load + :type key_type: dynamodb_encryption_sdk.identifiers.EncryptionKeyTypes + :param key_encoding: Encoding used to serialize ``key`` + :type key_encoding: dynamodb_encryption_sdk.identifiers.KeyEncodingType + :returns: Loaded key + :rtype: bytes + """ @abc.abstractmethod def validate_algorithm(self, algorithm): - """""" + # type: (Text) -> None + """Determine whether the requested algorithm name is compatible with this authenticator. + + :param str algorithm: Algorithm name + :raises InvalidAlgorithmError: if specified algorithm name is not compatible with this authenticator + """ @abc.abstractmethod def sign(self, key, data): - """""" + # type: (Any, bytes) -> bytes + """Sign ``data`` using loaded ``key``. + + :param key: Loaded key + :param bytes data: Data to sign + :returns: Calculated signature + :rtype: bytes + :raises SigningError: if unable to sign ``data`` with ``key`` + """ @abc.abstractmethod def verify(self, key, signature, data): - """""" + # type: (Any, bytes, bytes) -> None + """Verify ``signature`` over ``data`` using ``key``. + + :param key: Loaded key + :param bytes signature: Signature to verify + :param bytes data: Data over which to verify signature + :raises SignatureVerificationError: if unable to verify ``signature`` + """ @attr.s @@ -54,12 +94,17 @@ class JavaMac(JavaAuthenticator): https://docs.oracle.com/javase/8/docs/api/javax/crypto/Mac.html https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Mac """ + java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) algorithm_type = attr.ib(validator=callable_validator) hash_type = attr.ib(validator=callable_validator) def _build_hmac_signer(self, key): - """""" + # type: (bytes) -> Any + """Build HMAC signer using instance algorithm and hash type and ``key``. + + :param bytes key: Key to use in signer + """ return self.algorithm_type( key, self.hash_type(), @@ -67,13 +112,28 @@ def _build_hmac_signer(self, key): ) def load_key(self, key, key_type, key_encoding): - """""" + # (bytes, EncryptionKeyTypes, KeyEncodingType) -> bytes + """Load a raw key from bytes. + + :param bytes key: Raw key bytes to load + :param key_type: Type of key to load + :type key_type: dynamodb_encryption_sdk.identifiers.EncryptionKeyTypes + :param key_encoding: Encoding used to serialize ``key`` + :type key_encoding: dynamodb_encryption_sdk.identifiers.KeyEncodingType + :returns: Loaded key + :rtype: bytes + :raises ValueError: if ``key_type`` is not symmetric or ``key_encoding`` is not raw + """ + if not (key_type is EncryptionKeyTypes.SYMMETRIC and key_encoding is KeyEncodingType.RAW): + raise ValueError('Key type must be symmetric and encoding must be raw.') + return key def validate_algorithm(self, algorithm): # type: (Text) -> None - """Determine whether the requested algorithm name is compatible with this signature. + """Determine whether the requested algorithm name is compatible with this authenticator. + :param str algorithm: Algorithm name :raises InvalidAlgorithmError: if specified algorithm name is not compatible with this authenticator """ if not algorithm.startswith(self.java_name): @@ -88,25 +148,38 @@ def sign(self, key, data): # type: (bytes, bytes) -> bytes """Sign ``data`` using loaded ``key``. - :param bytes key: Raw HMAC key + :param bytes key: Loaded key :param bytes data: Data to sign :returns: Calculated signature :rtype: bytes + :raises SigningError: if unable to sign ``data`` with ``key`` """ - signer = self._build_hmac_signer(key) - signer.update(data) - return signer.finalize() + try: + signer = self._build_hmac_signer(key) + signer.update(data) + return signer.finalize() + except Exception: + message = 'Unable to sign data' + _LOGGER.exception(message) + raise SigningError(message) def verify(self, key, signature, data): - """ + # type: (bytes, bytes, bytes) -> None + """Verify ``signature`` over ``data`` using ``key``. - :param bytes key: Raw HMAC key + :param bytes key: Loaded key :param bytes signature: Signature to verify :param bytes data: Data over which to verify signature + :raises SignatureVerificationError: if unable to verify ``signature`` """ - verifier = self._build_hmac_signer(key) - verifier.update(data) - verifier.verify(signature) + try: + verifier = self._build_hmac_signer(key) + verifier.update(data) + verifier.verify(signature) + except Exception: + message = 'Unable to verify signature' + _LOGGER.exception(message) + raise SignatureVerificationError(message) @attr.s @@ -116,6 +189,7 @@ class JavaSignature(JavaAuthenticator): https://docs.oracle.com/javase/8/docs/api/java/security/Signature.html https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature """ + java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) algorithm_type = attr.ib() hash_type = attr.ib(validator=callable_validator) @@ -123,8 +197,9 @@ class JavaSignature(JavaAuthenticator): def validate_algorithm(self, algorithm): # type: (Text) -> None - """Determine whether the requested algorithm name is compatible with this signature. + """Determine whether the requested algorithm name is compatible with this authenticator. + :param str algorithm: Algorithm name :raises InvalidAlgorithmError: if specified algorithm name is not compatible with this authenticator """ if not algorithm.endswith(self.java_name): @@ -136,35 +211,77 @@ def validate_algorithm(self, algorithm): ) def load_key(self, key, key_type, key_encoding): - """""" + # (bytes, EncryptionKeyTypes, KeyEncodingType) -> Any + # TODO: narrow down the output type + """Load a key object from the provided raw key bytes. + + :param bytes key: Raw key bytes to load + :param key_type: Type of key to load + :type key_type: dynamodb_encryption_sdk.identifiers.EncryptionKeyTypes + :param key_encoding: Encoding used to serialize ``key`` + :type key_encoding: dynamodb_encryption_sdk.identifiers.KeyEncodingType + :returns: Loaded key + :rtype: TODO: + :raises ValueError: if ``key_type`` and ``key_encoding`` are not a valid pairing + """ return load_rsa_key(key, key_type, key_encoding) def sign(self, key, data): - """""" + # type: (Any, bytes) -> bytes + # TODO: narrow down the key type + """Sign ``data`` using loaded ``key``. + + :param key: Loaded key + :type key: TODO: + :param bytes data: Data to sign + :returns: Calculated signature + :rtype: bytes + :raises SigningError: if unable to sign ``data`` with ``key`` + """ if hasattr(key, 'public_bytes'): raise SigningError('"sign" is not supported by public keys') - # TODO: normalize to SigningError - return key.sign( - data, - self.padding_type(), - self.hash_type() - ) + try: + return key.sign( + data, + self.padding_type(), + self.hash_type() + ) + except Exception: + message = 'Unable to sign data' + _LOGGER.exception(message) + raise SigningError(message) def verify(self, key, signature, data): - """""" + # type: (Any, bytes, bytes) -> None + # TODO: narrow down the key type + """Verify ``signature`` over ``data`` using ``key``. + + :param key: Loaded key + :type key: TODO: + :param bytes signature: Signature to verify + :param bytes data: Data over which to verify signature + :raises SignatureVerificationError: if unable to verify ``signature`` + """ if hasattr(key, 'private_bytes'): _key = key.public_key() else: _key = key - # TODO: normalize to SignatureVerificationError - _key.verify( - signature, - data, - self.padding_type(), - self.hash_type() - ) + try: + _key.verify( + signature, + data, + self.padding_type(), + self.hash_type() + ) + except Exception: + message = 'Unable to verify signature' + _LOGGER.exception(message) + raise SignatureVerificationError(message) +# Additional possible JCE names that we might support in the future if needed +# HmacSHA1 +# SHA(1|224|256|384|512)with(|EC)DSA JAVA_AUTHENTICATOR = { 'HmacSHA224': JavaMac('HmacSHA224', hmac.HMAC, hashes.SHA224), 'HmacSHA256': JavaMac('HmacSHA256', hmac.HMAC, hashes.SHA256), @@ -174,9 +291,4 @@ def verify(self, key, signature, data): 'SHA256withRSA': JavaSignature('SHA256withRSA', rsa, hashes.SHA256, padding.PKCS1v15), 'SHA384withRSA': JavaSignature('SHA384withRSA', rsa, hashes.SHA384, padding.PKCS1v15), 'SHA512withRSA': JavaSignature('SHA512withRSA', rsa, hashes.SHA512, padding.PKCS1v15) - # TODO: should we support these? - # HmacMD5 - # HmacSHA1 - # (NONE|SHA(1|224|256|384|512))with(|EC)DSA - # (NONE|SHA1)withRSA } diff --git a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py index 3704bb52..fa23e03f 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py @@ -13,10 +13,10 @@ """Cipher resource for JCE bridge.""" import attr +from dynamodb_encryption_sdk.exceptions import JceTransformationError from .primitives import ( - JAVA_ENCRYPTION_ALGORITHM, JavaEncryptionAlgorithm, JAVA_MODE, JavaMode, JAVA_PADDING, JavaPadding + JAVA_ENCRYPTION_ALGORITHM, JAVA_MODE, JAVA_PADDING, JavaEncryptionAlgorithm, JavaMode, JavaPadding ) -from dynamodb_encryption_sdk.exceptions import JceTransformationError __all__ = ('JavaCipher',) @@ -31,6 +31,7 @@ class JavaCipher(object): :param mode: TODO: :param padding: TODO: """ + cipher = attr.ib(validator=attr.validators.instance_of(JavaEncryptionAlgorithm)) mode = attr.ib(validator=attr.validators.instance_of(JavaMode)) padding = attr.ib(validator=attr.validators.instance_of(JavaPadding)) diff --git a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py index 19220c22..d4a46b0f 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py @@ -12,14 +12,14 @@ # language governing permissions and limitations under the License. """Cryptographic primitive resources for JCE bridge.""" import abc -import attr import logging import os +import attr from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import padding as symmetric_padding, hashes, serialization, keywrap +from cryptography.hazmat.primitives import hashes, keywrap, padding as symmetric_padding, serialization from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding, rsa -from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher +from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes import six from dynamodb_encryption_sdk.exceptions import ( @@ -79,8 +79,9 @@ def unpadder(self): @six.add_metaclass(abc.ABCMeta) class JavaPadding(object): + # pylint: disable=too-few-public-methods """Bridge the gap from the Java padding names and Python resources. - https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher + https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher """ @abc.abstractmethod @@ -90,7 +91,9 @@ def build(self, block_size): @attr.s class SimplePadding(JavaPadding): + # pylint: disable=too-few-public-methods """Padding types that do not require any preparation.""" + java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) padding = attr.ib(validator=callable_validator) @@ -106,7 +109,9 @@ def build(self, block_size=None): @attr.s class BlockSizePadding(JavaPadding): + # pylint: disable=too-few-public-methods """Padding types that require a block size input.""" + java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) padding = attr.ib(validator=callable_validator) @@ -122,6 +127,7 @@ def build(self, block_size): @attr.s class OaepPadding(JavaPadding): + # pylint: disable=too-few-public-methods """OAEP padding types. These require more complex setup. .. warning:: @@ -130,6 +136,7 @@ class OaepPadding(JavaPadding): The same hashing algorithm should be used by both OAEP and the MGF, but by default Java always uses SHA1 for the MGF. """ + java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) padding = attr.ib(validator=callable_validator) digest = attr.ib(validator=callable_validator) @@ -152,9 +159,11 @@ def build(self, block_size=None): @attr.s class JavaMode(object): + # pylint: disable=too-few-public-methods """Bridge the gap from the Java encryption mode names and Python resources. - https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher + https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher """ + java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) mode = attr.ib(validator=callable_validator) @@ -170,9 +179,11 @@ def build(self, iv): @attr.s class JavaEncryptionAlgorithm(object): + # pylint: disable=too-few-public-methods """Bridge the gap from the Java encryption algorithm names and Python resources. https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher """ + java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) cipher = attr.ib() @@ -284,6 +295,7 @@ def unwrap(self, wrapping_key, wrapped_key): raise UnwrappingError(error_message) def encrypt(self, key, data, mode, padding): + # this can be disabled by _disable_encryption, so pylint: disable=method-hidden """Encrypt data using the supplied values. :param bytes key: Loaded encryption key @@ -315,6 +327,7 @@ def encrypt(self, key, data, mode, padding): raise EncryptionError(error_message) def decrypt(self, key, data, mode, padding): + # this can be disabled by _disable_encryption, so pylint: disable=method-hidden """Decrypt data using the supplied values. :param bytes key: Loaded decryption key @@ -360,11 +373,23 @@ def decrypt(self, key, data, mode, padding): def load_rsa_key(key, key_type, key_encoding): - """""" + # (bytes, EncryptionKeyTypes, KeyEncodingType) -> Any + # TODO: narrow down the output type + """Load an RSA key object from the provided raw key bytes. + + :param bytes key: Raw key bytes to load + :param key_type: Type of key to load + :type key_type: dynamodb_encryption_sdk.identifiers.EncryptionKeyTypes + :param key_encoding: Encoding used to serialize ``key`` + :type key_encoding: dynamodb_encryption_sdk.identifiers.KeyEncodingType + :returns: Loaded key + :rtype: TODO: + :raises ValueError: if ``key_type`` and ``key_encoding`` are not a valid pairing + """ try: loader = _RSA_KEY_LOADING[key_type][key_encoding] except KeyError: - raise Exception('Invalid key type: {}'.format(key_type)) + raise ValueError('Invalid key type and encoding: {} and {}'.format(key_type, key_encoding)) kwargs = dict(data=key, backend=default_backend()) if key_type is EncryptionKeyTypes.PRIVATE: @@ -374,7 +399,7 @@ def load_rsa_key(key, key_type, key_encoding): _KEY_LOADERS = { - rsa: load_rsa_key + rsa: load_rsa_key } @@ -409,6 +434,7 @@ def load_key(self, key, key_type, key_encoding): return _KEY_LOADERS[self.cipher](key, key_type, key_encoding) def encrypt(self, key, data, mode, padding): + # pylint: disable=unused-argument,no-self-use """Encrypt data using the supplied values. :param bytes key: Loaded encryption key @@ -432,6 +458,7 @@ def encrypt(self, key, data, mode, padding): raise EncryptionError(error_message) def decrypt(self, key, data, mode, padding): + # pylint: disable=unused-argument,no-self-use """Decrypt data using the supplied values. :param bytes key: Loaded decryption key @@ -457,20 +484,12 @@ def decrypt(self, key, data, mode, padding): 'RSA': JavaAsymmetricEncryptionAlgorithm('RSA', rsa), 'AES': JavaSymmetricEncryptionAlgorithm('AES', algorithms.AES), 'AESWrap': JavaSymmetricEncryptionAlgorithm('AESWrap', algorithms.AES) - # TODO: Should we support these? - # DES : pretty sure we don't want to support this - # DESede : pretty sure we don't want to support this - # 'BLOWFISH': JavaSymmetricEncryptionAlgorithm('Blowfish', algorithms.Blowfish) } JAVA_MODE = { 'ECB': JavaMode('ECB', modes.ECB), 'CBC': JavaMode('CBC', modes.CBC), 'CTR': JavaMode('CTR', modes.CTR), 'GCM': JavaMode('GCM', modes.GCM) - # TODO: Should we support these? - # 'OFB': JavaMode('OFB', modes.OFB) - # 'CFB': JavaMode('CFB', modes.CFB) - # 'CFB8': JavaMode('CFB8', modes.CFB8) } JAVA_PADDING = { 'NoPadding': SimplePadding('NoPadding', _NoPadding), diff --git a/src/dynamodb_encryption_sdk/internal/defaults.py b/src/dynamodb_encryption_sdk/internal/defaults.py deleted file mode 100644 index ca55ca17..00000000 --- a/src/dynamodb_encryption_sdk/internal/defaults.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -"""""" -__all__ = ('LOGGING_NAME', 'MATERIAL_DESCRIPTION_VERSION') - -LOGGING_NAME = 'dynamodb_encryption_sdk' -MATERIAL_DESCRIPTION_VERSION = b'\00' * 4 diff --git a/src/dynamodb_encryption_sdk/internal/formatting/__init__.py b/src/dynamodb_encryption_sdk/internal/formatting/__init__.py index 22070dbc..eec2d539 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/__init__.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/__init__.py @@ -10,4 +10,4 @@ # 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. -"""""" +"""Data formatting utilities for the DynamoDB Encryption Client.""" diff --git a/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py b/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py index c1639037..0e303902 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py @@ -19,7 +19,7 @@ try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Callable, Dict, List, Union # noqa pylint: disable=unused-import - from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import + from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import,ungrouped-imports except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass @@ -27,13 +27,13 @@ from boto3.dynamodb.types import Binary from dynamodb_encryption_sdk.exceptions import DeserializationError -from dynamodb_encryption_sdk.internal.defaults import LOGGING_NAME +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME from dynamodb_encryption_sdk.internal.formatting.deserialize import decode_byte, decode_length, decode_tag, decode_value from dynamodb_encryption_sdk.internal.identifiers import Tag, TagValues, TEXT_ENCODING from dynamodb_encryption_sdk.internal.str_ops import to_str __all__ = ('deserialize_attribute',) -_LOGGER = logging.getLogger(LOGGING_NAME) +_LOGGER = logging.getLogger(LOGGER_NAME) def deserialize_attribute(serialized_attribute): # noqa: C901 pylint: disable=too-many-locals diff --git a/src/dynamodb_encryption_sdk/internal/formatting/material_description.py b/src/dynamodb_encryption_sdk/internal/formatting/material_description.py index 45df93ff..ed3d76fa 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/material_description.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/material_description.py @@ -15,15 +15,16 @@ import logging import struct -from .deserialize import decode_value, unpack_value -from .serialize import encode_value from dynamodb_encryption_sdk.exceptions import InvalidMaterialDescriptionError, InvalidMaterialDescriptionVersionError -from dynamodb_encryption_sdk.internal.defaults import LOGGING_NAME, MATERIAL_DESCRIPTION_VERSION +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME from dynamodb_encryption_sdk.internal.identifiers import Tag from dynamodb_encryption_sdk.internal.str_ops import to_bytes, to_str +from .deserialize import decode_value, unpack_value +from .serialize import encode_value __all__ = ('serialize', 'deserialize') -_LOGGER = logging.getLogger(LOGGING_NAME) +_LOGGER = logging.getLogger(LOGGER_NAME) +_MATERIAL_DESCRIPTION_VERSION = b'\00' * 4 def serialize(material_description): @@ -34,7 +35,7 @@ def serialize(material_description): :returns: Serialized material description as a DynamoDB binary attribute value :rtype: dict """ - material_description_bytes = bytearray(MATERIAL_DESCRIPTION_VERSION) + material_description_bytes = bytearray(_MATERIAL_DESCRIPTION_VERSION) # TODO: verify Java sorting order for name, value in sorted(material_description.items(), key=lambda x: x[0]): @@ -42,10 +43,12 @@ def serialize(material_description): material_description_bytes.extend(encode_value(to_bytes(name))) material_description_bytes.extend(encode_value(to_bytes(value))) except (TypeError, struct.error): - raise InvalidMaterialDescriptionError('Invalid name or value in material description: "{name}"="{value}"'.format( - name=name, - value=value - )) + raise InvalidMaterialDescriptionError( + 'Invalid name or value in material description: "{name}"="{value}"'.format( + name=name, + value=value + ) + ) return {Tag.BINARY.dynamodb_tag: bytes(material_description_bytes)} @@ -99,5 +102,5 @@ def _read_version(material_description_bytes): message = 'Malformed material description version' _LOGGER.exception(message) raise InvalidMaterialDescriptionError(message) - if version != MATERIAL_DESCRIPTION_VERSION: + if version != _MATERIAL_DESCRIPTION_VERSION: raise InvalidMaterialDescriptionVersionError('Invalid material description version: {}'.format(repr(version))) diff --git a/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py b/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py index de34b905..0fcaa79e 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py @@ -14,7 +14,7 @@ import struct try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Sized # pylint: disable=unused-import + from typing import Sized # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py b/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py index 9ab0afd6..17729642 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py @@ -16,7 +16,7 @@ try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Callable # noqa pylint: disable=unused-import - from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import + from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import,ungrouped-imports except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass @@ -24,14 +24,14 @@ from boto3.dynamodb.types import Binary, DYNAMODB_CONTEXT from dynamodb_encryption_sdk.exceptions import SerializationError -from dynamodb_encryption_sdk.internal.defaults import LOGGING_NAME +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME from dynamodb_encryption_sdk.internal.formatting.serialize import encode_length, encode_value from dynamodb_encryption_sdk.internal.identifiers import Tag, TagValues from dynamodb_encryption_sdk.internal.str_ops import to_bytes from dynamodb_encryption_sdk.internal.utils import sorted_key_map __all__ = ('serialize_attribute',) -_LOGGER = logging.getLogger(LOGGING_NAME) +_LOGGER = logging.getLogger(LOGGER_NAME) _RESERVED = b'\x00' diff --git a/src/dynamodb_encryption_sdk/internal/formatting/transform.py b/src/dynamodb_encryption_sdk/internal/formatting/transform.py index ab8ac20a..8541639a 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/transform.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/transform.py @@ -12,12 +12,12 @@ # language governing permissions and limitations under the License. """Helper tools for translating between native and DynamoDB items.""" try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any, Dict # pylint: disable=unused-import + from typing import Any, Dict # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from boto3.dynamodb.types import TypeSerializer, TypeDeserializer +from boto3.dynamodb.types import TypeDeserializer, TypeSerializer __all__ = ('dict_to_ddb', 'ddb_to_dict') diff --git a/src/dynamodb_encryption_sdk/internal/identifiers.py b/src/dynamodb_encryption_sdk/internal/identifiers.py index 55b08b5f..d0acd67a 100644 --- a/src/dynamodb_encryption_sdk/internal/identifiers.py +++ b/src/dynamodb_encryption_sdk/internal/identifiers.py @@ -10,11 +10,11 @@ # 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. -"""""" +"""Unique identifiers for internal use only.""" from enum import Enum try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any, ByteString, Dict, List, Text, Union # pylint: disable=unused-import + from typing import Text # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass @@ -31,6 +31,7 @@ class ReservedAttributes(Enum): """Item attributes reserved for use by DynamoDBEncryptionClient""" + MATERIAL_DESCRIPTION = '*amzn-ddb-map-desc*' SIGNATURE = '*amzn-ddb-map-sig*' @@ -64,6 +65,7 @@ def __init__(self, tag, dynamodb_tag, element_tag=None): class TagValues(Enum): """Static values to use when serializing attribute values.""" + FALSE = b'\x00' TRUE = b'\x01' @@ -76,6 +78,7 @@ class SignatureValues(Enum): The only time we actually use these values, we use the SHA256 hash of the value, so we pre-compute these hashes here. """ + ENCRYPTED = ( b'ENCRYPTED', b"9A\x15\xacN\xb0\x9a\xa4\x94)4\x88\x16\xb2\x03\x81'\xb0\xf9\xe3\xa5 7*\xe1\x00\xca\x19\xfb\x08\xfdP" @@ -98,6 +101,7 @@ def __init__(self, raw, sha256): class MaterialDescriptionKeys(Enum): """Static keys for use when building and reading material descriptions.""" + ATTRIBUTE_ENCRYPTION_MODE = 'amzn-ddb-map-sym-mode' SIGNING_KEY_ALGORITHM = 'amzn-ddb-map-signingAlg' WRAPPED_DATA_KEY = 'amzn-ddb-env-key' @@ -108,4 +112,5 @@ class MaterialDescriptionKeys(Enum): class MaterialDescriptionValues(Enum): """Static default values for use when building material descriptions.""" + CBC_PKCS5_ATTRIBUTE_ENCRYPTION = '/CBC/PKCS5Padding' diff --git a/src/dynamodb_encryption_sdk/internal/utils.py b/src/dynamodb_encryption_sdk/internal/utils.py index e64ffd28..ddcde4e0 100644 --- a/src/dynamodb_encryption_sdk/internal/utils.py +++ b/src/dynamodb_encryption_sdk/internal/utils.py @@ -10,13 +10,19 @@ # 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. -"""""" +"""Otherwise undifferentiated utility resources.""" import attr import botocore.client from dynamodb_encryption_sdk.internal.str_ops import to_bytes from dynamodb_encryption_sdk.structures import TableInfo +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Dict, Text # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + __all__ = ('sorted_key_map', 'TableInfoCache') @@ -45,12 +51,13 @@ class TableInfoCache(object): :param bool auto_refresh_table_indexes: Should we attempt to refresh information about table indexes? Requires ``dynamodb:DescribeTable`` permissions on each table. """ + _client = attr.ib(validator=attr.validators.instance_of(botocore.client.BaseClient)) _auto_refresh_table_indexes = attr.ib(validator=attr.validators.instance_of(bool)) def __attrs_post_init__(self): """Set up the empty cache.""" - self._all_tables_info = {} + self._all_tables_info = {} # type: Dict[Text, TableInfo] def table_info(self, table_name): """Collect a TableInfo object for the specified table, creating and adding it to diff --git a/src/dynamodb_encryption_sdk/internal/validators.py b/src/dynamodb_encryption_sdk/internal/validators.py index e6c3bc04..3eb1c4b7 100644 --- a/src/dynamodb_encryption_sdk/internal/validators.py +++ b/src/dynamodb_encryption_sdk/internal/validators.py @@ -10,15 +10,16 @@ # 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. -"""Custom attrs validators.""" +"""Custom validators for ``attrs``.""" __all__ = ('dictionary_validator', 'iterable_validator') def dictionary_validator(key_type, value_type): - """attrs validator that performs deep type checking of dictionaries.""" + """Validator for ``attrs`` that performs deep type checking of dictionaries.""" def _validate_dictionary(instance, attribute, value): + # pylint: disable=unused-argument """Validate that a dictionary is structured as expected. :raises TypeError: if ``value`` is not a dictionary @@ -45,9 +46,10 @@ def _validate_dictionary(instance, attribute, value): def iterable_validator(iterable_type, member_type): - """attrs validator that performs deep type checking of iterables.""" + """Validator for ``attrs`` that performs deep type checking of iterables.""" def _validate_tuple(instance, attribute, value): + # pylint: disable=unused-argument """Validate that a dictionary is structured as expected. :raises TypeError: if ``value`` is not of ``iterable_type`` type @@ -70,6 +72,7 @@ def _validate_tuple(instance, attribute, value): def callable_validator(instance, attribute, value): + # pylint: disable=unused-argument """Validate that an attribute value is callable. :raises TypeError: if ``value`` is not callable diff --git a/src/dynamodb_encryption_sdk/material_providers/__init__.py b/src/dynamodb_encryption_sdk/material_providers/__init__.py index 02830208..82735d17 100644 --- a/src/dynamodb_encryption_sdk/material_providers/__init__.py +++ b/src/dynamodb_encryption_sdk/material_providers/__init__.py @@ -11,8 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Cryptographic materials providers.""" -from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials -from dynamodb_encryption_sdk.structures import EncryptionContext +from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials # noqa pylint: disable=unused-import +from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import __all__ = ('CryptographicMaterialsProvider',) @@ -22,6 +22,7 @@ class CryptographicMaterialsProvider(object): def decryption_materials(self, encryption_context): # type: (EncryptionContext) -> DecryptionMaterials + # pylint: disable=unused-argument,no-self-use """Return decryption materials. :param encryption_context: Encryption context for request @@ -32,6 +33,7 @@ def decryption_materials(self, encryption_context): def encryption_materials(self, encryption_context): # type: (EncryptionContext) -> EncryptionMaterials + # pylint: disable=unused-argument,no-self-use """Return encryption materials. :param encryption_context: Encryption context for request @@ -41,6 +43,8 @@ def encryption_materials(self, encryption_context): raise AttributeError('No encryption materials available') def refresh(self): + # type: () -> None + # pylint: disable=unused-argument,no-self-use """Ask this instance to refresh the cryptographic materials. .. note:: diff --git a/src/dynamodb_encryption_sdk/material_providers/aws_kms.py b/src/dynamodb_encryption_sdk/material_providers/aws_kms.py index a3063cb8..32f0eda6 100644 --- a/src/dynamodb_encryption_sdk/material_providers/aws_kms.py +++ b/src/dynamodb_encryption_sdk/material_providers/aws_kms.py @@ -12,28 +12,34 @@ # language governing permissions and limitations under the License. """Cryptographic materials provider for use with the AWS Key Management Service (KMS).""" from __future__ import division + import base64 from enum import Enum import attr -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.hkdf import HKDF import boto3 import botocore.client import botocore.session +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF import six -from . import CryptographicMaterialsProvider +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.exceptions import UnknownRegionError, UnwrappingError, WrappingError from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes, KeyEncodingType -from dynamodb_encryption_sdk.internal import dynamodb_types from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys, TEXT_ENCODING from dynamodb_encryption_sdk.internal.str_ops import to_bytes, to_str from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials, RawEncryptionMaterials -from dynamodb_encryption_sdk.structures import EncryptionContext +from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import +from . import CryptographicMaterialsProvider __all__ = ('AwsKmsCryptographicMaterialsProvider',) @@ -49,12 +55,14 @@ class HkdfInfo(Enum): """Info strings used for HKDF calculations.""" + ENCRYPTION = b'Encryption' SIGNING = b'Signing' class EncryptionContextKeys(Enum): """Special keys for use in the AWS KMS encryption context.""" + CONTENT_ENCRYPTION_ALGORITHM = '*' + MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value + '*' SIGNATURE_ALGORITHM = '*' + MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value + '*' TABLE_NAME = '*aws-kms-table*' @@ -62,12 +70,14 @@ class EncryptionContextKeys(Enum): @attr.s class KeyInfo(object): + # pylint: disable=too-few-public-methods """Identifying information for a specific key and how it should be used. :param str description: algorithm identifier joined with key length in bits :param str algorithm: algorithm identifier :param int length: Key length in bits """ + description = attr.ib(validator=attr.validators.instance_of(six.string_types)) algorithm = attr.ib(validator=attr.validators.instance_of(six.string_types)) length = attr.ib(validator=attr.validators.instance_of(six.integer_types)) @@ -112,6 +122,7 @@ class AwsKmsCryptographicMaterialsProvider(CryptographicMaterialsProvider): :param dict regional_clients: Dictionary mapping AWS region names to pre-configured boto3 KMS clients (optional) """ + _key_id = attr.ib(validator=attr.validators.instance_of(six.string_types)) _botocore_session = attr.ib( validator=attr.validators.instance_of(botocore.session.Session), @@ -133,19 +144,19 @@ class AwsKmsCryptographicMaterialsProvider(CryptographicMaterialsProvider): def __attrs_post_init__(self): # type: () -> None """Load the content and signing key info.""" - self._content_key_info = KeyInfo.from_material_description( + self._content_key_info = KeyInfo.from_material_description( # pylint: disable=attribute-defined-outside-init material_description=self._material_description, description_key=MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value, default_algorithm=_DEFAULT_CONTENT_ENCRYPTION_ALGORITHM, default_key_length=_DEFAULT_CONTENT_KEY_LENGTH ) - self._signing_key_info = KeyInfo.from_material_description( + self._signing_key_info = KeyInfo.from_material_description( # pylint: disable=attribute-defined-outside-init material_description=self._material_description, description_key=MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value, default_algorithm=_DEFAULT_SIGNING_ALGORITHM, default_key_length=_DEFAULT_SIGNING_KEY_LENGTH ) - self._regional_clients = {} + self._regional_clients = {} # type: Dict[Text, botocore.client.BaseClient] # noqa pylint: disable=attribute-defined-outside-init def _add_regional_client(self, region_name): # type: (Text) -> None @@ -181,6 +192,7 @@ def _client(self, key_id): def _select_key_id(self, encryption_context): # type: (EncryptionContext) -> Text + # pylint: disable=unused-argument """Select the desired key id. .. note:: @@ -197,6 +209,8 @@ def _select_key_id(self, encryption_context): return self._key_id def _validate_key_id(self, key_id, encryption_context): + # type: (EncryptionContext) -> None + # pylint: disable=unused-argument,no-self-use """Validate the selected key id. .. note:: diff --git a/src/dynamodb_encryption_sdk/material_providers/static.py b/src/dynamodb_encryption_sdk/material_providers/static.py index 88d032dd..3c1ab28a 100644 --- a/src/dynamodb_encryption_sdk/material_providers/static.py +++ b/src/dynamodb_encryption_sdk/material_providers/static.py @@ -13,9 +13,9 @@ """Cryptographic materials provider for use with pre-configured encryption and decryption materials.""" import attr -from . import CryptographicMaterialsProvider from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials -from dynamodb_encryption_sdk.structures import EncryptionContext +from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import +from . import CryptographicMaterialsProvider __all__ = ('StaticCryptographicMaterialsProvider',) @@ -29,6 +29,7 @@ class StaticCryptographicMaterialsProvider(CryptographicMaterialsProvider): :param encryption_materials: Encryption materials to provide (optional) :type encryption_materials: dynamodb_encryption_sdk.materials.EncryptionMaterials """ + _decryption_materials = attr.ib( validator=attr.validators.optional(attr.validators.instance_of(DecryptionMaterials)), default=None @@ -47,7 +48,7 @@ def decryption_materials(self, encryption_context): :raises AttributeError: if no decryption materials are available """ if self._decryption_materials is None: - super(StaticCryptographicMaterialsProvider, self).decryption_materials(encryption_context) + return super(StaticCryptographicMaterialsProvider, self).decryption_materials(encryption_context) return self._decryption_materials @@ -60,6 +61,6 @@ def encryption_materials(self, encryption_context): :raises AttributeError: if no encryption materials are available """ if self._encryption_materials is None: - super(StaticCryptographicMaterialsProvider, self).encryption_materials(encryption_context) + return super(StaticCryptographicMaterialsProvider, self).encryption_materials(encryption_context) return self._encryption_materials diff --git a/src/dynamodb_encryption_sdk/material_providers/wrapped.py b/src/dynamodb_encryption_sdk/material_providers/wrapped.py index 91ce7368..96c3c025 100644 --- a/src/dynamodb_encryption_sdk/material_providers/wrapped.py +++ b/src/dynamodb_encryption_sdk/material_providers/wrapped.py @@ -13,11 +13,11 @@ """Cryptographic materials provider to use ephemeral content encryption keys wrapped by delegated keys.""" import attr -from . import CryptographicMaterialsProvider from dynamodb_encryption_sdk.delegated_keys import DelegatedKey from dynamodb_encryption_sdk.exceptions import UnwrappingError, WrappingError from dynamodb_encryption_sdk.materials.wrapped import WrappedCryptographicMaterials -from dynamodb_encryption_sdk.structures import EncryptionContext +from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import +from . import CryptographicMaterialsProvider __all__ = ('WrappedCryptographicMaterialsProvider',) @@ -43,6 +43,7 @@ class WrappedCryptographicMaterialsProvider(CryptographicMaterialsProvider): ``unwrapping_key`` must be provided if providing decryption materials or loading materials from material description """ + _signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _wrapping_key = attr.ib( validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), @@ -54,6 +55,7 @@ class WrappedCryptographicMaterialsProvider(CryptographicMaterialsProvider): ) def _build_materials(self, encryption_context): + # type: (EncryptionContext) -> WrappedCryptographicMaterials """Construct :param encryption_context: Encryption context for request diff --git a/src/dynamodb_encryption_sdk/materials/__init__.py b/src/dynamodb_encryption_sdk/materials/__init__.py index 50edf40a..a1c73e82 100644 --- a/src/dynamodb_encryption_sdk/materials/__init__.py +++ b/src/dynamodb_encryption_sdk/materials/__init__.py @@ -10,18 +10,18 @@ # 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 materials are containers that provide delegated keys for cryptographic operations.""" import abc try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from mypy_extensions import NoReturn + from mypy_extensions import NoReturn # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass import six -from dynamodb_encryption_sdk.delegated_keys import DelegatedKey +from dynamodb_encryption_sdk.delegated_keys import DelegatedKey # noqa pylint: disable=unused-import __all__ = ('CryptographicMaterials', 'EncryptionMaterials', 'DecryptionMaterials') @@ -82,18 +82,20 @@ class EncryptionMaterials(CryptographicMaterials): @property def decryption_key(self): # type: () -> NoReturn - """ + """Encryption materials do not provide decryption keys. + :raises NotImplementedError: because encryption materials do not contain decryption keys """ - raise NotImplementedError('EncryptionMaterials do not provide decryption keys.') + raise NotImplementedError('Encryption materials do not provide decryption keys.') @property def verification_key(self): # type: () -> NoReturn - """ + """Encryption materials do not provide verification keys. + :raises NotImplementedError: because encryption materials do not contain verification keys """ - raise NotImplementedError('EncryptionMaterials do not provide verification keys.') + raise NotImplementedError('Encryption materials do not provide verification keys.') class DecryptionMaterials(CryptographicMaterials): @@ -102,15 +104,17 @@ class DecryptionMaterials(CryptographicMaterials): @property def encryption_key(self): # type: () -> NoReturn - """ + """Decryption materials do not provide encryption keys. + :raises NotImplementedError: because decryption materials do not contain encryption keys """ - raise NotImplementedError('EncryptionMaterials do not provide encryption keys.') + raise NotImplementedError('Decryption materials do not provide encryption keys.') @property def signing_key(self): # type: () -> NoReturn - """ + """Decryption materials do not provide signing keys. + :raises NotImplementedError: because decryption materials do not contain signing keys """ - raise NotImplementedError('EncryptionMaterials do not provide signing keys.') + raise NotImplementedError('Decryption materials do not provide signing keys.') diff --git a/src/dynamodb_encryption_sdk/materials/raw.py b/src/dynamodb_encryption_sdk/materials/raw.py index e72adcdd..f8aea5b4 100644 --- a/src/dynamodb_encryption_sdk/materials/raw.py +++ b/src/dynamodb_encryption_sdk/materials/raw.py @@ -36,6 +36,7 @@ @attr.s class RawEncryptionMaterials(EncryptionMaterials): + # inheritance confuses pylint: disable=abstract-method """Encryption materials for use directly with delegated keys. .. note:: @@ -48,6 +49,7 @@ class RawEncryptionMaterials(EncryptionMaterials): :type encryption_key: dynamodb_encryption_sdk.delegated_keys.DelegatedKey :param dict material_description: Material description to use with these cryptographic materials """ + _signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _encryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _material_description = attr.ib( @@ -96,6 +98,7 @@ def encryption_key(self): @attr.s class RawDecryptionMaterials(DecryptionMaterials): + # inheritance confuses pylint: disable=abstract-method """Encryption materials for use directly with delegated keys. .. note:: @@ -108,6 +111,7 @@ class RawDecryptionMaterials(DecryptionMaterials): :type decryption_key: dynamodb_encryption_sdk.delegated_keys.DelegatedKey :param dict material_description: Material description to use with these cryptographic materials """ + _verification_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _decryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _material_description = attr.ib( diff --git a/src/dynamodb_encryption_sdk/materials/wrapped.py b/src/dynamodb_encryption_sdk/materials/wrapped.py index 22f70800..070d47f9 100644 --- a/src/dynamodb_encryption_sdk/materials/wrapped.py +++ b/src/dynamodb_encryption_sdk/materials/wrapped.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. """Cryptographic materials to use ephemeral content encryption keys wrapped by delegated keys.""" from __future__ import division + import base64 import copy @@ -26,7 +27,7 @@ from dynamodb_encryption_sdk.internal.validators import dictionary_validator from dynamodb_encryption_sdk.materials import CryptographicMaterials -__all__ = ('WrappedRawCryptographicMaterials',) +__all__ = ('WrappedCryptographicMaterials',) _DEFAULT_CONTENT_ENCRYPTION_ALGORITHM = 'AES/256' _WRAPPING_TRANSFORMATION = { 'AES': 'AESWrap', @@ -57,6 +58,7 @@ class WrappedCryptographicMaterials(CryptographicMaterials): :param dict material_description: Material description to use with these cryptographic materials """ + _signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _wrapping_key = attr.ib( validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), @@ -74,17 +76,18 @@ class WrappedCryptographicMaterials(CryptographicMaterials): def __attrs_post_init__(self): """Prepare the content key.""" - self._content_key_algorithm = self.material_description.get( + self._content_key_algorithm = self.material_description.get( # pylint: disable=attribute-defined-outside-init MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value, _DEFAULT_CONTENT_ENCRYPTION_ALGORITHM ) if MaterialDescriptionKeys.WRAPPED_DATA_KEY.value in self.material_description: - self._content_key = self._content_key_from_material_description() + self._content_key = self._content_key_from_material_description() # noqa pylint: disable=attribute-defined-outside-init else: - self._content_key, self._material_description = self._generate_content_key() + self._content_key, self._material_description = self._generate_content_key() # noqa pylint: disable=attribute-defined-outside-init - def _wrapping_transformation(self, algorithm): + @staticmethod + def _wrapping_transformation(algorithm): """Convert the specified algorithm name to the desired wrapping algorithm transformation. :param str algorithm: Algorithm name diff --git a/src/dynamodb_encryption_sdk/structures.py b/src/dynamodb_encryption_sdk/structures.py index 979095f4..b1d4f6cc 100644 --- a/src/dynamodb_encryption_sdk/structures.py +++ b/src/dynamodb_encryption_sdk/structures.py @@ -10,10 +10,10 @@ # 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 attr +"""Common structures used by the DynamoDB Encryption Client.""" import copy +import attr import six from dynamodb_encryption_sdk.exceptions import InvalidArgumentError @@ -24,7 +24,7 @@ __all__ = ('EncryptionContext', 'AttributeActions', 'TableIndex', 'TableInfo') -def _validate_attribute_values_are_ddb_items(instance, attribute, value): +def _validate_attribute_values_are_ddb_items(instance, attribute, value): # pylint: disable=unused-argument """Validate that dictionary values in ``value`` match the structure of DynamoDB JSON items. @@ -40,6 +40,7 @@ def _validate_attribute_values_are_ddb_items(instance, attribute, value): @attr.s class EncryptionContext(object): + # pylint: disable=too-few-public-methods """Additional information about an encryption request. :param str table_name: Table name @@ -48,6 +49,7 @@ class EncryptionContext(object): :param dict attributes: Plaintext item attributes :param dict material_description: Material description to use with this request """ + table_name = attr.ib( validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), default=None @@ -82,6 +84,7 @@ class AttributeActions(object): :type default_action: dynamodb_encryption_sdk.identifiers.ItemAction :param dict attribute_actions: Dictionary mapping attribute names to specific actions """ + default_action = attr.ib( validator=attr.validators.instance_of(ItemAction), default=ItemAction.ENCRYPT_AND_SIGN @@ -101,7 +104,8 @@ def __attrs_post_init__(self): # Enums are not hashable, but their names are unique _unique_actions = set([self.default_action.name]) _unique_actions.update(set([action.name for action in self.attribute_actions.values()])) - self.take_no_actions = _unique_actions == set([ItemAction.DO_NOTHING.name]) + no_actions = _unique_actions == set([ItemAction.DO_NOTHING.name]) + self.take_no_actions = no_actions # attrs confuses pylint: disable=attribute-defined-outside-init def action(self, attribute_name): # (text) -> ItemAction @@ -159,11 +163,13 @@ def __add__(self, other): @attr.s class TableIndex(object): + # pylint: disable=too-few-public-methods """Describes a table index. :param str partition: Name of the partition attribute :param str sort: Name of the sort attribute (optional) """ + partition = attr.ib(validator=attr.validators.instance_of(six.string_types)) sort = attr.ib( validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), @@ -172,7 +178,7 @@ class TableIndex(object): def __attrs_post_init__(self): """Set the ``attributes`` attribute for ease of access later.""" - self.attributes = set([self.partition]) + self.attributes = set([self.partition]) # attrs confuses pylint: disable=attribute-defined-outside-init if self.sort is not None: self.attributes.add(self.sort) @@ -204,7 +210,7 @@ def from_key_schema(cls, key_schema): @attr.s class TableInfo(object): - """Description of a DynamoDB table. + """Describes a DynamoDB table. :param str name: Table name :param bool all_encrypting_secondary_indexes: Should we allow secondary index attributes to be encrypted? @@ -213,6 +219,7 @@ class TableInfo(object): :param secondary_indexes: Set of TableIndex objects describing any secondary indexes :type secondary_indexes: set of dynamodb_encryption_sdk.structures.TableIndex """ + name = attr.ib(validator=attr.validators.instance_of(six.string_types)) _primary_index = attr.ib( validator=attr.validators.optional(attr.validators.instance_of(TableIndex)), diff --git a/src/pylintrc b/src/pylintrc index de56ef0f..43812946 100644 --- a/src/pylintrc +++ b/src/pylintrc @@ -1,6 +1,10 @@ [BASIC] # Allow function names up to 50 characters function-rgx = [a-z_][a-z0-9_]{2,50}$ +# Whitelist argument names: iv +argument-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$) +# Whitelist variable names: iv +variable-rgx = ([a-z_][a-z0-9_]{2,30}$)|(^iv$) [DESIGN] max-args = 10