diff --git a/Microsoft.Azure.Cosmos/src/Fluent/Settings/ClientEncryptionPolicyDefinition.cs b/Microsoft.Azure.Cosmos/src/Fluent/Settings/ClientEncryptionPolicyDefinition.cs
index 7b103f74b5..024f2b05ea 100644
--- a/Microsoft.Azure.Cosmos/src/Fluent/Settings/ClientEncryptionPolicyDefinition.cs
+++ b/Microsoft.Azure.Cosmos/src/Fluent/Settings/ClientEncryptionPolicyDefinition.cs
@@ -9,19 +9,55 @@ namespace Microsoft.Azure.Cosmos.Fluent
///
/// fluent definition.
+ /// The should be initialized with
+ /// policyFormatVersion 2 and "Deterministic" encryption type, if "id" property or properties which are part of partition key need to be encrypted.
+ /// All partition key property values included as part of have to be JSON strings.
///
+ ///
+ /// This example shows how to create a using .
+ ///
+ ///
+ ///
+ ///
public sealed class ClientEncryptionPolicyDefinition
{
private readonly Collection clientEncryptionIncludedPaths = new Collection();
private readonly ContainerBuilder parent;
private readonly Action attachCallback;
+ private readonly int policyFormatVersion;
internal ClientEncryptionPolicyDefinition(
ContainerBuilder parent,
- Action attachCallback)
+ Action attachCallback,
+ int policyFormatVersion = 1)
{
this.parent = parent;
this.attachCallback = attachCallback;
+ this.policyFormatVersion = (policyFormatVersion > 2 || policyFormatVersion < 1) ? throw new ArgumentException($"Supported versions of client encryption policy are 1 and 2. ") : policyFormatVersion;
}
///
@@ -41,7 +77,7 @@ public ClientEncryptionPolicyDefinition WithIncludedPath(ClientEncryptionInclude
/// An instance of the parent.
public ContainerBuilder Attach()
{
- this.attachCallback(new ClientEncryptionPolicy(this.clientEncryptionIncludedPaths));
+ this.attachCallback(new ClientEncryptionPolicy(this.clientEncryptionIncludedPaths, this.policyFormatVersion));
return this.parent;
}
}
diff --git a/Microsoft.Azure.Cosmos/src/Fluent/Settings/ContainerBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/Settings/ContainerBuilder.cs
index 53b2e1205d..567396646f 100644
--- a/Microsoft.Azure.Cosmos/src/Fluent/Settings/ContainerBuilder.cs
+++ b/Microsoft.Azure.Cosmos/src/Fluent/Settings/ContainerBuilder.cs
@@ -90,12 +90,14 @@ ChangeFeedPolicyDefinition WithChangeFeedPolicy(TimeSpan retention)
///
/// Defines the ClientEncryptionPolicy for Azure Cosmos container
///
+ /// Version of the client encryption policy definition. Current supported versions are 1 and 2. Default version is 1.
/// An instance of .
- public ClientEncryptionPolicyDefinition WithClientEncryptionPolicy()
+ public ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(int policyFormatVersion = 1)
{
return new ClientEncryptionPolicyDefinition(
this,
- (clientEncryptionPolicy) => this.AddClientEncryptionPolicy(clientEncryptionPolicy));
+ (clientEncryptionPolicy) => this.AddClientEncryptionPolicy(clientEncryptionPolicy),
+ policyFormatVersion);
}
///
diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs
index ab3dcc5e74..6be228d73b 100644
--- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs
+++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs
@@ -12,19 +12,53 @@ namespace Microsoft.Azure.Cosmos
using Newtonsoft.Json.Linq;
///
- /// Client encryption policy.
+ /// The should be initialized with
+ /// policyFormatVersion 2 and "Deterministic" encryption type, if "id" property or properties which are part of partition key need to be encrypted.
+ /// All partition key property values have to be JSON strings.
///
+ ///
+ /// This example shows how to create a .
+ ///
+ /// paths = new Collection()
+ /// {
+ /// new ClientEncryptionIncludedPath()
+ /// {
+ /// Path = partitionKeyPath,
+ /// ClientEncryptionKeyId = "key1",
+ /// EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ /// EncryptionType = "Deterministic"
+ /// },
+ /// new ClientEncryptionIncludedPath()
+ /// {
+ /// Path = "/id",
+ /// ClientEncryptionKeyId = "key2",
+ /// EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ /// EncryptionType = "Deterministic"
+ /// },
+ /// };
+ ///
+ /// ContainerProperties setting = new ContainerProperties()
+ /// {
+ /// Id = containerName,
+ /// PartitionKeyPath = partitionKeyPath,
+ /// ClientEncryptionPolicy = new ClientEncryptionPolicy(includedPaths:paths, policyFormatVersion:2)
+ /// };
+ /// ]]>
+ ///
+ ///
public sealed class ClientEncryptionPolicy
{
///
/// Initializes a new instance of the class.
///
/// List of paths to include in the policy definition.
- public ClientEncryptionPolicy(IEnumerable includedPaths)
+ /// Version of the client encryption policy definition. Current supported versions are 1 and 2. Default version is 1.
+ public ClientEncryptionPolicy(IEnumerable includedPaths, int policyFormatVersion = 1)
{
- ClientEncryptionPolicy.ValidateIncludedPaths(includedPaths);
+ this.PolicyFormatVersion = (policyFormatVersion > 2 || policyFormatVersion < 1) ? throw new ArgumentException($"Supported versions of client encryption policy are 1 and 2. ") : policyFormatVersion;
+ ClientEncryptionPolicy.ValidateIncludedPaths(includedPaths, policyFormatVersion);
this.IncludedPaths = includedPaths;
- this.PolicyFormatVersion = 1;
}
[JsonConstructor]
@@ -33,7 +67,7 @@ private ClientEncryptionPolicy()
}
///
- /// Paths of the item that need encryption along with path-specific settings.
+ /// Paths of the item that need encryption along with path-specific settings.
///
[JsonProperty(PropertyName = "includedPaths")]
public IEnumerable IncludedPaths
@@ -55,43 +89,60 @@ public IEnumerable IncludedPaths
internal IDictionary AdditionalProperties { get; private set; }
///
- /// Ensures that partition key paths are not specified in the client encryption policy for encryption.
+ /// Ensures that partition key paths specified in the client encryption policy for encryption are encrypted using Deterministic encryption algorithm.
///
/// Tokens corresponding to validated partition key.
- internal void ValidatePartitionKeyPathsAreNotEncrypted(IReadOnlyList> partitionKeyPathTokens)
+ internal void ValidatePartitionKeyPathsIfEncrypted(IReadOnlyList> partitionKeyPathTokens)
{
Debug.Assert(partitionKeyPathTokens != null);
- IEnumerable propertiesToEncrypt = this.IncludedPaths.Select(p => p.Path.Substring(1));
+
foreach (IReadOnlyList tokensInPath in partitionKeyPathTokens)
{
Debug.Assert(tokensInPath != null);
if (tokensInPath.Count > 0)
{
string topLevelToken = tokensInPath.First();
- if (propertiesToEncrypt.Contains(topLevelToken))
+
+ // paths in included paths start with "/". Get the ClientEncryptionIncludedPath and validate.
+ IEnumerable encryptedPartitionKeyPath = this.IncludedPaths.Where(p => p.Path.Substring(1).Equals(topLevelToken));
+
+ if (encryptedPartitionKeyPath.Any())
{
- throw new ArgumentException($"Paths which are part of the partition key may not be included in the {nameof(ClientEncryptionPolicy)}.", nameof(ContainerProperties.ClientEncryptionPolicy));
+ if (this.PolicyFormatVersion < 2)
+ {
+ throw new ArgumentException($"Path: /{topLevelToken} which is part of the partition key cannot be encrypted with PolicyFormatVersion: {this.PolicyFormatVersion}. Please use PolicyFormatVersion: 2. ");
+ }
+
+ // for the ClientEncryptionIncludedPath found check the encryption type.
+ if (encryptedPartitionKeyPath.Select(et => et.EncryptionType).FirstOrDefault() != "Deterministic")
+ {
+ throw new ArgumentException($"Path: /{topLevelToken} which is part of the partition key has to be encrypted with Deterministic type Encryption.");
+ }
}
}
}
}
- private static void ValidateIncludedPaths(IEnumerable clientEncryptionIncludedPath)
+ private static void ValidateIncludedPaths(
+ IEnumerable clientEncryptionIncludedPath,
+ int policyFormatVersion)
{
List includedPathsList = new List();
foreach (ClientEncryptionIncludedPath path in clientEncryptionIncludedPath)
{
- ClientEncryptionPolicy.ValidateClientEncryptionIncludedPath(path);
+ ClientEncryptionPolicy.ValidateClientEncryptionIncludedPath(path, policyFormatVersion);
if (includedPathsList.Contains(path.Path))
{
- throw new ArgumentException("Duplicate Path found.", nameof(clientEncryptionIncludedPath));
+ throw new ArgumentException($"Duplicate Path found: {path.Path}.");
}
includedPathsList.Add(path.Path);
}
}
- private static void ValidateClientEncryptionIncludedPath(ClientEncryptionIncludedPath clientEncryptionIncludedPath)
+ private static void ValidateClientEncryptionIncludedPath(
+ ClientEncryptionIncludedPath clientEncryptionIncludedPath,
+ int policyFormatVersion)
{
if (clientEncryptionIncludedPath == null)
{
@@ -104,8 +155,7 @@ private static void ValidateClientEncryptionIncludedPath(ClientEncryptionInclude
}
if (clientEncryptionIncludedPath.Path[0] != '/'
- || clientEncryptionIncludedPath.Path.LastIndexOf('/') != 0
- || string.Equals(clientEncryptionIncludedPath.Path.Substring(1), "id"))
+ || clientEncryptionIncludedPath.Path.LastIndexOf('/') != 0)
{
throw new ArgumentException($"Invalid path '{clientEncryptionIncludedPath.Path ?? string.Empty}'.");
}
@@ -120,6 +170,19 @@ private static void ValidateClientEncryptionIncludedPath(ClientEncryptionInclude
throw new ArgumentNullException(nameof(clientEncryptionIncludedPath.EncryptionType));
}
+ if (string.Equals(clientEncryptionIncludedPath.Path.Substring(1), "id"))
+ {
+ if (policyFormatVersion < 2)
+ {
+ throw new ArgumentException($"Path: {clientEncryptionIncludedPath.Path} cannot be encrypted with PolicyFormatVersion: {policyFormatVersion}. Please use PolicyFormatVersion: 2. ");
+ }
+
+ if (clientEncryptionIncludedPath.EncryptionType != "Deterministic")
+ {
+ throw new ArgumentException($"Only Deterministic encryption type is supported for path: {clientEncryptionIncludedPath.Path}. ");
+ }
+ }
+
if (!string.Equals(clientEncryptionIncludedPath.EncryptionType, "Deterministic") &&
!string.Equals(clientEncryptionIncludedPath.EncryptionType, "Randomized"))
{
diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs
index 80280573c2..fb1eb0a165 100644
--- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs
+++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs
@@ -703,7 +703,7 @@ internal void ValidateRequiredProperties()
if (this.ClientEncryptionPolicy != null)
{
- this.ClientEncryptionPolicy.ValidatePartitionKeyPathsAreNotEncrypted(this.PartitionKeyPathTokens);
+ this.ClientEncryptionPolicy.ValidatePartitionKeyPathsIfEncrypted(this.PartitionKeyPathTokens);
}
}
}
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs
index 71e8ee5970..a17de1087c 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs
@@ -1365,10 +1365,17 @@ public async Task ClientEncryptionPolicyTest()
{
new ClientEncryptionIncludedPath()
{
- Path = "/path1",
+ Path = partitionKeyPath,
ClientEncryptionKeyId = "dekId1",
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
- EncryptionType = "Randomized"
+ EncryptionType = "Deterministic"
+ },
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/id",
+ ClientEncryptionKeyId = "dekId2",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Deterministic"
},
new ClientEncryptionIncludedPath()
{
@@ -1383,7 +1390,7 @@ public async Task ClientEncryptionPolicyTest()
{
Id = containerName,
PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash },
- ClientEncryptionPolicy = new ClientEncryptionPolicy(paths)
+ ClientEncryptionPolicy = new ClientEncryptionPolicy(includedPaths:paths,policyFormatVersion:2)
};
ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(setting);
@@ -1391,8 +1398,64 @@ public async Task ClientEncryptionPolicyTest()
Container container = containerResponse;
ContainerProperties responseSettings = containerResponse;
- Assert.AreEqual(2, responseSettings.ClientEncryptionPolicy.IncludedPaths.Count());
+ Assert.AreEqual(3, responseSettings.ClientEncryptionPolicy.IncludedPaths.Count());
ClientEncryptionIncludedPath includedPath = responseSettings.ClientEncryptionPolicy.IncludedPaths.ElementAt(0);
+ Assert.AreEqual(partitionKeyPath, includedPath.Path);
+ Assert.AreEqual("dekId1", includedPath.ClientEncryptionKeyId);
+ Assert.AreEqual("AEAD_AES_256_CBC_HMAC_SHA256", includedPath.EncryptionAlgorithm);
+ Assert.AreEqual("Deterministic", includedPath.EncryptionType);
+
+ includedPath = responseSettings.ClientEncryptionPolicy.IncludedPaths.ElementAt(1);
+ Assert.AreEqual("/id", includedPath.Path);
+ Assert.AreEqual("dekId2", includedPath.ClientEncryptionKeyId);
+ Assert.AreEqual("AEAD_AES_256_CBC_HMAC_SHA256", includedPath.EncryptionAlgorithm);
+ Assert.AreEqual("Deterministic", includedPath.EncryptionType);
+
+ includedPath = responseSettings.ClientEncryptionPolicy.IncludedPaths.ElementAt(2);
+ Assert.AreEqual("/path2", includedPath.Path);
+ Assert.AreEqual("dekId2", includedPath.ClientEncryptionKeyId);
+ Assert.AreEqual("AEAD_AES_256_CBC_HMAC_SHA256", includedPath.EncryptionAlgorithm);
+ Assert.AreEqual("Deterministic", includedPath.EncryptionType);
+
+ ContainerResponse readResponse = await container.ReadContainerAsync();
+ Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
+ Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy);
+
+ // version 1 test.
+ containerName = Guid.NewGuid().ToString();
+ partitionKeyPath = "/users";
+ paths = new Collection()
+ {
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/path1",
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Randomized"
+ },
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/path2",
+ ClientEncryptionKeyId = "dekId2",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Deterministic"
+ }
+ };
+
+ setting = new ContainerProperties()
+ {
+ Id = containerName,
+ PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash },
+ ClientEncryptionPolicy = new ClientEncryptionPolicy(paths)
+ };
+
+ containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(setting);
+ Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
+ container = containerResponse;
+ responseSettings = containerResponse;
+
+ Assert.AreEqual(2, responseSettings.ClientEncryptionPolicy.IncludedPaths.Count());
+ includedPath = responseSettings.ClientEncryptionPolicy.IncludedPaths.ElementAt(0);
Assert.AreEqual("/path1", includedPath.Path);
Assert.AreEqual("dekId1", includedPath.ClientEncryptionKeyId);
Assert.AreEqual("AEAD_AES_256_CBC_HMAC_SHA256", includedPath.EncryptionAlgorithm);
@@ -1404,9 +1467,9 @@ public async Task ClientEncryptionPolicyTest()
Assert.AreEqual("AEAD_AES_256_CBC_HMAC_SHA256", includedPath.EncryptionAlgorithm);
Assert.AreEqual("Deterministic", includedPath.EncryptionType);
- ContainerResponse readResponse = await container.ReadContainerAsync();
+ readResponse = await container.ReadContainerAsync();
Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
- Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy);
+ Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy);
// replace without updating CEP should be successful
readResponse.Resource.IndexingPolicy = new Cosmos.IndexingPolicy()
@@ -1464,7 +1527,7 @@ public async Task ClientEncryptionPolicyFailureTest()
}
catch (ArgumentException ex)
{
- Assert.IsTrue(ex.Message.Contains("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'."));
+ Assert.IsTrue(ex.Message.Contains("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'."), ex.Message);
}
try
@@ -1478,15 +1541,16 @@ public async Task ClientEncryptionPolicyFailureTest()
};
Collection pathsList = new Collection()
- {
- new ClientEncryptionIncludedPath()
- {
- Path = "/path1",
- ClientEncryptionKeyId = "dekId1",
- EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
- EncryptionType = "Randomized"
- },
- };
+ {
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/path1",
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Randomized"
+ },
+ };
+
pathsList.Add(path1);
ContainerProperties setting = new ContainerProperties()
@@ -1500,7 +1564,7 @@ public async Task ClientEncryptionPolicyFailureTest()
}
catch (ArgumentException ex)
{
- Assert.IsTrue(ex.Message.Contains("Duplicate Path found."));
+ Assert.IsTrue(ex.Message.Contains("Duplicate Path found: /path1."), ex.Message);
}
try
@@ -1514,6 +1578,105 @@ public async Task ClientEncryptionPolicyFailureTest()
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
EncryptionType = "Randomized"
},
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/path1",
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Deterministic"
+ },
+ };
+
+ ContainerProperties setting = new ContainerProperties()
+ {
+ Id = containerName,
+ PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash },
+ ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsToEncryptWithPartitionKey, 2)
+ };
+
+ await this.cosmosDatabase.CreateContainerAsync(setting);
+ Assert.Fail("Creating container should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Path: /users which is part of the partition key has to be encrypted with Deterministic type Encryption."), ex.Message);
+ }
+
+ try
+ {
+ Collection pathsToEncryptWithPartitionKey = new Collection()
+ {
+ new ClientEncryptionIncludedPath()
+ {
+ Path = partitionKeyPath,
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Deterministic"
+ },
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/id",
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Randomized"
+ },
+ };
+
+ ContainerProperties setting = new ContainerProperties()
+ {
+ Id = containerName,
+ PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash },
+ ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsToEncryptWithPartitionKey, 2)
+ };
+
+ await this.cosmosDatabase.CreateContainerAsync(setting);
+ Assert.Fail("Creating container should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Only Deterministic encryption type is supported for path: /id."), ex.Message);
+ }
+
+ // failure due to policy format version 1. for Pk and Id
+ try
+ {
+ Collection pathsToEncryptWithPartitionKey = new Collection()
+ {
+ new ClientEncryptionIncludedPath()
+ {
+ Path = partitionKeyPath,
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Deterministic"
+ },
+ };
+
+ ContainerProperties setting = new ContainerProperties()
+ {
+ Id = containerName,
+ PartitionKey = new Documents.PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = Documents.PartitionKind.Hash },
+ ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsToEncryptWithPartitionKey)
+ };
+
+ await this.cosmosDatabase.CreateContainerAsync(setting);
+ Assert.Fail("Creating container should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Path: /users which is part of the partition key cannot be encrypted with PolicyFormatVersion: 1. Please use PolicyFormatVersion: 2."), ex.Message);
+ }
+
+ try
+ {
+ Collection pathsToEncryptWithPartitionKey = new Collection()
+ {
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/id",
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Deterministic"
+ },
};
ContainerProperties setting = new ContainerProperties()
@@ -1528,7 +1691,65 @@ public async Task ClientEncryptionPolicyFailureTest()
}
catch (ArgumentException ex)
{
- Assert.IsTrue(ex.Message.Contains("Paths which are part of the partition key may not be included in the ClientEncryptionPolicy."));
+ Assert.IsTrue(ex.Message.Contains("Path: /id cannot be encrypted with PolicyFormatVersion: 1. Please use PolicyFormatVersion: 2."), ex.Message);
+ }
+
+ // hierarchical partition keys
+ try
+ {
+ Collection pathsToEncryptWithPartitionKey = new Collection()
+ {
+ new ClientEncryptionIncludedPath()
+ {
+ Path = "/id",
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Randomized"
+ },
+ };
+
+ ContainerProperties setting = new ContainerProperties()
+ {
+ Id = containerName,
+ PartitionKeyPaths = new Collection { "/path1", "/id" },
+ ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsToEncryptWithPartitionKey, 2)
+ };
+
+ await this.cosmosDatabase.CreateContainerAsync(setting);
+ Assert.Fail("Creating container should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Only Deterministic encryption type is supported for path: /id."), ex.Message);
+ }
+
+ // hierarchical partition keys
+ try
+ {
+ Collection pathsToEncryptWithPartitionKey = new Collection()
+ {
+ new ClientEncryptionIncludedPath()
+ {
+ Path = partitionKeyPath,
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ EncryptionType = "Randomized"
+ },
+ };
+
+ ContainerProperties setting = new ContainerProperties()
+ {
+ Id = containerName,
+ PartitionKeyPaths = new Collection { partitionKeyPath, "/path1" },
+ ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsToEncryptWithPartitionKey, 2)
+ };
+
+ await this.cosmosDatabase.CreateContainerAsync(setting);
+ Assert.Fail("Creating container should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Path: /users which is part of the partition key has to be encrypted with Deterministic type Encryption."), ex.Message);
}
}
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs
index d038032f93..ca8aaadec1 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs
@@ -137,7 +137,7 @@ public async Task ContainerContractTest()
Assert.AreEqual(4, spatialPath.SpatialTypes.Count); // All SpatialTypes are returned
Assert.AreEqual(1, responseProperties.ClientEncryptionPolicy.IncludedPaths.Count());
- Assert.IsTrue(responseProperties.ClientEncryptionPolicy.PolicyFormatVersion <= 1);
+ Assert.IsTrue(responseProperties.ClientEncryptionPolicy.PolicyFormatVersion <= 2);
ClientEncryptionIncludedPath clientEncryptionIncludedPath = responseProperties.ClientEncryptionPolicy.IncludedPaths.First();
Assert.IsTrue(this.VerifyClientEncryptionIncludedPath(clientEncryptionIncludedPath1, clientEncryptionIncludedPath));
}
@@ -601,26 +601,27 @@ public async Task WithClientEncryptionPolicyTest()
await TestCommon.CreateClientEncryptionKey("dekId1", databaseInlineCore);
await TestCommon.CreateClientEncryptionKey("dekId2", databaseInlineCore);
+ // version 2
string containerName = Guid.NewGuid().ToString();
string partitionKeyPath = "/users";
ClientEncryptionIncludedPath path1 = new ClientEncryptionIncludedPath()
{
- Path = "/path1",
+ Path = partitionKeyPath,
ClientEncryptionKeyId = "dekId1",
- EncryptionType = "Randomized",
+ EncryptionType = "Deterministic",
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256"
};
ClientEncryptionIncludedPath path2 = new ClientEncryptionIncludedPath()
{
- Path = "/path2",
+ Path = "/id",
ClientEncryptionKeyId = "dekId2",
- EncryptionType = "Randomized",
+ EncryptionType = "Deterministic",
EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
};
-
+
ContainerResponse containerResponse = await this.database.DefineContainer(containerName, partitionKeyPath)
- .WithClientEncryptionPolicy()
+ .WithClientEncryptionPolicy(policyFormatVersion:2)
.WithIncludedPath(path1)
.WithIncludedPath(path2)
.Attach()
@@ -641,6 +642,47 @@ public async Task WithClientEncryptionPolicyTest()
Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy);
+ // version 1
+ containerName = Guid.NewGuid().ToString();
+ partitionKeyPath = "/users";
+ path1 = new ClientEncryptionIncludedPath()
+ {
+ Path = "/path1",
+ ClientEncryptionKeyId = "dekId1",
+ EncryptionType = "Randomized",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256"
+ };
+
+ path2 = new ClientEncryptionIncludedPath()
+ {
+ Path = "/path2",
+ ClientEncryptionKeyId = "dekId2",
+ EncryptionType = "Randomized",
+ EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256",
+ };
+
+ containerResponse = await this.database.DefineContainer(containerName, partitionKeyPath)
+ .WithClientEncryptionPolicy()
+ .WithIncludedPath(path1)
+ .WithIncludedPath(path2)
+ .Attach()
+ .CreateAsync();
+
+ Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
+ container = containerResponse;
+ responseSettings = containerResponse;
+
+ Assert.IsNotNull(responseSettings.ClientEncryptionPolicy);
+ Assert.AreEqual(2, responseSettings.ClientEncryptionPolicy.IncludedPaths.Count());
+ clientEncryptionIncludedPath = responseSettings.ClientEncryptionPolicy.IncludedPaths.First();
+ Assert.IsTrue(this.VerifyClientEncryptionIncludedPath(path1, clientEncryptionIncludedPath));
+ clientEncryptionIncludedPath = responseSettings.ClientEncryptionPolicy.IncludedPaths.Last();
+ Assert.IsTrue(this.VerifyClientEncryptionIncludedPath(path2, clientEncryptionIncludedPath));
+
+ readResponse = await container.ReadContainerAsync();
+ Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
+ Assert.IsNotNull(readResponse.Resource.ClientEncryptionPolicy);
+
// update CEP and replace container
readResponse.Resource.ClientEncryptionPolicy = null;
try
@@ -737,6 +779,76 @@ public async Task WithClientEncryptionPolicyFailureTest()
{
Assert.IsTrue(ex.Message.Contains("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'. "));
}
+
+ // invalid policy version for partition key encryption
+ path1.EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256";
+ path1.Path = partitionKeyPath;
+ try
+ {
+ ContainerResponse containerResponse = await this.database.DefineContainer(containerName, partitionKeyPath)
+ .WithClientEncryptionPolicy()
+ .WithIncludedPath(path1)
+ .Attach()
+ .CreateAsync();
+
+ Assert.Fail("CreateCollection with invalid ClientEncryptionPolicy should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Path: /users which is part of the partition key cannot be encrypted with PolicyFormatVersion: 1. Please use PolicyFormatVersion: 2."), ex.Message);
+ }
+
+ // invalid policy version for id encryption
+ path1.Path = "/id";
+ try
+ {
+ ContainerResponse containerResponse = await this.database.DefineContainer(containerName, partitionKeyPath)
+ .WithClientEncryptionPolicy()
+ .WithIncludedPath(path1)
+ .Attach()
+ .CreateAsync();
+
+ Assert.Fail("CreateCollection with invalid ClientEncryptionPolicy should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Path: /id cannot be encrypted with PolicyFormatVersion: 1. Please use PolicyFormatVersion: 2."), ex.Message);
+ }
+
+ // invalid encryption type for id encryption
+ path1.EncryptionType = "Randomized";
+ path1.Path = partitionKeyPath;
+ try
+ {
+ ContainerResponse containerResponse = await this.database.DefineContainer(containerName, partitionKeyPath)
+ .WithClientEncryptionPolicy(2)
+ .WithIncludedPath(path1)
+ .Attach()
+ .CreateAsync();
+
+ Assert.Fail("CreateCollection with invalid ClientEncryptionPolicy should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Path: /users which is part of the partition key has to be encrypted with Deterministic type Encryption."), ex.Message);
+ }
+
+ // invalid encryption type for id encryption
+ path1.Path = "/id";
+ try
+ {
+ ContainerResponse containerResponse = await this.database.DefineContainer(containerName, partitionKeyPath)
+ .WithClientEncryptionPolicy(2)
+ .WithIncludedPath(path1)
+ .Attach()
+ .CreateAsync();
+
+ Assert.Fail("CreateCollection with invalid ClientEncryptionPolicy should have failed.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("Only Deterministic encryption type is supported for path: /id."), ex.Message);
+ }
}
private bool VerifyClientEncryptionIncludedPath(ClientEncryptionIncludedPath expected, ClientEncryptionIncludedPath actual)
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json
index aa8403d330..f8cb3adf8e 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json
@@ -996,10 +996,10 @@
],
"MethodInfo": "System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath] IncludedPaths;CanRead:True;CanWrite:True;System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath] get_IncludedPaths();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
- "Void .ctor(System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath])": {
+ "Void .ctor(System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath], Int32)": {
"Type": "Constructor",
"Attributes": [],
- "MethodInfo": "[Void .ctor(System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath]), Void .ctor(System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath])]"
+ "MethodInfo": "[Void .ctor(System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath], Int32), Void .ctor(System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath], Int32)]"
}
},
"NestedTypes": {}
@@ -4176,10 +4176,10 @@
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.ContainerProperties Build();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
- "Microsoft.Azure.Cosmos.Fluent.ClientEncryptionPolicyDefinition WithClientEncryptionPolicy()": {
+ "Microsoft.Azure.Cosmos.Fluent.ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(Int32)": {
"Type": "Method",
"Attributes": [],
- "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.ClientEncryptionPolicyDefinition WithClientEncryptionPolicy();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
+ "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Microsoft.Azure.Cosmos.Fluent.ConflictResolutionDefinition WithConflictResolution()": {
"Type": "Method",