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
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 )
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)
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')
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
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, )
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