Skip to content

Commit

Permalink
Remove algorithm string from encryptionKeyWrapMetadata and converting…
Browse files Browse the repository at this point in the history
… MicrosoftDataEncryptionException to CosmosException (#20845)

* Adding type on encryptionMetadata and coverting MicrosoftDataEncryptionException to CosmosException

* correction class name of logger

* Increasing the encryption length support to 8000 from 1024

* adding test case and fixing nested pojo array issue

* updating plain text test

* reverting change for encryptionKeyWrapMatadata

* removing pojo to test plain text

* updating test flow for new emulator

* disabling asyncChangeFeed_fromNow_fullFidelity_forFullRange

* fixing test for new emulator
  • Loading branch information
simplynaveen20 authored and benbp committed Apr 28, 2021
1 parent c26927b commit cb45f21
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@

package com.azure.cosmos.encryption;

import com.azure.cosmos.BridgeInternal;
import com.azure.cosmos.CosmosAsyncClient;
import com.azure.cosmos.CosmosAsyncClientEncryptionKey;
import com.azure.cosmos.CosmosAsyncContainer;
import com.azure.cosmos.CosmosAsyncDatabase;
import com.azure.cosmos.CosmosException;
import com.azure.cosmos.encryption.implementation.EncryptionProcessor;
import com.azure.cosmos.implementation.HttpConstants;
import com.azure.cosmos.implementation.Utils;
import com.azure.cosmos.implementation.caches.AsyncCache;
import com.azure.cosmos.models.ClientEncryptionPolicy;
import com.azure.cosmos.models.CosmosClientEncryptionKeyProperties;
import com.microsoft.data.encryption.cryptography.EncryptionKeyStoreProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

/**
* CosmosClient with Encryption support.
*/
public class CosmosEncryptionAsyncClient {
private final static Logger LOGGER = LoggerFactory.getLogger(CosmosEncryptionAsyncClient.class);
private final CosmosAsyncClient cosmosAsyncClient;
private final AsyncCache<String, ClientEncryptionPolicy> clientEncryptionPolicyCacheByContainerId;
private final AsyncCache<String, CosmosClientEncryptionKeyProperties> clientEncryptionKeyPropertiesCacheByKeyId;
Expand Down Expand Up @@ -95,9 +103,26 @@ Mono<CosmosClientEncryptionKeyProperties> fetchClientEncryptionKeyPropertiesAsyn

return clientEncryptionKey.read().map(cosmosClientEncryptionKeyResponse ->
cosmosClientEncryptionKeyResponse.getProperties()
).onErrorResume(throwable -> Mono.error(new IllegalStateException("Encryption Based Container without Data " +
"Encryption Keys. " +
"Please make sure you have created the Client Encryption Keys", throwable)));
).onErrorResume(throwable -> {
Throwable unwrappedException = reactor.core.Exceptions.unwrap(throwable);
if (!(unwrappedException instanceof Exception)) {
// fatal error
LOGGER.error("Unexpected failure {}", unwrappedException.getMessage(), unwrappedException);
return Mono.error(unwrappedException);
}
Exception exception = (Exception) unwrappedException;
CosmosException dce = Utils.as(exception, CosmosException.class);
if (dce != null) {
if (dce.getStatusCode() == HttpConstants.StatusCodes.NOTFOUND) {
String message = "Encryption Based Container without Data Encryption Keys. " +
"Please make sure you have created the Client Encryption Keys";
return Mono.error(BridgeInternal.createCosmosException(HttpConstants.StatusCodes.NOTFOUND, message));
}
return Mono.error(dce);
}

return Mono.error(exception);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package com.azure.cosmos.encryption.implementation;

import com.azure.cosmos.BridgeInternal;
import com.azure.cosmos.CosmosAsyncContainer;
import com.azure.cosmos.encryption.CosmosEncryptionAsyncClient;
import com.azure.cosmos.encryption.EncryptionBridgeInternal;
Expand Down Expand Up @@ -55,7 +56,7 @@ public class EncryptionProcessor {
private EncryptionSettings encryptionSettings;
private AtomicBoolean isEncryptionSettingsInitDone;
private ClientEncryptionPolicy clientEncryptionPolicy;
private final static int STRING_SIZE_ENCRYPTION_LIMIT = 1024;
private final static int STRING_SIZE_ENCRYPTION_LIMIT = 8000;

public EncryptionProcessor(CosmosAsyncContainer cosmosAsyncContainer,
CosmosEncryptionAsyncClient encryptionCosmosClient) {
Expand Down Expand Up @@ -377,7 +378,7 @@ public void decryptAndSerializeProperty(EncryptionSettings encryptionSettings, O
if (propertyValueHolder.isObject()) {
for (Iterator<Map.Entry<String, JsonNode>> it = propertyValueHolder.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> child = it.next();
if (child.getValue().isObject()) {
if (child.getValue().isObject() || child.getValue().isArray()) {
decryptAndSerializeProperty(encryptionSettings, (ObjectNode) propertyValueHolder,
child.getValue(), propertyName);
} else if (!child.getValue().isNull()) {
Expand Down Expand Up @@ -467,9 +468,10 @@ public static Pair<TypeMarker, byte[]> toByteArray(JsonNode jsonNode) {
SqlSerializerFactory.getOrCreate("varchar", STRING_SIZE_ENCRYPTION_LIMIT, 0, 0, StandardCharsets.UTF_8.toString()).serialize(jsonNode.asText()));
}
} catch (MicrosoftDataEncryptionException ex) {
throw new IllegalStateException("Unable to convert JSON to byte[]", ex);
throw BridgeInternal.createCosmosException("Unable to convert JSON to byte[]", ex, null, 0, null);
}
throw new IncompatibleClassChangeError("Invalid or Unsupported Data Type Passed " + jsonNode.getNodeType());
throw BridgeInternal.createCosmosException(0,
"Invalid or Unsupported Data Type Passed " + jsonNode.getNodeType());
}

public static JsonNode toJsonNode(byte[] serializedBytes, TypeMarker typeMarker) {
Expand All @@ -487,9 +489,9 @@ public static JsonNode toJsonNode(byte[] serializedBytes, TypeMarker typeMarker)
STRING_SIZE_ENCRYPTION_LIMIT, 0, 0, StandardCharsets.UTF_8.toString()).deserialize(serializedBytes));
}
} catch (MicrosoftDataEncryptionException ex) {
throw new IllegalStateException("Unable to convert byte[] to JSON", ex);
throw BridgeInternal.createCosmosException("Unable to convert byte[] to JSON", ex, null, 0, null);
}
throw new IncompatibleClassChangeError("Invalid or Unsupported Data Type Passed " + typeMarker);
throw BridgeInternal.createCosmosException(0, "Invalid or Unsupported Data Type Passed " + typeMarker);
}

public enum TypeMarker {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ public void before_CosmosItemTest() {
new EncryptionCrudTest.TestEncryptionKeyStoreProvider());
cosmosEncryptionAsyncDatabase =
cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosDatabaseProperties.getId());
//Create ClientEncryptionKeys
metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1");
metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2");
cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1",
CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block();
cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2",
CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata2).block();

//Create collection with clientEncryptionPolicy
ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(EncryptionCrudTest.getPaths());
Expand All @@ -68,14 +75,6 @@ public void before_CosmosItemTest() {
cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(containerProperties).block();
cosmosEncryptionAsyncContainer =
cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerProperties.getId());

//Create collection with ClientEncryptionKeys
metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1");
metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2");
cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1",
CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block();
cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2",
CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata2).block();
}

@Test(groups = {"encryption"}, priority = 0, timeOut = TIMEOUT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import com.azure.cosmos.CosmosAsyncClient;
import com.azure.cosmos.CosmosAsyncDatabase;
import com.azure.cosmos.CosmosClientBuilder;
import com.azure.cosmos.CosmosException;
import com.azure.cosmos.encryption.models.CosmosEncryptionAlgorithm;
import com.azure.cosmos.encryption.models.CosmosEncryptionType;
import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption;
import com.azure.cosmos.models.ClientEncryptionIncludedPath;
import com.azure.cosmos.models.ClientEncryptionPolicy;
import com.azure.cosmos.models.CosmosContainerProperties;
Expand All @@ -19,7 +21,6 @@
import com.azure.cosmos.models.PartitionKey;
import com.azure.cosmos.models.SqlParameter;
import com.azure.cosmos.models.SqlQuerySpec;
import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption;
import com.azure.cosmos.rx.TestSuiteBase;
import com.azure.cosmos.util.CosmosPagedFlux;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand All @@ -31,6 +32,7 @@
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -65,19 +67,19 @@ public void before_CosmosItemTest() {
cosmosEncryptionAsyncDatabase =
cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosAsyncDatabase);

ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths());
String containerId = UUID.randomUUID().toString();
CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk");
properties.setClientEncryptionPolicy(clientEncryptionPolicy);
cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block();
cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId);
metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1");
metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2");
cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1",
CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block();
cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2",
CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata2).block();
cosmosEncryptionAsyncDatabase.rewrapClientEncryptionKey("key2", metadata2).block();

ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths());
String containerId = UUID.randomUUID().toString();
CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk");
properties.setClientEncryptionPolicy(clientEncryptionPolicy);
cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block();
cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId);
}

@AfterClass(groups = {"encryption"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true)
Expand All @@ -87,7 +89,7 @@ public void afterClass() {
}

@Test(groups = {"encryption"}, timeOut = TIMEOUT)
public void createItemEncrypt_readItemDecrypt() {
public void createItemEncrypt_readItemDecrypt() throws MicrosoftDataEncryptionException, IOException {
Pojo properties = getItem(UUID.randomUUID().toString());
CosmosItemResponse<Pojo> itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block();
Expand All @@ -98,10 +100,35 @@ public void createItemEncrypt_readItemDecrypt() {
Pojo readItem = cosmosEncryptionAsyncContainer.readItem(properties.id, new PartitionKey(properties.mypk),
new CosmosItemRequestOptions(), Pojo.class).block().getItem();
validateResponse(properties, readItem);

//Check for max length support of 8000
properties = getItem(UUID.randomUUID().toString());
String longString = "";
for (int i = 0; i < 8000; i++) {
longString += "a";
}
properties.sensitiveString = longString;
itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block();
assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
responseItem = itemResponse.getItem();
validateResponse(properties, responseItem);

//Check for exception for length greater that 8000
longString += "a";
properties.sensitiveString = longString;
try {
cosmosEncryptionAsyncContainer.createItem(properties,
new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block();
fail("Item create should fail as length of encryption field is greater than 8000");
} catch (CosmosException ex) {
assertThat(ex.getMessage()).contains("Unable to convert JSON to byte[]");
assertThat(ex.getCause() instanceof MicrosoftDataEncryptionException).isTrue();
}
}

@Test(groups = {"encryption"}, timeOut = TIMEOUT)
public void upsertItem_readItem() throws Exception {
public void upsertItem_readItem() {
Pojo properties = getItem(UUID.randomUUID().toString());
CosmosItemResponse<Pojo> itemResponse = cosmosEncryptionAsyncContainer.upsertItem(properties,
new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block();
Expand Down Expand Up @@ -299,7 +326,6 @@ private void validateResponse(Pojo originalItem, Pojo result) {
assertThat(result.sensitiveChildPojo2DArray[0][0].sensitiveIntArray).isEqualTo(originalItem.sensitiveChildPojo2DArray[0][0].sensitiveIntArray);
assertThat(result.sensitiveChildPojo2DArray[0][0].sensitiveStringArray).isEqualTo(originalItem.sensitiveChildPojo2DArray[0][0].sensitiveStringArray);
assertThat(result.sensitiveChildPojo2DArray[0][0].sensitiveString3DArray).isEqualTo(originalItem.sensitiveChildPojo2DArray[0][0].sensitiveString3DArray);

}

public static Pojo getItem(String documentId) {
Expand All @@ -316,10 +342,14 @@ public static Pojo getItem(String documentId) {

Pojo nestedPojo = new Pojo();
nestedPojo.id = "nestedPojo";
nestedPojo.mypk = "nestedPojo";
nestedPojo.sensitiveString = "nestedPojo";
nestedPojo.sensitiveDouble = 10.123;
nestedPojo.sensitiveInt = 123;
nestedPojo.sensitiveLong = 1234;
nestedPojo.sensitiveStringArray = new String[]{"str1", "str1"};
nestedPojo.sensitiveString3DArray = new String[][][]{{{"str1", "str2"}, {"str3", "str4"}}, {{"str5", "str6"}, {
"str7", "str8"}}};
nestedPojo.sensitiveBoolean = true;

pojo.sensitiveNestedPojo = nestedPojo;
Expand All @@ -336,7 +366,9 @@ public static Pojo getItem(String documentId) {
childPojo1.sensitiveInt = 123;
childPojo1.sensitiveLong = 1234;
childPojo1.sensitiveBoolean = true;

childPojo1.sensitiveStringArray = new String[]{"str1", "str1"};
childPojo1.sensitiveString3DArray = new String[][][]{{{"str1", "str2"}, {"str3", "str4"}}, {{"str5", "str6"}, {
"str7", "str8"}}};
Pojo childPojo2 = new Pojo();
childPojo2.id = "childPojo2";
childPojo2.sensitiveString = "child2TestingString";
Expand Down Expand Up @@ -475,7 +507,6 @@ public static List<ClientEncryptionIncludedPath> getPaths() {
includedPath8.setEncryptionType(CosmosEncryptionType.DETERMINISTIC);
includedPath8.setEncryptionAlgorithm(CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256);


ClientEncryptionIncludedPath includedPath9 = new ClientEncryptionIncludedPath();
includedPath9.setClientEncryptionKeyId("key1");
includedPath9.setPath("/sensitiveIntArray");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public EncryptionKeyWrapMetadata() {
@Beta(value = Beta.SinceVersion.V4_14_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING)
public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source) {
this.type = source.type;
this.algorithm = source.algorithm;
this.name = source.name;
this.value = source.value;
}

Expand All @@ -43,26 +43,21 @@ public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source) {
*/
@Beta(value = Beta.SinceVersion.V4_14_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING)
public EncryptionKeyWrapMetadata(String name, String value) {
this("custom", name, value, null);
this("custom", name, value);
}

private EncryptionKeyWrapMetadata(String type, String name, String value, String algorithm) {
private EncryptionKeyWrapMetadata(String type, String name, String value) {
Preconditions.checkNotNull(type, "type is null");
Preconditions.checkNotNull(value, "value is null");
this.type = type;
this.name = name;
this.value = value;
this.algorithm = algorithm;
}

@JsonProperty("type")
@JsonInclude(JsonInclude.Include.NON_NULL)
private String type;

@JsonProperty("algorithm")
@JsonInclude(JsonInclude.Include.NON_NULL)
private String algorithm;

@JsonProperty("value")
@JsonInclude(JsonInclude.Include.NON_NULL)
private String value;
Expand Down Expand Up @@ -107,13 +102,13 @@ public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) return false;
EncryptionKeyWrapMetadata that = (EncryptionKeyWrapMetadata) obj;
return Objects.equals(type, that.type) &&
Objects.equals(algorithm, that.algorithm) &&
Objects.equals(name, that.name) &&
Objects.equals(value, that.value);
}

@Override
public int hashCode() {
return Objects.hash(type, algorithm, value);
return Objects.hash(type, name, value);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ public void asyncChangeFeed_fromNow_incremental_forFullRange() throws Exception
drainAndValidateChangeFeedResults(options, null, expectedEventCountAfterUpdates);
}

@Test(groups = { "emulator" }, timeOut = TIMEOUT)
//TODO Temporarily disabling
@Test(groups = { "emulator" }, timeOut = TIMEOUT, enabled = false)
public void asyncChangeFeed_fromNow_fullFidelity_forFullRange() throws Exception {
this.createContainer(
(cp) -> cp.setChangeFeedPolicy(ChangeFeedPolicy.createFullFidelityPolicy(Duration.ofMinutes(10)))
Expand Down

0 comments on commit cb45f21

Please sign in to comment.