/
ClientEncryptionPolicy.cs
215 lines (191 loc) · 9.98 KB
/
ClientEncryptionPolicy.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
/// <summary>
/// The <see cref="ClientEncryptionPolicy"/> 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.
/// </summary>
/// <example>
/// This example shows how to create a <see cref="ClientEncryptionPolicy"/>.
/// <code language="c#">
/// <![CDATA[
/// Collection<ClientEncryptionIncludedPath> paths = new Collection<ClientEncryptionIncludedPath>()
/// {
/// 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)
/// };
/// ]]>
/// </code>
/// </example>
public sealed class ClientEncryptionPolicy
{
/// <summary>
/// Initializes a new instance of the <see cref="ClientEncryptionPolicy"/> class.
/// The <see cref="PolicyFormatVersion"/> will be set to 1.
/// Note: If you need to include partition key or id field paths as part of <see cref="ClientEncryptionPolicy"/>, please set <see cref="PolicyFormatVersion"/> to 2.
/// </summary>
/// <param name="includedPaths">List of paths to include in the policy definition.</param>
public ClientEncryptionPolicy(IEnumerable<ClientEncryptionIncludedPath> includedPaths)
: this(includedPaths: includedPaths, policyFormatVersion: 1)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ClientEncryptionPolicy"/> class.
/// Note: If you need to include partition key or id field paths as part of <see cref="ClientEncryptionPolicy"/>, please set <see cref="PolicyFormatVersion"/> to 2.
/// </summary>
/// <param name="includedPaths">List of paths to include in the policy definition.</param>
/// <param name="policyFormatVersion"> Version of the client encryption policy definition. Current supported versions are 1 and 2.</param>
public ClientEncryptionPolicy(IEnumerable<ClientEncryptionIncludedPath> includedPaths, int policyFormatVersion)
{
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;
}
[JsonConstructor]
private ClientEncryptionPolicy()
{
}
/// <summary>
/// Paths of the item that need encryption along with path-specific settings.
/// </summary>
[JsonProperty(PropertyName = "includedPaths")]
public IEnumerable<ClientEncryptionIncludedPath> IncludedPaths
{
get; private set;
}
/// <summary>
/// Version of the client encryption policy definition.
/// </summary>
[JsonProperty(PropertyName = "policyFormatVersion")]
public int PolicyFormatVersion { get; private set; }
/// <summary>
/// This contains additional values for scenarios where the SDK is not aware of new fields.
/// This ensures that if resource is read and updated none of the fields will be lost in the process.
/// </summary>
[JsonExtensionData]
internal IDictionary<string, JToken> AdditionalProperties { get; private set; }
/// <summary>
/// Ensures that partition key paths specified in the client encryption policy for encryption are encrypted using Deterministic encryption algorithm.
/// </summary>
/// <param name="partitionKeyPathTokens">Tokens corresponding to validated partition key.</param>
internal void ValidatePartitionKeyPathsIfEncrypted(IReadOnlyList<IReadOnlyList<string>> partitionKeyPathTokens)
{
Debug.Assert(partitionKeyPathTokens != null);
foreach (IReadOnlyList<string> tokensInPath in partitionKeyPathTokens)
{
Debug.Assert(tokensInPath != null);
if (tokensInPath.Count > 0)
{
string topLevelToken = tokensInPath.First();
// paths in included paths start with "/". Get the ClientEncryptionIncludedPath and validate.
IEnumerable<ClientEncryptionIncludedPath> encryptedPartitionKeyPath = this.IncludedPaths.Where(p => p.Path.Substring(1).Equals(topLevelToken));
if (encryptedPartitionKeyPath.Any())
{
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> clientEncryptionIncludedPath,
int policyFormatVersion)
{
List<string> includedPathsList = new List<string>();
foreach (ClientEncryptionIncludedPath path in clientEncryptionIncludedPath)
{
ClientEncryptionPolicy.ValidateClientEncryptionIncludedPath(path, policyFormatVersion);
if (includedPathsList.Contains(path.Path))
{
throw new ArgumentException($"Duplicate Path found: {path.Path}.");
}
includedPathsList.Add(path.Path);
}
}
private static void ValidateClientEncryptionIncludedPath(
ClientEncryptionIncludedPath clientEncryptionIncludedPath,
int policyFormatVersion)
{
if (clientEncryptionIncludedPath == null)
{
throw new ArgumentNullException(nameof(clientEncryptionIncludedPath));
}
if (string.IsNullOrWhiteSpace(clientEncryptionIncludedPath.Path))
{
throw new ArgumentNullException(nameof(clientEncryptionIncludedPath.Path));
}
if (clientEncryptionIncludedPath.Path[0] != '/'
|| clientEncryptionIncludedPath.Path.LastIndexOf('/') != 0)
{
throw new ArgumentException($"Invalid path '{clientEncryptionIncludedPath.Path ?? string.Empty}'.");
}
if (string.IsNullOrWhiteSpace(clientEncryptionIncludedPath.ClientEncryptionKeyId))
{
throw new ArgumentNullException(nameof(clientEncryptionIncludedPath.ClientEncryptionKeyId));
}
if (string.IsNullOrWhiteSpace(clientEncryptionIncludedPath.EncryptionType))
{
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"))
{
throw new ArgumentException("EncryptionType should be either 'Deterministic' or 'Randomized'. ", nameof(clientEncryptionIncludedPath));
}
if (string.IsNullOrWhiteSpace(clientEncryptionIncludedPath.EncryptionAlgorithm))
{
throw new ArgumentNullException(nameof(clientEncryptionIncludedPath.EncryptionAlgorithm));
}
if (!string.Equals(clientEncryptionIncludedPath.EncryptionAlgorithm, "AEAD_AES_256_CBC_HMAC_SHA256"))
{
throw new ArgumentException("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'. ", nameof(clientEncryptionIncludedPath));
}
}
}
}