def get_or_create_provider(self, material_name, version):
        # type: (Text, int) -> CryptographicMaterialsProvider
        """Obtain a cryptographic materials provider identified by a name and version.

        If the requested version does not exist, a new one will be created.

        :param str material_name: Material to locate
        :param int version: Version of material to locate
        :returns: cryptographic materials provider
        :rtype: CryptographicMaterialsProvider
        :raises InvalidVersionError: if the requested version is not available and cannot be created
        """
        encryption_key = JceNameLocalDelegatedKey.generate(
            MetaStoreValues.ENCRYPTION_ALGORITHM.value,
            MetaStoreValues.KEY_BITS.value)
        signing_key = JceNameLocalDelegatedKey.generate(
            MetaStoreValues.INTEGRITY_ALGORITHM.value,
            MetaStoreValues.KEY_BITS.value)
        encryption_key, signing_key = self._save_or_load_materials(
            material_name, version, encryption_key, signing_key)
        return WrappedCryptographicMaterialsProvider(
            signing_key=signing_key,
            wrapping_key=encryption_key,
            unwrapping_key=encryption_key,
            material_description=self._material_description(
                material_name, version),
        )
def test_key_generation(client):
    collection = client.db.key_store

    key_bytes = os.urandom(32)

    wrapping_key = JceNameLocalDelegatedKey(
        key=key_bytes,
        algorithm="AES",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    signing_key = JceNameLocalDelegatedKey(
        key=key_bytes,
        algorithm="HmacSHA512",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    wrapped_cmp = WrappedCryptographicMaterialsProvider(
        wrapping_key=wrapping_key,
        unwrapping_key=wrapping_key,
        signing_key=signing_key,
    )

    key_store = MongodbKeyStore(collection=collection,
                                materials_provider=wrapped_cmp)

    key_id = "key1"
    new_main_key = key_store.create_main_key(key_id)
    main_key = key_store.get_main_key(key_id)

    assert main_key.key_id == new_main_key.key_id
    assert main_key.key_bytes == new_main_key.key_bytes
Example #3
0
    def _build_materials(
        self,
        encryption_context: EncryptionContext,
    ) -> WrappedCryptographicMaterials:
        """Construct
        :param EncryptionContext encryption_context: Encryption context for request
        :returns: Wrapped cryptographic materials
        :rtype: WrappedCryptographicMaterials
        """
        material_description = self._material_description.copy()
        material_description.update(encryption_context.material_description)

        main_key = self._key_store.get_main_key(material_description["key_id"])

        wrapping_key = JceNameLocalDelegatedKey(
            key=main_key.key_bytes,
            algorithm="AES",
            key_type=EncryptionKeyType.SYMMETRIC,
            key_encoding=KeyEncodingType.RAW,
        )
        signing_key = JceNameLocalDelegatedKey(
            key=main_key.key_bytes,
            algorithm="HmacSHA512",
            key_type=EncryptionKeyType.SYMMETRIC,
            key_encoding=KeyEncodingType.RAW,
        )
        return WrappedCryptographicMaterials(
            wrapping_key=wrapping_key,
            unwrapping_key=wrapping_key,
            signing_key=signing_key,
            material_description=material_description,
        )
def test_wrapped_rsa_encrypted_table(ddb_table_name):
    wrapping_key_bytes = JceNameLocalDelegatedKey.generate("RSA", 4096).key
    signing_key_bytes = JceNameLocalDelegatedKey.generate(
        "SHA512withRSA", 4096).key
    wrapped_rsa_encrypted_table.encrypt_item(ddb_table_name,
                                             wrapping_key_bytes,
                                             signing_key_bytes)
def test_no_decryption_key_but_decryption_requested(actions,
                                                    parametrized_item):
    encryption_key = JceNameLocalDelegatedKey.generate("AES", 256)
    signing_key = JceNameLocalDelegatedKey.generate("HmacSHA256", 256)
    encrypting_cmp = StaticCryptographicMaterialsProvider(
        encryption_materials=RawEncryptionMaterials(
            encryption_key=encryption_key, signing_key=signing_key))
    decrypting_cmp = StaticCryptographicMaterialsProvider(
        decryption_materials=RawDecryptionMaterials(
            verification_key=signing_key))

    encrypted_item = encrypt_python_item(
        parametrized_item,
        CryptoConfig(materials_provider=encrypting_cmp,
                     encryption_context=EncryptionContext(),
                     attribute_actions=actions),
    )

    with pytest.raises(DecryptionError) as excinfo:
        decrypt_python_item(
            encrypted_item,
            CryptoConfig(materials_provider=decrypting_cmp,
                         encryption_context=EncryptionContext(),
                         attribute_actions=actions),
        )

    excinfo.match(
        "Attribute actions ask for some attributes to be decrypted but no decryption key is available"
    )
def wrapped_cmp():
    wrapping_key = JceNameLocalDelegatedKey.generate("AES", 256)
    signing_key = JceNameLocalDelegatedKey.generate("HmacSHA512", 256)
    cmp = WrappedCryptographicMaterialsProvider(signing_key=signing_key,
                                                wrapping_key=wrapping_key,
                                                unwrapping_key=wrapping_key)
    return cmp
def test_wrapped_symmetric_encrypted_table(ddb_table_name):
    wrapping_key_bytes = JceNameLocalDelegatedKey.generate("AES", 256).key
    signing_key_bytes = JceNameLocalDelegatedKey.generate("HmacSHA512",
                                                          256).key
    wrapped_symmetric_encrypted_table.encrypt_item(ddb_table_name,
                                                   wrapping_key_bytes,
                                                   signing_key_bytes)
    def _load_materials(self, material_name, version):
        # type: (Text, int) -> Tuple[JceNameLocalDelegatedKey, JceNameLocalDelegatedKey]
        """Load materials from table.

        :returns: Materials loaded into delegated keys
        :rtype: tuple(JceNameLocalDelegatedKey)
        """
        _LOGGER.debug('Loading material "%s" version %d from MetaStore table',
                      material_name, version)
        key = {
            MetaStoreAttributeNames.PARTITION.value: material_name,
            MetaStoreAttributeNames.SORT.value: version
        }
        response = self._encrypted_table.get_item(Key=key)
        try:
            item = response["Item"]
        except KeyError:
            raise InvalidVersionError('Version not found: "{}#{}"'.format(
                material_name, version))

        try:
            encryption_key_kwargs = dict(
                key=item[MetaStoreAttributeNames.ENCRYPTION_KEY.value].value,
                algorithm=item[
                    MetaStoreAttributeNames.ENCRYPTION_ALGORITHM.value],
                key_type=EncryptionKeyType.SYMMETRIC,
                key_encoding=KeyEncodingType.RAW,
            )
            signing_key_kwargs = dict(
                key=item[MetaStoreAttributeNames.INTEGRITY_KEY.value].value,
                algorithm=item[
                    MetaStoreAttributeNames.INTEGRITY_ALGORITHM.value],
                key_type=EncryptionKeyType.SYMMETRIC,
                key_encoding=KeyEncodingType.RAW,
            )
        except KeyError:
            raise Exception("Invalid record")

        # need to handle if the material type version is not in the item
        # https://github.com/aws/aws-dynamodb-encryption-python/issues/140
        if item[MetaStoreAttributeNames.MATERIAL_TYPE_VERSION.
                value] != MetaStoreValues.MATERIAL_TYPE_VERSION.value:
            raise InvalidVersionError('Unsupported material type: "{}"'.format(
                item[MetaStoreAttributeNames.MATERIAL_TYPE_VERSION.value]))

        encryption_key = JceNameLocalDelegatedKey(**encryption_key_kwargs)
        signing_key = JceNameLocalDelegatedKey(**signing_key_kwargs)
        return encryption_key, signing_key
    def _generate_content_key(self):
        """Generate the content encryption key and create a new material description containing
        necessary information about the content and wrapping keys.

        :returns content key and new material description
        :rtype: tuple containing DelegatedKey and dict
        """
        if self._wrapping_key is None:
            raise WrappingError("Cryptographic materials cannot be generated: no wrapping key")

        wrapping_algorithm = self.material_description.get(
            MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value,
            self._wrapping_transformation(self._wrapping_key.algorithm),
        )
        args = self._content_key_algorithm.split("/", 1)
        content_algorithm = args[0]
        try:
            content_key_length = int(args[1])
        except IndexError:
            content_key_length = None
        content_key = JceNameLocalDelegatedKey.generate(algorithm=content_algorithm, key_length=content_key_length)
        wrapped_key = self._wrapping_key.wrap(
            algorithm=wrapping_algorithm, content_key=content_key.key, additional_associated_data=None
        )
        new_material_description = self.material_description.copy()
        new_material_description.update(
            {
                MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: base64.b64encode(wrapped_key),
                MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value: self._content_key_algorithm,
                MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value: wrapping_algorithm,
            }
        )
        return content_key, new_material_description
def _load_key(key):
    key_material = base64.b64decode(key["material"])
    key_type = _KEY_TYPE[key["type"].upper()]
    key_encoding = _KEY_ENCODING[key["encoding"].upper()]
    return JceNameLocalDelegatedKey(
        key=key_material, algorithm=key["algorithm"], key_type=key_type, key_encoding=key_encoding
    )
Example #11
0
def test_no_encryption_key():
    signing_key = JceNameLocalDelegatedKey.generate('HmacSHA512', 256)
    encryption_materials = RawEncryptionMaterials(signing_key=signing_key)

    with pytest.raises(AttributeError) as excinfo:
        encryption_materials.encryption_key

    excinfo.match('No encryption key available')
def _load_key(key):
    key_material = base64.b64decode(key['material'])
    key_type = _KEY_TYPE[key['type'].upper()]
    key_encoding = _KEY_ENCODING[key['encoding'].upper()]
    return JceNameLocalDelegatedKey(key=key_material,
                                    algorithm=key['algorithm'],
                                    key_type=key_type,
                                    key_encoding=key_encoding)
Example #13
0
def test_no_decryption_key():
    verification_key = JceNameLocalDelegatedKey.generate('HmacSHA512', 256)
    decryption_materials = RawDecryptionMaterials(
        verification_key=verification_key)

    with pytest.raises(AttributeError) as excinfo:
        decryption_materials.decryption_key

    excinfo.match('No decryption key available')
Example #14
0
    def _load_materials(self, material_name, version):
        # type: (Text, int) -> Tuple[JceNameLocalDelegatedKey, JceNameLocalDelegatedKey]
        """Load materials from table.

        :returns: Materials loaded into delegated keys
        :rtype: tuple(JceNameLocalDelegatedKey)
        """
        key = {
            MetaStoreAttributeNames.PARTITION.value: material_name,
            MetaStoreAttributeNames.SORT.value: version
        }
        response = self._encrypted_table.get_item(Key=key)
        try:
            item = response['Item']
        except KeyError:
            raise InvalidVersionError('Version not found: "{}#{}"'.format(
                material_name, version))

        try:
            encryption_key_kwargs = dict(
                key=item[MetaStoreAttributeNames.ENCRYPTION_KEY.value].value,
                algorithm=item[
                    MetaStoreAttributeNames.ENCRYPTION_ALGORITHM.value],
                key_type=EncryptionKeyType.SYMMETRIC,
                key_encoding=KeyEncodingType.RAW)
            signing_key_kwargs = dict(
                key=item[MetaStoreAttributeNames.INTEGRITY_KEY.value].value,
                algorithm=item[
                    MetaStoreAttributeNames.INTEGRITY_ALGORITHM.value],
                key_type=EncryptionKeyType.SYMMETRIC,
                key_encoding=KeyEncodingType.RAW)
        except KeyError:
            raise Exception('TODO: Invalid record')

        # TODO: handle if the material type version is not in the item
        if item[MetaStoreAttributeNames.MATERIAL_TYPE_VERSION.
                value] != MetaStoreValues.MATERIAL_TYPE_VERSION.value:
            raise InvalidVersionError('Unsupported material type: "{}"'.format(
                item[MetaStoreAttributeNames.MATERIAL_TYPE_VERSION.value]))

        encryption_key = JceNameLocalDelegatedKey(**encryption_key_kwargs)
        signing_key = JceNameLocalDelegatedKey(**signing_key_kwargs)
        return encryption_key, signing_key
def test_no_encryption_key_but_encryption_requested(actions, parametrized_item):
    signing_key = JceNameLocalDelegatedKey.generate("HmacSHA256", 256)
    cmp = StaticCryptographicMaterialsProvider(encryption_materials=RawEncryptionMaterials(signing_key=signing_key))
    crypto_config = CryptoConfig(
        materials_provider=cmp, encryption_context=EncryptionContext(), attribute_actions=actions
    )

    with pytest.raises(EncryptionError) as excinfo:
        encrypt_python_item(parametrized_item, crypto_config)

    excinfo.match("Attribute actions ask for some attributes to be encrypted but no encryption key is available")
def test_key_generation():
    table_name = "key_store"

    DynamodbKeyStore.create_table(
        client=boto3.client("dynamodb", region_name="us-east-1"),
        table_name=table_name,
    )

    table = boto3.resource("dynamodb",
                           region_name="us-east-1").Table(table_name)

    key_bytes = os.urandom(32)

    wrapping_key = JceNameLocalDelegatedKey(
        key=key_bytes,
        algorithm="AES",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    signing_key = JceNameLocalDelegatedKey(
        key=key_bytes,
        algorithm="HmacSHA512",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    wrapped_cmp = WrappedCryptographicMaterialsProvider(
        wrapping_key=wrapping_key,
        unwrapping_key=wrapping_key,
        signing_key=signing_key,
    )

    key_store = DynamodbKeyStore(table=table, materials_provider=wrapped_cmp)

    key_id = "key1"
    new_main_key = key_store.create_main_key(key_id)
    main_key = key_store.get_main_key(key_id)

    assert main_key.key_id == new_main_key.key_id
    assert main_key.key_bytes == new_main_key.key_bytes
Example #17
0
def create_in_memory_key_store():
    key_bytes = os.urandom(32)

    wrapping_key = JceNameLocalDelegatedKey(
        key=key_bytes,
        algorithm="AES",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    signing_key = JceNameLocalDelegatedKey(
        key=key_bytes,
        algorithm="HmacSHA512",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    wrapped_cmp = WrappedCryptographicMaterialsProvider(
        wrapping_key=wrapping_key,
        unwrapping_key=wrapping_key,
        signing_key=signing_key,
    )

    key_store = InMemoryKeyStore(materials_provider=wrapped_cmp)

    return key_store
def test_only_sign_item(parametrized_item):
    signing_key = JceNameLocalDelegatedKey.generate("HmacSHA256", 256)
    cmp = StaticCryptographicMaterialsProvider(
        encryption_materials=RawEncryptionMaterials(signing_key=signing_key),
        decryption_materials=RawDecryptionMaterials(verification_key=signing_key),
    )
    actions = AttributeActions(default_action=CryptoAction.SIGN_ONLY)
    crypto_config = CryptoConfig(
        materials_provider=cmp, encryption_context=EncryptionContext(), attribute_actions=actions
    )

    signed_item = encrypt_python_item(parametrized_item, crypto_config)
    material_description = signed_item[ReservedAttributes.MATERIAL_DESCRIPTION.value].value
    assert MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value.encode("utf-8") not in material_description

    decrypt_python_item(signed_item, crypto_config)
    def _derive_delegated_key(self, initial_material, key_info, hkdf_info):
        # type: (bytes, KeyInfo, HkdfInfo) -> JceNameLocalDelegatedKey
        """Derive the raw key and use it to build a JceNameLocalDelegatedKey.

        :param bytes initial_material: Initial material to use with KDF
        :param KeyInfo key_info: Key information to use to calculate encryption key
        :param HkdfInfo hkdf_info: Info to use in HKDF calculation
        :returns: Delegated key to use for encryption and decryption
        :rtype: JceNameLocalDelegatedKey
        """
        raw_key = self._hkdf(initial_material, key_info.length // 8, hkdf_info.value)
        return JceNameLocalDelegatedKey(
            key=raw_key,
            algorithm=key_info.algorithm,
            key_type=EncryptionKeyType.SYMMETRIC,
            key_encoding=KeyEncodingType.RAW,
        )
Example #20
0
def encrypt_item(table_name, aes_wrapping_key_bytes, hmac_signing_key_bytes):
    """Demonstrate use of EncryptedTable to transparently encrypt an item."""
    index_key = {"partition_attribute": "is this", "sort_attribute": 55}
    plaintext_item = {
        "example": "data",
        "some numbers": 99,
        "and some binary": Binary(b"\x00\x01\x02"),
        "leave me": "alone",  # We want to ignore this attribute
    }
    # Collect all of the attributes that will be encrypted (used later).
    encrypted_attributes = set(plaintext_item.keys())
    encrypted_attributes.remove("leave me")
    # Collect all of the attributes that will not be encrypted (used later).
    unencrypted_attributes = set(index_key.keys())
    unencrypted_attributes.add("leave me")
    # Add the index pairs to the item.
    plaintext_item.update(index_key)

    # Create a normal table resource.
    table = boto3.resource("dynamodb").Table(table_name)  # generated code confuse pylint: disable=no-member
    # Create a crypto materials provider using the provided wrapping and signing keys.
    wrapping_key = JceNameLocalDelegatedKey(
        key=aes_wrapping_key_bytes,
        algorithm="AES",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    signing_key = JceNameLocalDelegatedKey(
        key=hmac_signing_key_bytes,
        algorithm="HmacSHA512",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    )
    wrapped_cmp = WrappedCryptographicMaterialsProvider(
        wrapping_key=wrapping_key,
        unwrapping_key=wrapping_key,
        signing_key=signing_key)
    # Create attribute actions that tells the encrypted table to encrypt all attributes except one.
    actions = AttributeActions(
        default_action=CryptoAction.ENCRYPT_AND_SIGN,
        attribute_actions={"leave me": CryptoAction.DO_NOTHING})
    # Use these objects to create an encrypted table resource.
    encrypted_table = EncryptedTable(table=table,
                                     materials_provider=wrapped_cmp,
                                     attribute_actions=actions)

    # Put the item to the table, using the encrypted table resource to transparently encrypt it.
    encrypted_table.put_item(Item=plaintext_item)

    # Get the encrypted item using the standard table resource.
    encrypted_item = table.get_item(Key=index_key)["Item"]

    # Get the item using the encrypted table resource, transparently decyrpting it.
    decrypted_item = encrypted_table.get_item(Key=index_key)["Item"]

    # Verify that all of the attributes are different in the encrypted item
    for name in encrypted_attributes:
        assert encrypted_item[name] != plaintext_item[name]
        assert decrypted_item[name] == plaintext_item[name]

    # Verify that all of the attributes that should not be encrypted were not.
    for name in unencrypted_attributes:
        assert decrypted_item[name] == encrypted_item[name] == plaintext_item[
            name]

    # Clean up the item
    encrypted_table.delete_item(Key=index_key)
def test_warn_on_short_keys(caplog, algorithm, key_bits, too_short, error_message):
    with caplog.at_level(logging.DEBUG):
        _test = JceNameLocalDelegatedKey.generate(algorithm, key_bits)

    logging_results = caplog.text
    assert (too_short and error_message in logging_results) or (not too_short and error_message not in logging_results)
    'aws-kms-ec-attr':
    '*keys*',
    'amzn-ddb-wrap-alg':
    'kms',
    'amzn-ddb-env-alg':
    'AES/256',
    'amzn-ddb-sig-alg':
    'HmacSHA256/256',
    'amzn-ddb-env-key':
    base64.b64encode(
        _DERIVED_KEYS['encrypted_initial_material']).decode('utf-8')
}
_DELEGATED_KEYS = {
    'encryption':
    JceNameLocalDelegatedKey(key=_DERIVED_KEYS['encryption_key'],
                             algorithm='AES',
                             key_type=EncryptionKeyType.SYMMETRIC,
                             key_encoding=KeyEncodingType.RAW),
    'signing':
    JceNameLocalDelegatedKey(key=_DERIVED_KEYS['mac_key'],
                             algorithm='HmacSHA256',
                             key_type=EncryptionKeyType.SYMMETRIC,
                             key_encoding=KeyEncodingType.RAW)
}


@pytest.fixture
def patch_boto3_session(mocker):
    mocker.patch.object(
        dynamodb_encryption_sdk.material_providers.aws_kms.boto3.session,
        'Session')
    return dynamodb_encryption_sdk.material_providers.aws_kms.boto3.session.Session
        b"\x1a\xc2\xa5(i\x0c4\x10\xe8\xe2\xf3\x17}\t\xd6"
    ),  # encrypted using our public-test CMK in us-west-2
    "encryption_key": b"\xb3~{,Z\x80\x7f\x82I\xe5<h\x12\x16\x7fZ\xac\xe8]\xbc\x16\x92s\x0e\xe7\xca\xd8|X\xbf\xc32",
    "mac_key": b"w\xb5O*\xa75\xbc4\x9bn}\xf4J\xa6\xfb\xb5F\xa4,\xde\x8b\xe6hkpt\xd8\\i\xef#\xce",
}
_DEFAULT_ADDITIONAL_MATERIAL_DESCRIPTION = {
    "aws-kms-ec-attr": "*keys*",
    "amzn-ddb-wrap-alg": "kms",
    "amzn-ddb-env-alg": "AES/256",
    "amzn-ddb-sig-alg": "HmacSHA256/256",
    "amzn-ddb-env-key": base64.b64encode(_DERIVED_KEYS["encrypted_initial_material"]).decode("utf-8"),
}
_DELEGATED_KEYS = {
    "encryption": JceNameLocalDelegatedKey(
        key=_DERIVED_KEYS["encryption_key"],
        algorithm="AES",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    ),
    "signing": JceNameLocalDelegatedKey(
        key=_DERIVED_KEYS["mac_key"],
        algorithm="HmacSHA256",
        key_type=EncryptionKeyType.SYMMETRIC,
        key_encoding=KeyEncodingType.RAW,
    ),
}


@pytest.fixture
def patch_boto3_session(mocker):
    mocker.patch.object(dynamodb_encryption_sdk.material_providers.aws_kms.boto3.session, "Session")
    return dynamodb_encryption_sdk.material_providers.aws_kms.boto3.session.Session
def test_generate_correct_key_length(algorithm, requested_bits, expected_bits,
                                     length_finder):
    test = JceNameLocalDelegatedKey.generate(algorithm, requested_bits)

    assert length_finder(test.key) == expected_bits