diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java index 3fc634f3b436..34c43b9a4912 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java @@ -14,6 +14,7 @@ import com.azure.cosmos.implementation.caches.AsyncCache; import com.azure.cosmos.models.ClientEncryptionPolicy; import com.azure.cosmos.models.CosmosClientEncryptionKeyProperties; +import com.azure.cosmos.models.CosmosContainerResponse; import com.microsoft.data.encryption.cryptography.EncryptionKeyStoreProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,16 +61,16 @@ Mono getClientEncryptionPolicyAsync( cacheKey, null, () -> container.read(). - map(cosmosContainerResponse -> cosmosContainerResponse.getProperties().getClientEncryptionPolicy())); + map(cosmosContainerResponse -> getClientEncryptionPolicyWithVersionValidation(cosmosContainerResponse))); } else { return this.clientEncryptionPolicyCacheByContainerId.getAsync( cacheKey, null, - () -> container.read().map(cosmosContainerResponse -> cosmosContainerResponse.getProperties().getClientEncryptionPolicy())) + () -> container.read().map(cosmosContainerResponse -> getClientEncryptionPolicyWithVersionValidation(cosmosContainerResponse))) .flatMap(clientEncryptionPolicy -> this.clientEncryptionPolicyCacheByContainerId.getAsync( cacheKey, clientEncryptionPolicy, - () -> container.read().map(cosmosContainerResponse -> cosmosContainerResponse.getProperties().getClientEncryptionPolicy()))); + () -> container.read().map(cosmosContainerResponse -> getClientEncryptionPolicyWithVersionValidation(cosmosContainerResponse)))); } } @@ -173,4 +174,14 @@ public CosmosEncryptionAsyncDatabase getCosmosEncryptionAsyncDatabase(String dat public void close() { cosmosAsyncClient.close(); } + + private ClientEncryptionPolicy getClientEncryptionPolicyWithVersionValidation(CosmosContainerResponse cosmosContainerResponse) { + ClientEncryptionPolicy clientEncryptionPolicy = cosmosContainerResponse.getProperties().getClientEncryptionPolicy(); + if (clientEncryptionPolicy.getPolicyFormatVersion() > 1) { + throw new UnsupportedOperationException("This version of the Encryption library cannot be used with this " + + "container. Please upgrade to the latest version of the same."); + } + + return clientEncryptionPolicy; + } } diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncDatabase.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncDatabase.java index bdb0333b4b25..ba9faf66cafd 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncDatabase.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncDatabase.java @@ -82,6 +82,13 @@ public Mono createClientEncryptionKey(String EncryptionKeyStoreProvider encryptionKeyStoreProvider = this.cosmosEncryptionAsyncClient.getEncryptionKeyStoreProvider(); + + if (!encryptionKeyStoreProvider.getProviderName().equals(encryptionKeyWrapMetadata.getType())) { + throw new IllegalArgumentException("The EncryptionKeyWrapMetadata Type value does not match with the " + + "ProviderName of EncryptionKeyStoreProvider configured on the Client. Please refer to https://aka" + + ".ms/CosmosClientEncryption for more details."); + } + try { KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.getOrCreate(encryptionKeyWrapMetadata.getName(), encryptionKeyWrapMetadata.getValue(), encryptionKeyStoreProvider, false); @@ -112,6 +119,13 @@ public Mono rewrapClientEncryptionKey(String EncryptionKeyStoreProvider encryptionKeyStoreProvider = this.cosmosEncryptionAsyncClient.getEncryptionKeyStoreProvider(); + + if (!encryptionKeyStoreProvider.getProviderName().equals(newEncryptionKeyWrapMetadata.getType())) { + throw new IllegalArgumentException("The EncryptionKeyWrapMetadata Type value does not match with the " + + "ProviderName of EncryptionKeyStoreProvider configured on the Client. Please refer to https://aka" + + ".ms/CosmosClientEncryption for more details."); + } + try { CosmosAsyncClientEncryptionKey clientEncryptionKey = this.cosmosAsyncDatabase.getClientEncryptionKey(clientEncryptionKeyId); diff --git a/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/EncryptionCodeSnippet.java b/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/EncryptionCodeSnippet.java index 4e09aafca8b8..45e640b0d1f9 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/EncryptionCodeSnippet.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/EncryptionCodeSnippet.java @@ -133,9 +133,9 @@ void createContainerWithClientEncryptionPolicy(CosmosAsyncClient client) { } void createClientEncryptionKey(CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase) { - EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); - EncryptionKeyWrapMetadata metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); - new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); + EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata("custom", "key1", "tempmetadata1"); + EncryptionKeyWrapMetadata metadata2 = new EncryptionKeyWrapMetadata("custom", "key2", "tempmetadata2"); + new EncryptionKeyWrapMetadata("custom", "key1", "tempmetadata1"); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1", CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block().getProperties(); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2", diff --git a/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/Program.java b/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/Program.java index eecbe3ff3963..1c2caaf5312f 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/Program.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/samples/java/com/azure/cosmos/Program.java @@ -63,6 +63,7 @@ public class Program { private static CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = null; private static CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase = null; private static CosmosEncryptionAsyncContainer cosmosEncryptionAsyncContainer = null; + private static AzureKeyVaultKeyStoreProvider encryptionKeyStoreProvider = null; public static void main(String[] args) throws Exception { try { @@ -98,7 +99,7 @@ private static CosmosEncryptionAsyncClient createClientInstance(Properties confi // This application must have keys/wrapKey and keys/unwrapKey permissions // on the keys that will be used for encryption. TokenCredential tokenCredentials = Program.getTokenCredential(configuration); - AzureKeyVaultKeyStoreProvider encryptionKeyStoreProvider = new AzureKeyVaultKeyStoreProvider(tokenCredentials); + encryptionKeyStoreProvider = new AzureKeyVaultKeyStoreProvider(tokenCredentials); return CosmosEncryptionAsyncClient.createCosmosEncryptionAsyncClient(asyncClient, encryptionKeyStoreProvider); } @@ -132,7 +133,7 @@ private static void initialize(CosmosEncryptionAsyncClient cosmosEncryptionAsync throw new IllegalArgumentException("Please specify a valid MasterKeyUrl in the appSettings.json"); } - EncryptionKeyWrapMetadata metadata = new EncryptionKeyWrapMetadata(dataEncryptionKeyId, masterKeyUrlFromConfig); + EncryptionKeyWrapMetadata metadata = new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), dataEncryptionKeyId, masterKeyUrlFromConfig); /// Generates an encryption key, wraps it using the key wrap metadata provided /// and saves the wrapped encryption key as an asynchronous operation in the Azure Cosmos service. diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/ClientEncryptionKeyTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/ClientEncryptionKeyTest.java new file mode 100644 index 000000000000..4f1446776668 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/ClientEncryptionKeyTest.java @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.encryption; + +import com.azure.cosmos.CosmosAsyncClient; +import com.azure.cosmos.CosmosAsyncDatabase; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.encryption.models.CosmosEncryptionAlgorithm; +import com.azure.cosmos.models.CosmosClientEncryptionKeyProperties; +import com.azure.cosmos.models.EncryptionKeyWrapMetadata; +import com.azure.cosmos.rx.TestSuiteBase; +import com.microsoft.data.encryption.cryptography.EncryptionKeyStoreProvider; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Factory; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class ClientEncryptionKeyTest extends TestSuiteBase { + private CosmosAsyncClient client; + private CosmosAsyncDatabase cosmosAsyncDatabase; + private CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient; + private CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase; + private EncryptionKeyStoreProvider encryptionKeyStoreProvider; + + @Factory(dataProvider = "clientBuilders") + public ClientEncryptionKeyTest(CosmosClientBuilder clientBuilder) { + super(clientBuilder); + } + + @BeforeClass(groups = {"encryption"}, timeOut = SETUP_TIMEOUT) + public void before_CosmosItemTest() { + assertThat(this.client).isNull(); + this.client = getClientBuilder().buildAsyncClient(); + encryptionKeyStoreProvider = new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); + cosmosAsyncDatabase = getSharedCosmosDatabase(this.client); + cosmosEncryptionAsyncClient = CosmosEncryptionAsyncClient.createCosmosEncryptionAsyncClient(this.client, + encryptionKeyStoreProvider); + cosmosEncryptionAsyncDatabase = + cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosAsyncDatabase); + } + + + @AfterClass(groups = {"encryption"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true) + public void afterClass() { + assertThat(this.client).isNotNull(); + this.client.close(); + } + + + @Test(groups = {"encryption"}, timeOut = TIMEOUT) + public void createClientEncryptionKey() { + EncryptionKeyWrapMetadata metadata = + new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), "key1", "tempmetadata1"); + + CosmosClientEncryptionKeyProperties clientEncryptionKey = + cosmosEncryptionAsyncDatabase.createClientEncryptionKey("ClientEncryptionKeyTest1", + CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata).block().getProperties(); + assertThat(clientEncryptionKey.getEncryptionKeyWrapMetadata()).isEqualTo(metadata); + + clientEncryptionKey = + cosmosEncryptionAsyncDatabase.rewrapClientEncryptionKey("ClientEncryptionKeyTest1", metadata).block().getProperties(); + assertThat(clientEncryptionKey.getEncryptionKeyWrapMetadata()).isEqualTo(metadata); + } + + @Test(groups = {"encryption"}, timeOut = TIMEOUT) + public void createClientEncryptionKeyWithException() { + EncryptionKeyWrapMetadata metadata = + new EncryptionKeyWrapMetadata("wrongName", "key1", "tempmetadata1"); + + try { + cosmosEncryptionAsyncDatabase.createClientEncryptionKey("ClientEncryptionKeyTest1", + CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata).block().getProperties(); + fail("createClientEncryptionKey should fail as it has wrong encryptionKeyWrapMetadata type"); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage()).isEqualTo("The EncryptionKeyWrapMetadata Type value does not match with the " + + "ProviderName of EncryptionKeyStoreProvider configured on the Client. Please refer to https://aka" + + ".ms/CosmosClientEncryption for more details."); + } + + try { + cosmosEncryptionAsyncDatabase.rewrapClientEncryptionKey("ClientEncryptionKeyTest1", metadata).block().getProperties(); + + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage()).isEqualTo("The EncryptionKeyWrapMetadata Type value does not match with the " + + "ProviderName of EncryptionKeyStoreProvider configured on the Client. Please refer to https://aka" + + ".ms/CosmosClientEncryption for more details."); + } + } +} diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java index d7d0aec99fb8..75184933f8e8 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java @@ -20,6 +20,7 @@ import com.azure.cosmos.models.PartitionKey; import com.azure.cosmos.models.ThroughputProperties; import com.azure.cosmos.rx.TestSuiteBase; +import com.microsoft.data.encryption.cryptography.EncryptionKeyStoreProvider; import org.mockito.Mockito; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -51,17 +52,17 @@ public CosmosEncryptionClientCachesTest(CosmosClientBuilder clientBuilder) { public void before_CosmosItemTest() { assertThat(this.client).isNull(); this.client = getClientBuilder().buildAsyncClient(); - + EncryptionKeyStoreProvider encryptionKeyStoreProvider = new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); //Creating DB CosmosDatabaseProperties cosmosDatabaseProperties = this.client.createDatabase("TestDBForEncryptionCacheTest" , ThroughputProperties.createManualThroughput(1000)).block().getProperties(); cosmosEncryptionAsyncClient = CosmosEncryptionAsyncClient.createCosmosEncryptionAsyncClient(this.client, - new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider()); + encryptionKeyStoreProvider); cosmosEncryptionAsyncDatabase = cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosDatabaseProperties.getId()); //Create ClientEncryptionKeys - metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); - metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); + metadata1 = new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), "key1", "tempmetadata1"); + metadata2 = new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), "key2", "tempmetadata2"); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1", CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block(); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2", diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java index cea43d44b230..bc0d3eb37101 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java @@ -6,6 +6,7 @@ import com.azure.cosmos.CosmosAsyncClient; import com.azure.cosmos.CosmosAsyncDatabase; import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.encryption.implementation.ReflectionUtils; import com.azure.cosmos.encryption.models.CosmosEncryptionAlgorithm; import com.azure.cosmos.encryption.models.CosmosEncryptionType; import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption; @@ -27,6 +28,7 @@ import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Factory; +import org.testng.annotations.Ignore; import org.testng.annotations.Test; import java.util.ArrayList; @@ -45,6 +47,7 @@ public class EncryptionAsyncApiCrudTest extends TestSuiteBase { private CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient; private CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase; private CosmosEncryptionAsyncContainer cosmosEncryptionAsyncContainer; + private CosmosEncryptionAsyncContainer encryptionContainerWithIncompatiblePolicyVersion; private EncryptionKeyWrapMetadata metadata1; private EncryptionKeyWrapMetadata metadata2; @@ -57,14 +60,15 @@ public EncryptionAsyncApiCrudTest(CosmosClientBuilder clientBuilder) { public void before_CosmosItemTest() { assertThat(this.client).isNull(); this.client = getClientBuilder().buildAsyncClient(); + EncryptionKeyStoreProvider encryptionKeyStoreProvider = new TestEncryptionKeyStoreProvider(); cosmosAsyncDatabase = getSharedCosmosDatabase(this.client); cosmosEncryptionAsyncClient = CosmosEncryptionAsyncClient.createCosmosEncryptionAsyncClient(this.client, - new TestEncryptionKeyStoreProvider()); + encryptionKeyStoreProvider); cosmosEncryptionAsyncDatabase = cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosAsyncDatabase); - metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); - metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); + metadata1 = new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), "key1", "tempmetadata1"); + metadata2 = new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), "key2", "tempmetadata2"); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1", CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block(); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2", @@ -76,6 +80,14 @@ public void before_CosmosItemTest() { properties.setClientEncryptionPolicy(clientEncryptionPolicy); cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block(); cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId); + + ClientEncryptionPolicy clientEncryptionWithPolicyFormatVersion2 = new ClientEncryptionPolicy(getPaths()); + ReflectionUtils.setPolicyFormatVersion(clientEncryptionWithPolicyFormatVersion2, 2); + containerId = UUID.randomUUID().toString(); + properties = new CosmosContainerProperties(containerId, "/mypk"); + properties.setClientEncryptionPolicy(clientEncryptionWithPolicyFormatVersion2); + cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block(); + encryptionContainerWithIncompatiblePolicyVersion = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId); } @AfterClass(groups = {"encryption"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true) @@ -220,7 +232,7 @@ public void queryItemsOnRandomizedEncryption() { } @Test(groups = {"encryption"}, timeOut = TIMEOUT) - public void queryItemsWithContinuationTokenAndPageSize() throws Exception { + public void queryItemsWithContinuationTokenAndPageSize() { List actualIds = new ArrayList<>(); EncryptionPojo properties = getItem(UUID.randomUUID().toString()); cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()), @@ -261,6 +273,22 @@ public void queryItemsWithContinuationTokenAndPageSize() throws Exception { assertThat(finalDocumentCount).isEqualTo(initialDocumentCount); } + @Ignore("Ignoring it temporarily because server always returning policyFormatVersion 0") + @Test(groups = {"encryption"}, timeOut = TIMEOUT) + public void incompatiblePolicyFormatVersion() { + try { + EncryptionPojo properties = getItem(UUID.randomUUID().toString()); + encryptionContainerWithIncompatiblePolicyVersion.createItem(properties, + new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block(); + fail("encryptionContainerWithIncompatiblePolicyVersion crud operation should fail on client encryption " + + "policy " + + "fetch because of policy format version greater than 1"); + } catch (UnsupportedOperationException ex) { + assertThat(ex.getMessage()).isEqualTo("This version of the Encryption library cannot be used with this " + + "container. Please upgrade to the latest version of the same."); + } + } + static void validateResponse(EncryptionPojo originalItem, EncryptionPojo result) { assertThat(result.getId()).isEqualTo(originalItem.getId()); assertThat(result.getNonSensitive()).isEqualTo(originalItem.getNonSensitive()); @@ -368,6 +396,12 @@ public static EncryptionPojo getItem(String documentId) { public static class TestEncryptionKeyStoreProvider extends EncryptionKeyStoreProvider { Map keyInfo = new HashMap<>(); + String providerName = "TEST_KEY_STORE_PROVIDER"; + + @Override + public String getProviderName() { + return providerName; + } public TestEncryptionKeyStoreProvider() { keyInfo.put("tempmetadata1", 1); diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionSyncApiCrudTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionSyncApiCrudTest.java index 958996eb0947..f25b26109c73 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionSyncApiCrudTest.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionSyncApiCrudTest.java @@ -59,13 +59,15 @@ public void before_CosmosItemTest() { this.client = getClientBuilder().buildClient(); this.cosmosDatabase = this.client.getDatabase(getSharedCosmosDatabase(this.cosmosClientAccessor.getCosmosAsyncClient(this.client)).getId()); + EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider encryptionKeyStoreProvider = + new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); this.cosmosEncryptionClient = CosmosEncryptionClient.createCosmosEncryptionClient(this.client, - new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider()); + encryptionKeyStoreProvider); this.cosmosEncryptionDatabase = cosmosEncryptionClient.getCosmosEncryptionDatabase(this.cosmosDatabase); - metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); - metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); + metadata1 = new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), "key1", "tempmetadata1"); + metadata2 = new EncryptionKeyWrapMetadata(encryptionKeyStoreProvider.getProviderName(), "key2", "tempmetadata2"); this.cosmosEncryptionDatabase.createClientEncryptionKey("key3", CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1); this.cosmosEncryptionDatabase.createClientEncryptionKey("key4", diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/EncryptionProcessorAndSettingsTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/EncryptionProcessorAndSettingsTest.java index af5b0848a0e4..ac6a0df46100 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/EncryptionProcessorAndSettingsTest.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/EncryptionProcessorAndSettingsTest.java @@ -3,11 +3,10 @@ package com.azure.cosmos.encryption.implementation; -import com.azure.core.credential.TokenCredential; import com.azure.cosmos.CosmosAsyncContainer; import com.azure.cosmos.encryption.CosmosEncryptionAsyncClient; -import com.azure.cosmos.encryption.EncryptionBridgeInternal; import com.azure.cosmos.encryption.EncryptionAsyncApiCrudTest; +import com.azure.cosmos.encryption.EncryptionBridgeInternal; import com.azure.cosmos.encryption.models.CosmosEncryptionAlgorithm; import com.azure.cosmos.encryption.models.CosmosEncryptionType; import com.azure.cosmos.implementation.Utils; @@ -15,8 +14,6 @@ import com.azure.cosmos.models.ClientEncryptionPolicy; import com.azure.cosmos.models.CosmosClientEncryptionKeyProperties; import com.azure.cosmos.models.EncryptionKeyWrapMetadata; -import com.azure.identity.ClientSecretCredential; -import com.azure.identity.ClientSecretCredentialBuilder; import com.microsoft.data.encryption.cryptography.EncryptionKeyStoreProvider; import org.assertj.core.api.Assertions; import org.bouncycastle.util.encoders.Hex; @@ -29,20 +26,19 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.Properties; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; public class EncryptionProcessorAndSettingsTest { private static final int TIMEOUT = 600000_000; + private static final EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider keyStoreProvider = + new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); @Test(groups = {"unit"}, timeOut = TIMEOUT) public void initializeEncryptionSettingsAsync() { CosmosAsyncContainer cosmosAsyncContainer = Mockito.mock(CosmosAsyncContainer.class); CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = Mockito.mock(CosmosEncryptionAsyncClient.class); - EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider keyStoreProvider = - new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); Mockito.when(cosmosEncryptionAsyncClient.getEncryptionKeyStoreProvider()).thenReturn(keyStoreProvider); Mockito.when(EncryptionBridgeInternal.getClientEncryptionPolicyAsync(cosmosEncryptionAsyncClient, Mockito.any(CosmosAsyncContainer.class), Mockito.anyBoolean())).thenReturn(Mono.just(generateClientEncryptionPolicy())); @@ -74,8 +70,6 @@ public void initializeEncryptionSettingsAsync() { public void withoutInitializeEncryptionSettingsAsync() { CosmosAsyncContainer cosmosAsyncContainer = Mockito.mock(CosmosAsyncContainer.class); CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = Mockito.mock(CosmosEncryptionAsyncClient.class); - EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider keyStoreProvider = - new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); Mockito.when(cosmosEncryptionAsyncClient.getEncryptionKeyStoreProvider()).thenReturn(keyStoreProvider); Mockito.when(EncryptionBridgeInternal.getClientEncryptionPolicyAsync(cosmosEncryptionAsyncClient, Mockito.any(CosmosAsyncContainer.class), Mockito.anyBoolean())).thenReturn(Mono.just(generateClientEncryptionPolicy())); @@ -110,8 +104,6 @@ public void withoutInitializeEncryptionSettingsAsync() { public void encryptionSettingCachedTimeToLive() { CosmosAsyncContainer cosmosAsyncContainer = Mockito.mock(CosmosAsyncContainer.class); CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = Mockito.mock(CosmosEncryptionAsyncClient.class); - EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider keyStoreProvider = - new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); Mockito.when(cosmosEncryptionAsyncClient.getEncryptionKeyStoreProvider()).thenReturn(keyStoreProvider); Mockito.when(EncryptionBridgeInternal.getClientEncryptionPolicyAsync(cosmosEncryptionAsyncClient, Mockito.any(CosmosAsyncContainer.class), Mockito.anyBoolean())).thenReturn(Mono.just(generateClientEncryptionPolicy())); @@ -142,8 +134,6 @@ public void encryptionSettingCachedTimeToLive() { public void invalidClientEncryptionKeyException() throws Exception { CosmosAsyncContainer cosmosAsyncContainer = Mockito.mock(CosmosAsyncContainer.class); CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = Mockito.mock(CosmosEncryptionAsyncClient.class); - EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider keyStoreProvider = - new EncryptionAsyncApiCrudTest.TestEncryptionKeyStoreProvider(); Mockito.when(cosmosEncryptionAsyncClient.getEncryptionKeyStoreProvider()).thenReturn(keyStoreProvider); Mockito.when(EncryptionBridgeInternal.getClientEncryptionPolicyAsync(cosmosEncryptionAsyncClient, Mockito.any(CosmosAsyncContainer.class), Mockito.anyBoolean())).thenReturn(Mono.just(generateClientEncryptionPolicy())); @@ -197,7 +187,8 @@ private CosmosClientEncryptionKeyProperties generateClientEncryptionKeyPropertie byte[] key = Hex.decode(("34 62 52 77 f9 ee 11 9f 04 8c 6f 50 9c e4 c2 5b b3 39 f4 d0 4d c1 6a 32 fa 2b 3b aa" + " " + "ae 1e d9 1c").replace(" ", "")); - EncryptionKeyWrapMetadata metadata = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); + EncryptionKeyWrapMetadata metadata = new EncryptionKeyWrapMetadata(this.keyStoreProvider.getProviderName(), + "key1", "tempmetadata1"); return new CosmosClientEncryptionKeyProperties("key1", CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, key, metadata); } diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/ReflectionUtils.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/ReflectionUtils.java index 5242287a871f..7ceadae804ff 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/ReflectionUtils.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/implementation/ReflectionUtils.java @@ -59,4 +59,8 @@ public static void setCosmosEncryptionAsyncClient(EncryptionProcessor encryption public static void setEncryptionSettings(EncryptionProcessor encryptionProcessor, EncryptionSettings encryptionSettings) { set(encryptionProcessor, encryptionSettings,"encryptionSettings"); } + + public static void setPolicyFormatVersion(ClientEncryptionPolicy clientEncryptionPolicy, int policyFormatVersion) { + set(clientEncryptionPolicy, policyFormatVersion,"policyFormatVersion"); + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentCollection.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentCollection.java index 18eb9ed4dc9a..2dfc84903a66 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentCollection.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentCollection.java @@ -352,7 +352,7 @@ public String getConflictsLink() { */ public ClientEncryptionPolicy getClientEncryptionPolicy() { if (this.clientEncryptionPolicyInternal == null) { - if (super.has(Constants.Properties.INDEXING_POLICY)) { + if (super.has(Constants.Properties.CLIENT_ENCRYPTION_POLICY)) { this.clientEncryptionPolicyInternal = super.getObject(Constants.Properties.CLIENT_ENCRYPTION_POLICY, ClientEncryptionPolicy.class); } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java index feb9b3ff8ed9..44aefa16cf2e 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java @@ -12,6 +12,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; + +import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; /** * Client encryption policy. @@ -76,6 +79,33 @@ public List getIncludedPaths() { return this.includedPaths; } + /** + * Version of the client encryption policy definition. + * @return policyFormatVersion + */ + @Beta(value = Beta.SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public int getPolicyFormatVersion() { + return policyFormatVersion; + } + + void validatePartitionKeyPathsAreNotEncrypted(List> partitionKeyPathTokens) { + checkNotNull(partitionKeyPathTokens, "partitionKeyPathTokens cannot be null"); + List propertiesToEncrypt = + this.includedPaths.stream().map(clientEncryptionIncludedPath -> clientEncryptionIncludedPath.getPath().substring(1)).collect(Collectors.toList()); + + for (List tokensInPath : partitionKeyPathTokens) { + checkNotNull(tokensInPath); + if (tokensInPath.size() > 0) { + String topLevelToken = tokensInPath.get(0); + if (propertiesToEncrypt.contains(topLevelToken)) { + throw new IllegalArgumentException(String.format("Path %s which is part of the partition key " + + "cannot be included" + + " in the ClientEncryptionPolicy.", topLevelToken)); + } + } + } + } + private void validateIncludedPaths(List clientEncryptionIncludedPath) { List includedPathsList = new ArrayList<>(); for (ClientEncryptionIncludedPath path : clientEncryptionIncludedPath) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java index 3d67631e04d0..b45108999a83 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java @@ -4,6 +4,7 @@ import com.azure.cosmos.implementation.DocumentCollection; import com.azure.cosmos.implementation.Resource; +import com.azure.cosmos.implementation.apachecommons.lang.StringUtils; import com.azure.cosmos.util.Beta; import java.time.Instant; @@ -22,6 +23,7 @@ public final class CosmosContainerProperties { private final DocumentCollection documentCollection; + private static final String PARTITION_KEY_TOKEN_DELIMETER = "/"; /** * Constructor @@ -122,6 +124,10 @@ public PartitionKeyDefinition getPartitionKeyDefinition() { */ public CosmosContainerProperties setPartitionKeyDefinition(PartitionKeyDefinition partitionKeyDefinition) { this.documentCollection.setPartitionKey(partitionKeyDefinition); + if (this.getClientEncryptionPolicy() != null) { + this.getClientEncryptionPolicy().validatePartitionKeyPathsAreNotEncrypted(this.getPartitionKeyPathTokensList()); + } + return this; } @@ -309,6 +315,10 @@ public ClientEncryptionPolicy getClientEncryptionPolicy() { */ @Beta(value = Beta.SinceVersion.V4_14_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) public CosmosContainerProperties setClientEncryptionPolicy(ClientEncryptionPolicy value) { + if (value != null) { + value.validatePartitionKeyPathsAreNotEncrypted(this.getPartitionKeyPathTokensList()); + } + this.documentCollection.setClientEncryptionPolicy(value); return this; } @@ -323,4 +333,23 @@ DocumentCollection getV2Collection() { collection.setIndexingPolicy(this.getIndexingPolicy()); return collection; } + + List> getPartitionKeyPathTokensList() { + if (this.getPartitionKeyDefinition() == null) { + throw new IllegalStateException("Container partition key is empty"); + } + + List> partitionKeyPathTokensList = new ArrayList<>(); + for (String path : this.getPartitionKeyDefinition().getPaths()) { + String[] splitPaths = path.split(PARTITION_KEY_TOKEN_DELIMETER); + List splitPathsList = new ArrayList<>(); + for (int i = 0; i < splitPaths.length; i++) { + if (StringUtils.isNotEmpty(splitPaths[i])) { + splitPathsList.add(splitPaths[i]); + } + } + partitionKeyPathTokensList.add(splitPathsList); + } + return partitionKeyPathTokensList; + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java index 60aaa9ad9377..fc6455eaff45 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java @@ -38,17 +38,15 @@ public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source) { /** * Creates a new instance of key wrap metadata based on an existing instance. * + * @param type Type of the metadata. * @param name Name of the metadata. * @param value Value of the metadata. */ - @Beta(value = Beta.SinceVersion.V4_14_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) - public EncryptionKeyWrapMetadata(String name, String value) { - this("custom", name, value); - } - - private EncryptionKeyWrapMetadata(String type, String name, String value) { + @Beta(value = Beta.SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public EncryptionKeyWrapMetadata(String type, String name, String value) { Preconditions.checkNotNull(type, "type is null"); Preconditions.checkNotNull(value, "value is null"); + Preconditions.checkNotNull(name, "name is null"); this.type = type; this.name = name; this.value = value; @@ -90,6 +88,18 @@ public String getName() { return name; } + /** + * Serialized form of metadata. + * Note: This value is saved in the Cosmos DB service. + * implementors of derived implementations should ensure that this does not have (private) key material or + * credential information. + * @return type of metadata. + */ + @Beta(value = Beta.SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public String getType() { + return type; + } + /** * Returns whether the properties of the passed in key wrap metadata matches with those in the current instance. * diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java index e80d224e5f58..3a825e6d1e4a 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java @@ -62,6 +62,8 @@ public enum SinceVersion { /** v4.14.0 */ V4_14_0, /** v4.15.0 */ - V4_15_0; + V4_15_0, + /** v4.16.0 */ + V4_16_0; } } diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosClientEncryptionKeyTest.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosClientEncryptionKeyTest.java index a4e6ae109a6e..1e0f2d0cf363 100644 --- a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosClientEncryptionKeyTest.java +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosClientEncryptionKeyTest.java @@ -47,7 +47,7 @@ public void afterClass() { @Test(groups = {"emulator"}, timeOut = TIMEOUT) public void createClientEncryptionKey() { - EncryptionKeyWrapMetadata encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); + EncryptionKeyWrapMetadata encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("key1", "tempmetadata1", "custom"); byte[] key = Hex.decode(("34 62 52 77 f9 ee 11 9f 04 8c 6f 50 9c e4 c2 5b b3 39 f4 d0 4d c1 6a 32 fa 2b 3b aa " + "ae 1e d9 1c").replace(" ", "")); @@ -66,7 +66,7 @@ public void createClientEncryptionKey() { @Test(groups = {"emulator"}, timeOut = TIMEOUT) public void replaceClientEncryptionKey() { - EncryptionKeyWrapMetadata encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("key2", "tempmetadata1"); + EncryptionKeyWrapMetadata encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("custom", "key2", "tempmetadata1"); byte[] key = Hex.decode(("34 62 52 77 f9 ee 11 9f 04 8c 6f 50 9c e4 c2 5b b3 39 f4 d0 4d c1 6a 32 fa 2b 3b aa " + "ae 1e d9 1c").replace(" ", "")); @@ -80,7 +80,7 @@ public void replaceClientEncryptionKey() { CosmosAsyncClientEncryptionKey clientEncryptionKey = createdDatabase.getClientEncryptionKey("key2"); - encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); + encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("custom", "key2", "tempmetadata2"); cosmosClientEncryptionKeyProperties = keyResponse.getProperties(); cosmosClientEncryptionKeyProperties.setEncryptionKeyWrapMetadata(encryptionKeyWrapMetadata); keyResponse = clientEncryptionKey.replace(cosmosClientEncryptionKeyProperties).block(); @@ -89,7 +89,7 @@ public void replaceClientEncryptionKey() { @Test(groups = {"emulator"}, timeOut = TIMEOUT) public void queryClientEncryptionKeys() { - EncryptionKeyWrapMetadata encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("key3", "tempmetadata1"); + EncryptionKeyWrapMetadata encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("custom", "key3", "tempmetadata1"); byte[] key = Hex.decode(("34 62 52 77 f9 ee 11 9f 04 8c 6f 50 9c e4 c2 5b b3 39 f4 d0 4d c1 6a 32 fa 2b 3b aa " + "ae 1e d9 1c").replace(" ", "")); diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java index 3113442d44d7..3a52ed2901e2 100644 --- a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java @@ -13,21 +13,25 @@ import com.azure.cosmos.implementation.feedranges.FeedRangeInternal; import com.azure.cosmos.implementation.feedranges.FeedRangePartitionKeyRangeImpl; import com.azure.cosmos.implementation.routing.Range; +import com.azure.cosmos.models.ChangeFeedPolicy; import com.azure.cosmos.models.ClientEncryptionIncludedPath; import com.azure.cosmos.models.ClientEncryptionPolicy; -import com.azure.cosmos.models.ChangeFeedPolicy; +import com.azure.cosmos.models.CosmosClientEncryptionKeyProperties; import com.azure.cosmos.models.CosmosContainerProperties; import com.azure.cosmos.models.CosmosContainerRequestOptions; import com.azure.cosmos.models.CosmosContainerResponse; import com.azure.cosmos.models.CosmosQueryRequestOptions; +import com.azure.cosmos.models.EncryptionKeyWrapMetadata; import com.azure.cosmos.models.FeedRange; import com.azure.cosmos.models.IndexingMode; import com.azure.cosmos.models.IndexingPolicy; import com.azure.cosmos.models.PartitionKey; +import com.azure.cosmos.models.PartitionKeyDefinition; import com.azure.cosmos.models.SqlQuerySpec; import com.azure.cosmos.models.ThroughputProperties; import com.azure.cosmos.rx.TestSuiteBase; import com.azure.cosmos.util.CosmosPagedIterable; +import org.spongycastle.util.encoders.Hex; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; @@ -37,14 +41,15 @@ import org.testng.annotations.Test; import reactor.core.publisher.Mono; -import java.util.ArrayList; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; public class CosmosContainerTest extends TestSuiteBase { @@ -62,6 +67,7 @@ public CosmosContainerTest(CosmosClientBuilder clientBuilder) { public void before_CosmosContainerTest() { client = getClientBuilder().buildClient(); createdDatabase = createSyncDatabase(client, preExistingDatabaseId); + createEncryptionKey(); } @AfterClass(groups = {"emulator"}, timeOut = 3 * SHUTDOWN_TIMEOUT, alwaysRun = true) @@ -109,13 +115,13 @@ public void createContainer_withEncryption() { path1.setPath("/path1"); path1.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256"); path1.setEncryptionType("Randomized"); - path1.setClientEncryptionKeyId("key1"); + path1.setClientEncryptionKeyId("containerTestKey1"); ClientEncryptionIncludedPath path2 = new ClientEncryptionIncludedPath(); path2.setPath("/path2"); path2.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256"); path2.setEncryptionType("Deterministic"); - path2.setClientEncryptionKeyId("key2"); + path2.setClientEncryptionKeyId("containerTestKey2"); List paths = new ArrayList<>(); paths.add(path1); @@ -129,6 +135,83 @@ public void createContainer_withEncryption() { validateContainerResponseWithEncryption(containerProperties, containerResponse, clientEncryptionPolicy); } + @Test(groups = {"emulator"}, timeOut = TIMEOUT) + public void createContainer_withPartitionKeyInEncryption() { + String collectionName = UUID.randomUUID().toString(); + CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName); + + ClientEncryptionIncludedPath path1 = new ClientEncryptionIncludedPath(); + path1.setPath("/mypk"); + path1.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256"); + path1.setEncryptionType("Randomized"); + path1.setClientEncryptionKeyId("containerTestKey1"); + + ClientEncryptionIncludedPath path2 = new ClientEncryptionIncludedPath(); + path2.setPath("/path2"); + path2.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256"); + path2.setEncryptionType("Deterministic"); + path2.setClientEncryptionKeyId("containerTestKey2"); + + List paths = new ArrayList<>(); + paths.add(path1); + paths.add(path2); + + ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths); + CosmosContainerResponse containerResponse = null; + + //Verify partition key in CosmosContainerProperties constructor with encrypted field. + try { + containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy); + containerResponse = createdDatabase.createContainer(containerProperties); + fail("createContainer should fail as mypk which is part of the partition key cannot be included in the " + + "ClientEncryptionPolicy."); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be included in" + + " the ClientEncryptionPolicy."); + } + + + //Verify for composite key + collectionName = UUID.randomUUID().toString(); + containerProperties = new CosmosContainerProperties(collectionName, "/mypk/mypk1"); + try { + containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy); + containerResponse = createdDatabase.createContainer(containerProperties); + fail("createContainer should fail as mypk which is part of the partition key cannot be included in the " + + "ClientEncryptionPolicy."); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be included in" + + " the ClientEncryptionPolicy."); + } + + + //Verify setPartitionKeyDefinition with encrypted field. + collectionName = UUID.randomUUID().toString(); + containerProperties = new CosmosContainerProperties(collectionName, "/differentKey"); + try { + containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy); + PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition(); + List keyPaths = new ArrayList<>(); + keyPaths.add("/mypk"); + partitionKeyDefinition.setPaths(keyPaths); + containerProperties.setPartitionKeyDefinition(partitionKeyDefinition); + containerResponse = createdDatabase.createContainer(containerProperties); + fail("createContainer should fail as mypk which is part of the partition key cannot be included in the " + + "ClientEncryptionPolicy."); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be included in" + + " the ClientEncryptionPolicy."); + } + + //This should pass as we check only the first key of the composite key. + collectionName = UUID.randomUUID().toString(); + containerProperties = new CosmosContainerProperties(collectionName, "/mypk1/mypk"); + containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy); + containerResponse = createdDatabase.createContainer(containerProperties); + assertThat(containerResponse.getRequestCharge()).isGreaterThan(0); + validateContainerResponseWithEncryption(containerProperties, containerResponse, clientEncryptionPolicy); + } + @DataProvider public static Object[][] analyticalTTLProvider() { return new Object[][]{ @@ -740,4 +823,19 @@ private void validateContainerResponseWithEncryption(CosmosContainerProperties c } } } + + private void createEncryptionKey() { + EncryptionKeyWrapMetadata encryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata("key1", "tempmetadata1", "custom"); + byte[] key = Hex.decode(("34 62 52 77 f9 ee 11 9f 04 8c 6f 50 9c e4 c2 5b b3 39 f4 d0 4d c1 6a 32 fa 2b 3b aa " + + "ae 1e d9 1c").replace(" ", "")); + + CosmosClientEncryptionKeyProperties cosmosClientEncryptionKeyProperties1 = + new CosmosClientEncryptionKeyProperties("containerTestKey1", "AEAD_AES_256_CBC_HMAC_SHA256", key, + encryptionKeyWrapMetadata); + CosmosClientEncryptionKeyProperties cosmosClientEncryptionKeyProperties2 = + new CosmosClientEncryptionKeyProperties("containerTestKey2", "AEAD_AES_256_CBC_HMAC_SHA256", key, + encryptionKeyWrapMetadata); + client.asyncClient().getDatabase(createdDatabase.getId()).createClientEncryptionKey(cosmosClientEncryptionKeyProperties1).block(); + client.asyncClient().getDatabase(createdDatabase.getId()).createClientEncryptionKey(cosmosClientEncryptionKeyProperties2).block(); + } } diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/models/ClientEncryptionPolicyTest.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/models/ClientEncryptionPolicyTest.java new file mode 100644 index 000000000000..49d24dbdd4e5 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/models/ClientEncryptionPolicyTest.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.models; + +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ClientEncryptionPolicyTest { + + @Test(groups = { "unit" }) + public void policyFormatVersionTest() { + CosmosContainerProperties containerProperties = new CosmosContainerProperties("{\"id\":\"030ed59b-90cb-4339" + + "-8d3d-775fda8af59a\",\"indexingPolicy\":{\"indexingMode\":\"consistent\",\"automatic\":true," + + "\"includedPaths\":[{\"path\":\"\\/*\"}],\"excludedPaths\":[{\"path\":\"\\/\\\"_etag\\\"\\/?\"}]}," + + "\"partitionKey\":{\"paths\":[\"\\/users\"],\"kind\":\"Hash\"}," + + "\"conflictResolutionPolicy\":{\"mode\":\"LastWriterWins\",\"conflictResolutionPath\":\"\\/_ts\"," + + "\"conflictResolutionProcedure\":\"\"},\"allowMaterializedViews\":false," + + "\"geospatialConfig\":{\"type\":\"Geography\"}," + + "\"clientEncryptionPolicy\":{\"includedPaths\":[{\"path\":\"\\/path1\"," + + "\"clientEncryptionKeyId\":\"dekId1\",\"encryptionAlgorithm\":\"AEAD_AES_256_CBC_HMAC_SHA256\"," + + "\"encryptionType\":\"Randomized\"},{\"path\":\"\\/path2\",\"clientEncryptionKeyId\":\"dekId2\"," + + "\"encryptionAlgorithm\":\"AEAD_AES_256_CBC_HMAC_SHA256\",\"encryptionType\":\"Deterministic\"}]," + + "\"policyFormatVersion\":2,\"newproperty\":\"value\"},\"_rid\":\"A3NIAPtyhlE=\",\"_ts\":1621365358," + + "\"_self\":\"dbs\\/A3NIAA==\\/colls\\/A3NIAPtyhlE=\\/\"," + + "\"_etag\":\"\\\"00000000-0000-0000-4c1a-3bb9032901d7\\\"\",\"_docs\":\"docs\\/\"," + + "\"_sprocs\":\"sprocs\\/\",\"_triggers\":\"triggers\\/\",\"_udfs\":\"udfs\\/\"," + + "\"_conflicts\":\"conflicts\\/\"}\n"); + + //Currently service is not returning policyFormatVersion, so validating V2 policy deserialization + assertThat(containerProperties.getClientEncryptionPolicy().getPolicyFormatVersion()).isEqualTo(2); + } +}