Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding type in EncryptionKeyWrapMetadata and performing validation on partition key #21407

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,16 +61,16 @@ Mono<ClientEncryptionPolicy> 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))));
}
}

Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ public Mono<CosmosClientEncryptionKeyResponse> 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 " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in similar scenario, does non Cosmos Exception type reach public surface area in the DotNet SDK?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes .NET has ArgumentException as well. To me this is correct and it should be non cosmos as it is validation on argument passed by user.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how would it work on the async API? do we do validation upfront? or do we return a Mono.of(IllegalArgException)

Copy link
Member Author

@simplynaveen20 simplynaveen20 May 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation is on async API itself which is doing validation upfront .

"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);
Expand Down Expand Up @@ -112,6 +119,13 @@ public Mono<CosmosClientEncryptionKeyResponse> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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",
Expand All @@ -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)
Expand Down Expand Up @@ -220,7 +232,7 @@ public void queryItemsOnRandomizedEncryption() {
}

@Test(groups = {"encryption"}, timeOut = TIMEOUT)
public void queryItemsWithContinuationTokenAndPageSize() throws Exception {
public void queryItemsWithContinuationTokenAndPageSize() {
List<String> actualIds = new ArrayList<>();
EncryptionPojo properties = getItem(UUID.randomUUID().toString());
cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -368,6 +396,12 @@ public static EncryptionPojo getItem(String documentId) {

public static class TestEncryptionKeyStoreProvider extends EncryptionKeyStoreProvider {
Map<String, Integer> keyInfo = new HashMap<>();
String providerName = "TEST_KEY_STORE_PROVIDER";

@Override
public String getProviderName() {
return providerName;
}

public TestEncryptionKeyStoreProvider() {
keyInfo.put("tempmetadata1", 1);
Expand Down
Loading