Skip to content

Commit

Permalink
Adding type in EncryptionKeyWrapMetadata and performing validation on…
Browse files Browse the repository at this point in the history
… partition key (Azure#21407)

* Adding type in EncryptionKeyWrapMetadata and doing validation on partition key

* adding beta tag

* fixing build error

* adding validation on policy format version

* Adding usnit test for policyFormatVersion deserialization

* resolving comments
  • Loading branch information
simplynaveen20 authored May 19, 2021
1 parent 5cc9830 commit 1b7f50d
Show file tree
Hide file tree
Showing 18 changed files with 403 additions and 49 deletions.
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 " +
"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

0 comments on commit 1b7f50d

Please sign in to comment.