[Internal] ClientEncryption: Add the Encryption algo and DEK management methods as a separate project (move) (#1288)

* DEK Provider abstraction

* Move DekCore methods to DekContainerCore

* Minor

* More fixes moving DekCore methods to DekContainerCore

* Add in DataEncryptionKey abstraction; get most tests working.

* Minor - rename _cts to createdTime in dekProperties to avoid using a system property in this doc

* Reduce coupling between SDK and CosmosDekProvider

* Move encryption algo (and DEK provider) into Encryption project; SDK to have an Encryptor.

* Move encryption algo (and DEK provider) into Encryption project; SDK to have an Encryptor. Missed files.

* Fill DEK query support

* Revert csproj tempfix; use test workaround

* Minor code review feedback; add AKV example

Co-authored-by: kirankumarkolli <kirankk@microsoft.com>
This commit is contained in:
abhijitpai 2020-04-11 22:03:13 +05:30 коммит произвёл GitHub
Родитель a2c1f7aa71
Коммит 50ebd8c8ff
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
56 изменённых файлов: 1617 добавлений и 1829 удалений

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Collections.Concurrent;
@ -17,15 +17,10 @@ namespace Microsoft.Azure.Cosmos
/// This (and AeadAes256CbcHmac256EncryptionKey) implementation for Cosmos DB is same as the existing
/// SQL client implementation with StyleCop related changes - also, we restrict to randomized encryption to start with.
/// </summary>
internal class AeadAes256CbcHmac256Algorithm : EncryptionAlgorithm
internal class AeadAes256CbcHmac256Algorithm : DataEncryptionKey
{
internal const string AlgorithmNameConstant = @"AEAD_AES_256_CBC_HMAC_SHA256";
/// <summary>
/// Algorithm Name
/// </summary>
internal override string AlgorithmName { get; } = AlgorithmNameConstant;
/// <summary>
/// Key size in bytes
/// </summary>
@ -88,6 +83,10 @@ namespace Microsoft.Azure.Cosmos
/// </summary>
private static readonly byte[] versionSize = new byte[] { sizeof(byte) };
public override byte[] RawKey => this.dataEncryptionKey.RootKey;
public override string EncryptionAlgorithm => CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized;
/// <summary>
/// Initializes a new instance of AeadAes256CbcHmac256Algorithm algorithm with a given key and encryption type
/// </summary>
@ -129,7 +128,7 @@ namespace Microsoft.Azure.Cosmos
/// </summary>
/// <param name="plainText">Plaintext data to be encrypted</param>
/// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
internal override byte[] EncryptData(byte[] plainText)
public override byte[] EncryptData(byte[] plainText)
{
return this.EncryptData(plainText, hasAuthenticationTag: true);
}
@ -258,7 +257,7 @@ namespace Microsoft.Azure.Cosmos
/// 2. Validate Authentication tag
/// 3. Decrypt the message
/// </summary>
internal override byte[] DecryptData(byte[] cipherText)
public override byte[] DecryptData(byte[] cipherText)
{
return this.DecryptData(cipherText, hasAuthenticationTag: true);
}

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System.Text;

Просмотреть файл

@ -2,28 +2,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Diagnostics;
internal sealed class CachedDekProperties
{
public string DatabaseId { get; }
public DataEncryptionKeyProperties ServerProperties { get; }
public DateTime ServerPropertiesExpiryUtc { get; }
public CachedDekProperties(
string databaseId,
DataEncryptionKeyProperties serverProperties,
DateTime serverPropertiesExpiryUtc)
{
Debug.Assert(databaseId != null);
Debug.Assert(serverProperties != null);
this.DatabaseId = databaseId;
this.ServerProperties = serverProperties;
this.ServerPropertiesExpiryUtc = serverPropertiesExpiryUtc;
}

Просмотреть файл

@ -0,0 +1,88 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Default implementation for a provider to get a data encryption key - wrapped keys are stored in a Cosmos DB container.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
public class CosmosDataEncryptionKeyProvider : DataEncryptionKeyProvider
{
private const string ContainerPartitionKeyPath = "/id";
private DataEncryptionKeyContainerCore dataEncryptionKeyContainerCore;
private Container container;
internal DekCache DekCache { get; }
internal Container Container
{
get
{
if (this.container != null)
{
return this.container;
}
throw new InvalidOperationException($"The {nameof(CosmosDataEncryptionKeyProvider)} was not initialized.");
}
}
public EncryptionKeyWrapProvider EncryptionKeyWrapProvider { get; }
public DataEncryptionKeyContainer DataEncryptionKeyContainer => dataEncryptionKeyContainerCore;
public CosmosDataEncryptionKeyProvider(
EncryptionKeyWrapProvider encryptionKeyWrapProvider,
TimeSpan? dekPropertiesTimeToLive = null)
{
this.EncryptionKeyWrapProvider = encryptionKeyWrapProvider;
this.dataEncryptionKeyContainerCore = new DataEncryptionKeyContainerCore(this);
this.DekCache = new DekCache(dekPropertiesTimeToLive);
}
public async Task InitializeAsync(
Database database,
string containerId,
CancellationToken cancellationToken = default)
{
if(this.container != null)
{
throw new InvalidOperationException($"{nameof(CosmosDataEncryptionKeyProvider)} has already been initialized.");
}
ContainerResponse containerResponse = await database.CreateContainerIfNotExistsAsync(
containerId,
partitionKeyPath: CosmosDataEncryptionKeyProvider.ContainerPartitionKeyPath);
if(containerResponse.Resource.PartitionKeyPath != CosmosDataEncryptionKeyProvider.ContainerPartitionKeyPath)
{
throw new ArgumentException($"Provided container {containerId} did not have the appropriate partition key definition. " +
$"The container needs to be created with PartitionKeyPath set to {CosmosDataEncryptionKeyProvider.ContainerPartitionKeyPath}.",
nameof(containerId));
}
this.container = containerResponse.Container;
}
public override async Task<DataEncryptionKey> FetchDataEncryptionKeyAsync(
string id,
string encryptionAlgorithm,
CancellationToken cancellationToken)
{
(DataEncryptionKeyProperties _, InMemoryRawDek inMemoryRawDek) = await this.dataEncryptionKeyContainerCore.FetchUnwrappedAsync(
id,
diagnosticsContext: CosmosDiagnosticsContext.Create(null),
cancellationToken: cancellationToken);
return inMemoryRawDek.DataEncryptionKey;
}
}
}

Просмотреть файл

@ -0,0 +1,35 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
/// <summary>
/// This is an empty implementation of CosmosDiagnosticsContext which has been plumbed through the DataEncryptionKeyProvider.
/// This may help adding diagnostics more easily in future.
/// </summary>
internal class CosmosDiagnosticsContext
{
private static readonly CosmosDiagnosticsContext UnusedSingleton = new CosmosDiagnosticsContext();
private static readonly IDisposable UnusedScopeSingleton = new Scope();
public static CosmosDiagnosticsContext Create(RequestOptions options)
{
return CosmosDiagnosticsContext.UnusedSingleton;
}
public IDisposable CreateScope(string scope)
{
return CosmosDiagnosticsContext.UnusedScopeSingleton;
}
private class Scope : IDisposable
{
public void Dispose()
{
}
}
}
}

Просмотреть файл

@ -2,21 +2,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
/// <summary>
/// Algorithms for use with client-side encryption support in Azure Cosmos DB.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
enum CosmosEncryptionAlgorithm
public static class CosmosEncryptionAlgorithm
{
/// <summary>
/// Authenticated Encryption algorithm based on https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
/// </summary>
AE_AES_256_CBC_HMAC_SHA_256_RANDOMIZED = 1
public const string AEAes256CbcHmacSha256Randomized = "AEAes256CbcHmacSha256Randomized";
}
}

Просмотреть файл

@ -0,0 +1,73 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Azure.Cosmos.Encryption
{
/// <summary>
/// Provides the default implementation for client-side encryption for Cosmos DB.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class CosmosEncryptor : Encryptor
{
public DataEncryptionKeyProvider DataEncryptionKeyProvider { get; }
/// <summary>
/// Initializes a new instance of Cosmos Encryptor.
/// </summary>
/// <param name="dataEncryptionKeyProvider"></param>
public CosmosEncryptor(DataEncryptionKeyProvider dataEncryptionKeyProvider)
{
this.DataEncryptionKeyProvider = dataEncryptionKeyProvider;
}
/// <inheritdoc/>
public override async Task<byte[]> DecryptAsync(
byte[] cipherText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default)
{
DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync(
dataEncryptionKeyId,
encryptionAlgorithm,
cancellationToken);
if (dek == null)
{
throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync)}.");
}
return dek.DecryptData(cipherText);
}
/// <inheritdoc/>
public override async Task<byte[]> EncryptAsync(
byte[] plainText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default)
{
DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync(
dataEncryptionKeyId,
encryptionAlgorithm,
cancellationToken);
if(dek == null)
{
throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync)}.");
}
return dek.EncryptData(plainText);
}
}
}

Просмотреть файл

@ -0,0 +1,81 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
/// <summary>
/// Abstraction for a data encryption key for use in client-side encryption.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
public abstract class DataEncryptionKey
{
/// <summary>
/// Raw key bytes of the data encryption key.
/// </summary>
public abstract byte[] RawKey { get; }
/// <summary>
/// Encryption algorithm to be used with this data encryption key.
/// </summary>
public abstract string EncryptionAlgorithm { get; }
/// <summary>
/// Encrypts the plainText with a data encryption key.
/// </summary>
/// <param name="plainText">Plain text value to be encrypted.</param>
/// <returns>Encrypted value.</returns>
public abstract byte[] EncryptData(byte[] plainText);
/// <summary>
/// Decrypts the cipherText with a data encryption key.
/// </summary>
/// <param name="cipherText">Ciphertext value to be decrypted.</param>
/// <returns>Plain text.</returns>
public abstract byte[] DecryptData(byte[] cipherText);
/// <summary>
/// Generates raw data encryption key bytes suitable for use with the provided encryption algorithm.
/// </summary>
/// <param name="encryptionAlgorithm">Encryption algorithm the returned key is intended to be used with.</param>
/// <returns>New instance of data encryption key.</returns>
public static byte[] Generate(string encryptionAlgorithm)
{
if (encryptionAlgorithm != CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized)
{
throw new ArgumentException($"Encryption algorithm not supported: {encryptionAlgorithm}", nameof(encryptionAlgorithm));
}
byte[] rawKey = new byte[32];
SecurityUtility.GenerateRandomBytes(rawKey);
return rawKey;
}
/// <summary>
/// Creates a new instance of data encryption key given the raw key bytes
/// suitable for use with the provided encryption algorithm.
/// </summary>
/// <param name="rawKey">Raw key bytes.</param>
/// <param name="encryptionAlgorithm">Encryption algorithm the returned key is intended to be used with.</param>
/// <returns>New instance of data encryption key.</returns>
public static DataEncryptionKey Create(
byte[] rawKey,
string encryptionAlgorithm)
{
if (rawKey == null)
{
throw new ArgumentNullException(nameof(rawKey));
}
if (encryptionAlgorithm != CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized)
{
throw new ArgumentException($"Encryption algorithm not supported: {encryptionAlgorithm}", nameof(encryptionAlgorithm));
}
AeadAes256CbcHmac256EncryptionKey aeKey = new AeadAes256CbcHmac256EncryptionKey(rawKey, AeadAes256CbcHmac256Algorithm.AlgorithmNameConstant);
return new AeadAes256CbcHmac256Algorithm(aeKey, EncryptionType.Randomized, algorithmVersion: 1);
}
}
}

Просмотреть файл

@ -0,0 +1,185 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Container for data encryption keys. Provides methods to create, re-wrap, read and enumerate data encryption keys.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
public abstract class DataEncryptionKeyContainer
{
/// <summary>
/// Generates a data encryption key, wraps it using the key wrap metadata provided
/// with the key wrapping provider in the EncryptionSerializer configured on the client via <see cref="CosmosClientBuilder.WithCustomSerializer"/>,
/// and saves the wrapped data encryption key as an asynchronous operation in the Azure Cosmos service.
/// </summary>
/// <param name="id">Unique identifier for the data encryption key.</param>
/// <param name="encryptionAlgorithm">Encryption algorithm that will be used along with this data encryption key to encrypt/decrypt data.</param>
/// <param name="encryptionKeyWrapMetadata">Metadata used by the configured key wrapping provider in order to wrap the key.</param>
/// <param name="requestOptions">(Optional) The options for the request.</param>
/// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
/// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing the read resource record.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="id"/> is not set.</exception>
/// <exception cref="CosmosException">
/// This exception can encapsulate many different types of errors.
/// To determine the specific error always look at the StatusCode property.
/// Some common codes you may get when creating a data encryption key are:
/// <list type="table">
/// <listheader>
/// <term>StatusCode</term><description>Reason for exception</description>
/// </listheader>
/// <item>
/// <term>400</term><description>BadRequest - This means something was wrong with the request supplied. It is likely that an id was not supplied for the new encryption key.</description>
/// </item>
/// <item>
/// <term>409</term><description>Conflict - This means an <see cref="DataEncryptionKeyProperties"/> with an id matching the id you supplied already existed.</description>
/// </item>
/// </list>
/// </exception>
/// <example>
///
/// <code language="c#">
/// <![CDATA[
/// AzureKeyVaultKeyWrapMetadata wrapMetadata = new AzureKeyVaultKeyWrapMetadata("/path/to/my/akv/secret/v1");
/// await this.cosmosDatabase.CreateDataEncryptionKeyAsync("myKey", wrapMetadata);
/// ]]>
/// </code>
/// </example>
public abstract Task<ItemResponse<DataEncryptionKeyProperties>> CreateDataEncryptionKeyAsync(
string id,
string encryptionAlgorithm,
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Wraps the raw data encryption key (after unwrapping using the old metadata if needed) using the provided
/// metadata with the help of the key wrapping provider in the EncryptionSerializer configured on the client via
/// <see cref="CosmosClientBuilder.WithCustomSerializer"/>, and saves the re-wrapped data encryption key as an asynchronous
/// operation in the Azure Cosmos service.
/// </summary>
/// <param name="id">Unique identifier of the data encryption key.</param>
/// <param name="newWrapMetadata">The metadata using which the data encryption key needs to now be wrapped.</param>
/// <param name="requestOptions">(Optional) The options for the request.</param>
/// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
/// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing details of the data encryption key that was re-wrapped.</returns>
/// <exception cref="CosmosException">
/// This exception can encapsulate many different types of errors.
/// To determine the specific error always look at the StatusCode property.
/// Some common codes you may get when re-wrapping a data encryption key are:
/// <list type="table">
/// <listheader>
/// <term>StatusCode</term>
/// <description>Reason for exception</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>
/// NotFound - This means the resource or parent resource you tried to replace did not exist.
/// </description>
/// </item>
/// <item>
/// <term>429</term>
/// <description>
/// TooManyRequests - This means you have exceeded the number of request units per second.
/// Consult the CosmosException.RetryAfter value to see how long you should wait before retrying this operation.
/// </description>
/// </item>
/// </list>
/// </exception>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// AzureKeyVaultKeyWrapMetadata v2Metadata = new AzureKeyVaultKeyWrapMetadata("/path/to/my/master/key/v2");
/// await key.RewrapAsync(v2Metadata);
/// ]]>
/// </code>
/// </example>
public abstract Task<ItemResponse<DataEncryptionKeyProperties>> RewrapDataEncryptionKeyAsync(
string id,
EncryptionKeyWrapMetadata newWrapMetadata,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Returns an iterator that can be iterated to get properties of data encryption keys.
/// </summary>
/// <param name="startId">(Optional) Starting value of the range (inclusive) of ids of data encryption keys for which properties needs to be returned.</param>
/// <param name="endId">(Optional) Ending value of the range (inclusive) of ids of data encryption keys for which properties needs to be returned.</param>
/// <param name="isDescending">Whether the results should be returned sorted in descending order of id.</param>
/// <param name="continuationToken">(Optional) The continuation token in the Azure Cosmos DB service.</param>
/// <param name="requestOptions">(Optional) The options for the request. Set <see cref="QueryRequestOptions.MaxItemCount"/> to restrict the number of results returned.</param>
/// <returns>An iterator over data encryption keys.</returns>
/// <example>
/// This create the type feed iterator for containers with queryDefinition as input.
/// <code language="c#">
/// <![CDATA[
/// FeedIterator<DataEncryptionKeyProperties> resultSet = this.cosmosDatabase.GetDataEncryptionKeyQueryIterator();
/// while (feedIterator.HasMoreResults)
/// {
/// foreach (DataEncryptionKeyProperties properties in await feedIterator.ReadNextAsync())
/// {
/// Console.WriteLine(properties.Id);
/// }
/// }
/// ]]>
/// </code>
/// </example>
/// <remarks>
/// <see cref="DataEncryptionKey.ReadDataEncryptionKeyAsync" /> is recommended for single data encryption key look-up.
/// </remarks>
public abstract FeedIterator<T> GetDataEncryptionKeyQueryIterator<T>(
string queryText = null,
string continuationToken = null,
QueryRequestOptions requestOptions = null);
/// <summary>
/// Reads the properties of a data encryption key from the Azure Cosmos service as an asynchronous operation.
/// </summary>
/// <param name="id">Unique identifier of the data encryption key.</param>
/// <param name="requestOptions">(Optional) The options for the request.</param>
/// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
/// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing details of the data encryption key that was read.</returns>
/// <exception cref="CosmosException">
/// This exception can encapsulate many different types of errors.
/// To determine the specific error always look at the StatusCode property.
/// Some common codes you may get when reading a data encryption key are:
/// <list type="table">
/// <listheader>
/// <term>StatusCode</term>
/// <description>Reason for exception</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>
/// NotFound - This means the resource or parent resource you tried to read did not exist.
/// </description>
/// </item>
/// <item>
/// <term>429</term>
/// <description>
/// TooManyRequests - This means you have exceeded the number of request units per second.
/// Consult the CosmosException.RetryAfter value to see how long you should wait before retrying this operation.
/// </description>
/// </item>
/// </list>
/// </exception>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// DataEncryptionKey key = this.database.GetDataEncryptionKey("keyId");
/// DataEncryptionKeyProperties keyProperties = await key.ReadAsync();
/// ]]>
/// </code>
/// </example>
public abstract Task<ItemResponse<DataEncryptionKeyProperties>> ReadDataEncryptionKeyAsync(
string id,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken));
}
}

Просмотреть файл

@ -0,0 +1,232 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
internal class DataEncryptionKeyContainerCore : DataEncryptionKeyContainer
{
internal CosmosDataEncryptionKeyProvider DekProvider { get; }
public DataEncryptionKeyContainerCore(CosmosDataEncryptionKeyProvider dekProvider)
{
this.DekProvider = dekProvider;
}
public override FeedIterator<T> GetDataEncryptionKeyQueryIterator<T>(
string queryText = null,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
return this.DekProvider.Container.GetItemQueryIterator<T>(queryText, continuationToken, requestOptions);
}
public override async Task<ItemResponse<DataEncryptionKeyProperties>> CreateDataEncryptionKeyAsync(
string id,
string encryptionAlgorithm,
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
if (encryptionAlgorithm != CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized)
{
throw new ArgumentException(string.Format("Unsupported Encryption Algorithm {0}", encryptionAlgorithm), nameof(encryptionAlgorithm));
}
if (encryptionKeyWrapMetadata == null)
{
throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata));
}
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
byte[] rawDek = DataEncryptionKey.Generate(encryptionAlgorithm);
(byte[] wrappedDek, EncryptionKeyWrapMetadata updatedMetadata, InMemoryRawDek inMemoryRawDek) = await this.WrapAsync(
id,
rawDek,
encryptionAlgorithm,
encryptionKeyWrapMetadata,
diagnosticsContext,
cancellationToken);
DataEncryptionKeyProperties dekProperties = new DataEncryptionKeyProperties(id, encryptionAlgorithm, wrappedDek, updatedMetadata, DateTime.UtcNow);
ItemResponse<DataEncryptionKeyProperties> dekResponse = await this.DekProvider.Container.CreateItemAsync(dekProperties, new PartitionKey(dekProperties.Id), cancellationToken: cancellationToken);
this.DekProvider.DekCache.SetDekProperties(id, dekResponse.Resource);
this.DekProvider.DekCache.SetRawDek(id, inMemoryRawDek);
return dekResponse;
}
/// <inheritdoc/>
public override async Task<ItemResponse<DataEncryptionKeyProperties>> ReadDataEncryptionKeyAsync(
string id,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
ItemResponse<DataEncryptionKeyProperties> response = await this.ReadInternalAsync(
id,
requestOptions,
diagnosticsContext: null,
cancellationToken: cancellationToken);
this.DekProvider.DekCache.SetDekProperties(id, response.Resource);
return response;
}
/// <inheritdoc/>
public override async Task<ItemResponse<DataEncryptionKeyProperties>> RewrapDataEncryptionKeyAsync(
string id,
EncryptionKeyWrapMetadata newWrapMetadata,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (newWrapMetadata == null)
{
throw new ArgumentNullException(nameof(newWrapMetadata));
}
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
(DataEncryptionKeyProperties dekProperties, InMemoryRawDek inMemoryRawDek) = await this.FetchUnwrappedAsync(
id,
diagnosticsContext,
cancellationToken);
(byte[] wrappedDek, EncryptionKeyWrapMetadata updatedMetadata, InMemoryRawDek updatedRawDek) = await this.WrapAsync(
id,
inMemoryRawDek.DataEncryptionKey.RawKey,
dekProperties.EncryptionAlgorithm,
newWrapMetadata,
diagnosticsContext,
cancellationToken);
if (requestOptions == null)
{
requestOptions = new ItemRequestOptions();
}
requestOptions.IfMatchEtag = dekProperties.ETag;
DataEncryptionKeyProperties newDekProperties = new DataEncryptionKeyProperties(dekProperties);
newDekProperties.WrappedDataEncryptionKey = wrappedDek;
newDekProperties.EncryptionKeyWrapMetadata = updatedMetadata;
ItemResponse<DataEncryptionKeyProperties> response = await this.DekProvider.Container.ReplaceItemAsync(
newDekProperties,
newDekProperties.Id,
new PartitionKey(newDekProperties.Id),
requestOptions,
cancellationToken);
Debug.Assert(response.Resource != null);
this.DekProvider.DekCache.SetDekProperties(id, response.Resource);
this.DekProvider.DekCache.SetRawDek(id, updatedRawDek);
return response;
}
internal async Task<(DataEncryptionKeyProperties, InMemoryRawDek)> FetchUnwrappedAsync(
string id,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
DataEncryptionKeyProperties dekProperties = await this.DekProvider.DekCache.GetOrAddDekPropertiesAsync(
id,
this.ReadResourceAsync,
diagnosticsContext,
cancellationToken);
InMemoryRawDek inMemoryRawDek = await this.DekProvider.DekCache.GetOrAddRawDekAsync(
dekProperties,
this.UnwrapAsync,
diagnosticsContext,
cancellationToken);
return (dekProperties, inMemoryRawDek);
}
internal async Task<(byte[], EncryptionKeyWrapMetadata, InMemoryRawDek)> WrapAsync(
string id,
byte[] key,
string encryptionAlgorithm,
EncryptionKeyWrapMetadata metadata,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
EncryptionKeyWrapResult keyWrapResponse;
using (diagnosticsContext.CreateScope("WrapDataEncryptionKey"))
{
keyWrapResponse = await this.DekProvider.EncryptionKeyWrapProvider.WrapKeyAsync(key, metadata, cancellationToken);
}
// Verify
DataEncryptionKeyProperties tempDekProperties = new DataEncryptionKeyProperties(id, encryptionAlgorithm, keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, DateTime.UtcNow);
InMemoryRawDek roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken);
if (!roundTripResponse.DataEncryptionKey.RawKey.SequenceEqual(key))
{
throw new InvalidOperationException("The key wrapping provider configured was unable to unwrap the wrapped key correctly.");
}
return (keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, roundTripResponse);
}
internal async Task<InMemoryRawDek> UnwrapAsync(
DataEncryptionKeyProperties dekProperties,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
EncryptionKeyUnwrapResult unwrapResult;
using (diagnosticsContext.CreateScope("UnwrapDataEncryptionKey"))
{
unwrapResult = await this.DekProvider.EncryptionKeyWrapProvider.UnwrapKeyAsync(
dekProperties.WrappedDataEncryptionKey,
dekProperties.EncryptionKeyWrapMetadata,
cancellationToken);
}
DataEncryptionKey dek = DataEncryptionKey.Create(unwrapResult.DataEncryptionKey, dekProperties.EncryptionAlgorithm);
return new InMemoryRawDek(dek, unwrapResult.ClientCacheTimeToLive);
}
private async Task<DataEncryptionKeyProperties> ReadResourceAsync(
string id,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
using (diagnosticsContext.CreateScope("ReadDataEncryptionKey"))
{
return await this.ReadInternalAsync(
id: id,
requestOptions: null,
diagnosticsContext: diagnosticsContext,
cancellationToken: cancellationToken);
}
}
private async Task<ItemResponse<DataEncryptionKeyProperties>> ReadInternalAsync(
string id,
RequestOptions requestOptions,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
return await this.DekProvider.Container.ReadItemAsync<DataEncryptionKeyProperties>(
id,
new PartitionKey(id),
cancellationToken: cancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,83 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Threading;
using System.Threading.Tasks;
internal class DataEncryptionKeyContainerInlineCore : DataEncryptionKeyContainer
{
private readonly DataEncryptionKeyContainerCore dataEncryptionKeyContainerCore;
public DataEncryptionKeyContainerInlineCore(DataEncryptionKeyContainerCore dataEncryptionKeyContainerCore)
{
if (dataEncryptionKeyContainerCore == null)
{
throw new ArgumentNullException(nameof(dataEncryptionKeyContainerCore));
}
this.dataEncryptionKeyContainerCore = dataEncryptionKeyContainerCore;
}
public override FeedIterator<T> GetDataEncryptionKeyQueryIterator<T>(
string queryText = null,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
return this.dataEncryptionKeyContainerCore.GetDataEncryptionKeyQueryIterator<T>(
queryText,
continuationToken,
requestOptions);
}
public override Task<ItemResponse<DataEncryptionKeyProperties>> CreateDataEncryptionKeyAsync(
string id,
string encryptionAlgorithm,
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
return TaskHelper.RunInlineIfNeededAsync(() =>
this.dataEncryptionKeyContainerCore.CreateDataEncryptionKeyAsync(id, encryptionAlgorithm, encryptionKeyWrapMetadata, requestOptions, cancellationToken));
}
/// <inheritdoc/>
public override Task<ItemResponse<DataEncryptionKeyProperties>> ReadDataEncryptionKeyAsync(
string id,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
return TaskHelper.RunInlineIfNeededAsync(() =>
this.dataEncryptionKeyContainerCore.ReadDataEncryptionKeyAsync(id, requestOptions, cancellationToken));
}
/// <inheritdoc/>
public override Task<ItemResponse<DataEncryptionKeyProperties>> RewrapDataEncryptionKeyAsync(
string id,
EncryptionKeyWrapMetadata newWrapMetadata,
ItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
if (newWrapMetadata == null)
{
throw new ArgumentNullException(nameof(newWrapMetadata));
}
return TaskHelper.RunInlineIfNeededAsync(() =>
this.dataEncryptionKeyContainerCore.RewrapDataEncryptionKeyAsync(id, newWrapMetadata, requestOptions, cancellationToken));
}
}
}

Просмотреть файл

@ -2,23 +2,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;
/// <summary>
/// Details of an encryption key for use with the Azure Cosmos DB service.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class DataEncryptionKeyProperties : IEquatable<DataEncryptionKeyProperties>
public class DataEncryptionKeyProperties : IEquatable<DataEncryptionKeyProperties>
{
/// <summary>
/// Initializes a new instance of <see cref="DataEncryptionKeyProperties"/>.
@ -29,14 +23,16 @@ namespace Microsoft.Azure.Cosmos
/// <param name="encryptionKeyWrapMetadata">Metadata used by the configured key wrapping provider in order to unwrap the key.</param>
public DataEncryptionKeyProperties(
string id,
CosmosEncryptionAlgorithm encryptionAlgorithm,
string encryptionAlgorithm,
byte[] wrappedDataEncryptionKey,
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata)
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
DateTime createdTime)
{
this.Id = !string.IsNullOrEmpty(id) ? id : throw new ArgumentNullException(nameof(id));
this.EncryptionAlgorithmId = encryptionAlgorithm;
this.EncryptionAlgorithm = encryptionAlgorithm;
this.WrappedDataEncryptionKey = wrappedDataEncryptionKey ?? throw new ArgumentNullException(nameof(wrappedDataEncryptionKey));
this.EncryptionKeyWrapMetadata = encryptionKeyWrapMetadata ?? throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata));
this.CreatedTime = createdTime;
}
/// <summary>
@ -51,7 +47,7 @@ namespace Microsoft.Azure.Cosmos
this.CreatedTime = source.CreatedTime;
this.ETag = source.ETag;
this.Id = source.Id;
this.EncryptionAlgorithmId = source.EncryptionAlgorithmId;
this.EncryptionAlgorithm = source.EncryptionAlgorithm;
this.EncryptionKeyWrapMetadata = new EncryptionKeyWrapMetadata(source.EncryptionKeyWrapMetadata);
this.LastModified = source.LastModified;
this.ResourceId = source.ResourceId;
@ -75,32 +71,32 @@ namespace Microsoft.Azure.Cosmos
/// '/', '\\', '?', '#'
/// </para>
/// </remarks>
[JsonProperty(PropertyName = Constants.Properties.Id)]
[JsonProperty(PropertyName = "id")]
public string Id { get; internal set; }
/// <summary>
/// Encryption algorithm that will be used along with this data encryption key to encrypt/decrypt data.
/// </summary>
[JsonProperty(PropertyName = Constants.Properties.EncryptionAlgorithmId, NullValueHandling = NullValueHandling.Ignore)]
public CosmosEncryptionAlgorithm EncryptionAlgorithmId { get; internal set; }
[JsonProperty(PropertyName = "encryptionAlgorithm", NullValueHandling = NullValueHandling.Ignore)]
public string EncryptionAlgorithm { get; internal set; }
/// <summary>
/// Wrapped form of the data encryption key.
/// </summary>
[JsonProperty(PropertyName = Constants.Properties.WrappedDataEncryptionKey, NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = "wrappedDataEncryptionKey", NullValueHandling = NullValueHandling.Ignore)]
public byte[] WrappedDataEncryptionKey { get; internal set; }
/// <summary>
/// Metadata for the wrapping provider that can be used to unwrap the wrapped data encryption key.
/// </summary>
[JsonProperty(PropertyName = Constants.Properties.KeyWrapMetadata, NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = "keyWrapMetadata", NullValueHandling = NullValueHandling.Ignore)]
public EncryptionKeyWrapMetadata EncryptionKeyWrapMetadata { get; internal set; }
/// <summary>
/// Gets the creation time of the resource from the Azure Cosmos DB service.
/// </summary>
[JsonConverter(typeof(UnixDateTimeConverter))]
[JsonProperty(PropertyName = Constants.Properties.CreatedTime, NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = "createTime", NullValueHandling = NullValueHandling.Ignore)]
public DateTime? CreatedTime { get; internal set; }
/// <summary>
@ -112,7 +108,7 @@ namespace Microsoft.Azure.Cosmos
/// <remarks>
/// ETags are used for concurrency checking when updating resources.
/// </remarks>
[JsonProperty(PropertyName = Constants.Properties.ETag, NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = "_etag", NullValueHandling = NullValueHandling.Ignore)]
public string ETag { get; internal set; }
/// <summary>
@ -120,7 +116,7 @@ namespace Microsoft.Azure.Cosmos
/// </summary>
/// <value>The last modified time stamp associated with the resource.</value>
[JsonConverter(typeof(UnixDateTimeConverter))]
[JsonProperty(PropertyName = Constants.Properties.LastModified, NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = "_ts", NullValueHandling = NullValueHandling.Ignore)]
public DateTime? LastModified { get; internal set; }
/// <summary>
@ -131,7 +127,7 @@ namespace Microsoft.Azure.Cosmos
/// A self-link is a static addressable Uri for each resource within a database account and follows the Azure Cosmos DB resource model.
/// E.g. a self-link for a document could be dbs/db_resourceid/colls/coll_resourceid/documents/doc_resourceid
/// </remarks>
[JsonProperty(PropertyName = Constants.Properties.SelfLink, NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = "_self", NullValueHandling = NullValueHandling.Ignore)]
public virtual string SelfLink { get; internal set; }
/// <summary>
@ -145,7 +141,7 @@ namespace Microsoft.Azure.Cosmos
/// resource whether that is a database, a collection or a document.
/// These resource ids are used when building up SelfLinks, a static addressable Uri for each resource within a database account.
/// </remarks>
[JsonProperty(PropertyName = Constants.Properties.RId, NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = "_rid", NullValueHandling = NullValueHandling.Ignore)]
internal string ResourceId { get; set; }
/// <summary>
@ -167,7 +163,7 @@ namespace Microsoft.Azure.Cosmos
{
return other != null &&
this.Id == other.Id &&
this.EncryptionAlgorithmId == other.EncryptionAlgorithmId &&
this.EncryptionAlgorithm == other.EncryptionAlgorithm &&
DataEncryptionKeyProperties.Equals(this.WrappedDataEncryptionKey, other.WrappedDataEncryptionKey) &&
EqualityComparer<EncryptionKeyWrapMetadata>.Default.Equals(this.EncryptionKeyWrapMetadata, other.EncryptionKeyWrapMetadata) &&
this.CreatedTime == other.CreatedTime &&
@ -185,7 +181,7 @@ namespace Microsoft.Azure.Cosmos
{
int hashCode = -1673632966;
hashCode = (hashCode * -1521134295) + EqualityComparer<string>.Default.GetHashCode(this.Id);
hashCode = (hashCode * -1521134295) + EqualityComparer<CosmosEncryptionAlgorithm>.Default.GetHashCode(this.EncryptionAlgorithmId);
hashCode = (hashCode * -1521134295) + EqualityComparer<string>.Default.GetHashCode(this.EncryptionAlgorithm);
hashCode = (hashCode * -1521134295) + EqualityComparer<EncryptionKeyWrapMetadata>.Default.GetHashCode(this.EncryptionKeyWrapMetadata);
hashCode = (hashCode * -1521134295) + EqualityComparer<DateTime?>.Default.GetHashCode(this.CreatedTime);
hashCode = (hashCode * -1521134295) + EqualityComparer<string>.Default.GetHashCode(this.ETag);

Просмотреть файл

@ -0,0 +1,28 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Abstraction for a provider to get data encryption keys for use in client-side encryption.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
public abstract class DataEncryptionKeyProvider
{
/// <summary>
/// Retrieves the data encryption key for the given id.
/// </summary>
/// <param name="id">Identifier of the data encryption key.</param>
/// <param name="encryptionAlgorithm">Encryption algorithm that the retrieved key will be used with.</param>
/// <param name="cancellationToken">Token for request cancellation.</param>
/// <returns>Data encryption key bytes.</returns>
public abstract Task<DataEncryptionKey> FetchDataEncryptionKeyAsync(
string id,
string encryptionAlgorithm,
CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -0,0 +1,113 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Common;
internal class DekCache
{
private readonly TimeSpan dekPropertiesTimeToLive;
// Internal for unit testing
internal AsyncCache<string, CachedDekProperties> DekPropertiesCache { get; } = new AsyncCache<string, CachedDekProperties>();
internal AsyncCache<string, InMemoryRawDek> RawDekCache { get; } = new AsyncCache<string, InMemoryRawDek>();
public DekCache(TimeSpan? dekPropertiesTimeToLive = null)
{
if (dekPropertiesTimeToLive.HasValue)
{
this.dekPropertiesTimeToLive = dekPropertiesTimeToLive.Value;
}
else
{
this.dekPropertiesTimeToLive = TimeSpan.FromMinutes(30);
}
}
public async Task<DataEncryptionKeyProperties> GetOrAddDekPropertiesAsync(
string dekId,
Func<string, CosmosDiagnosticsContext, CancellationToken, Task<DataEncryptionKeyProperties>> fetcher,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
CachedDekProperties cachedDekProperties = await this.DekPropertiesCache.GetAsync(
dekId,
null,
() => this.FetchAsync(dekId, fetcher, diagnosticsContext, cancellationToken),
cancellationToken);
if (cachedDekProperties.ServerPropertiesExpiryUtc <= DateTime.UtcNow)
{
cachedDekProperties = await this.DekPropertiesCache.GetAsync(
dekId,
null,
() => this.FetchAsync(dekId, fetcher, diagnosticsContext, cancellationToken),
cancellationToken,
forceRefresh: true);
}
return cachedDekProperties.ServerProperties;
}
public async Task<InMemoryRawDek> GetOrAddRawDekAsync(
DataEncryptionKeyProperties dekProperties,
Func<DataEncryptionKeyProperties, CosmosDiagnosticsContext, CancellationToken, Task<InMemoryRawDek>> unwrapper,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
InMemoryRawDek inMemoryRawDek = await this.RawDekCache.GetAsync(
dekProperties.SelfLink,
null,
() => unwrapper(dekProperties, diagnosticsContext, cancellationToken),
cancellationToken);
if (inMemoryRawDek.RawDekExpiry <= DateTime.UtcNow)
{
inMemoryRawDek = await this.RawDekCache.GetAsync(
dekProperties.SelfLink,
null,
() => unwrapper(dekProperties, diagnosticsContext, cancellationToken),
cancellationToken,
forceRefresh: true);
}
return inMemoryRawDek;
}
public void SetDekProperties(string dekId, DataEncryptionKeyProperties dekProperties)
{
CachedDekProperties cachedDekProperties = new CachedDekProperties(dekProperties, DateTime.UtcNow + this.dekPropertiesTimeToLive);
this.DekPropertiesCache.Set(dekId, cachedDekProperties);
}
public void SetRawDek(string dekId, InMemoryRawDek inMemoryRawDek)
{
this.RawDekCache.Set(dekId, inMemoryRawDek);
}
public async Task RemoveAsync(string dekId)
{
CachedDekProperties cachedDekProperties = await this.DekPropertiesCache.RemoveAsync(dekId);
if (cachedDekProperties != null)
{
this.RawDekCache.Remove(dekId);
}
}
private async Task<CachedDekProperties> FetchAsync(
string dekId,
Func<string, CosmosDiagnosticsContext, CancellationToken, Task<DataEncryptionKeyProperties>> fetcher,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
DataEncryptionKeyProperties serverProperties = await fetcher(dekId, diagnosticsContext, cancellationToken);
return new CachedDekProperties(serverProperties, DateTime.UtcNow + this.dekPropertiesTimeToLive);
}
}
}

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;

Просмотреть файл

@ -2,19 +2,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
/// <summary>
/// Result from a <see cref="EncryptionKeyWrapProvider"/> on unwrapping a wrapped data encryption key.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class EncryptionKeyUnwrapResult
public class EncryptionKeyUnwrapResult
{
/// <summary>
/// Initializes a new instance of the result of unwrapping a wrapped data encryption key.
@ -42,12 +37,12 @@ namespace Microsoft.Azure.Cosmos
/// <summary>
/// Raw form of the data encryption key.
/// </summary>
internal byte[] DataEncryptionKey { get; }
public byte[] DataEncryptionKey { get; }
/// <summary>
/// Amount of time after which the raw data encryption key must not be used
/// without invoking the <see cref="EncryptionKeyWrapProvider.UnwrapKeyAsync"/> again.
/// </summary>
internal TimeSpan ClientCacheTimeToLive { get; }
public TimeSpan ClientCacheTimeToLive { get; }
}
}

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Collections.Generic;
@ -12,12 +12,7 @@ namespace Microsoft.Azure.Cosmos
/// Metadata that a key wrapping provider can use to wrap/unwrap data encryption keys.
/// <seealso cref="EncryptionKeyWrapProvider" />
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class EncryptionKeyWrapMetadata : IEquatable<EncryptionKeyWrapMetadata>
public class EncryptionKeyWrapMetadata : IEquatable<EncryptionKeyWrapMetadata>
{
// For JSON deserialize
private EncryptionKeyWrapMetadata()

Просмотреть файл

@ -2,9 +2,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Threading;
using System.Threading.Tasks;
@ -13,12 +12,7 @@ namespace Microsoft.Azure.Cosmos
/// Implementations are expected to ensure that master keys are highly available and protected against accidental deletion.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
abstract class EncryptionKeyWrapProvider
public abstract class EncryptionKeyWrapProvider
{
/// <summary>
/// Wraps (i.e. encrypts) the provided data encryption key.

Просмотреть файл

@ -2,19 +2,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
/// <summary>
/// Result from a <see cref="EncryptionKeyWrapProvider"/> on wrapping a data encryption key.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class EncryptionKeyWrapResult
public class EncryptionKeyWrapResult
{
/// <summary>
/// Initializes a new instance of the result of wrapping a data encryption key.
@ -33,11 +28,11 @@ namespace Microsoft.Azure.Cosmos
/// <summary>
/// Wrapped form of the data encryption key.
/// </summary>
internal byte[] WrappedDataEncryptionKey { get; }
public byte[] WrappedDataEncryptionKey { get; }
/// <summary>
/// Metadata that can be used by the wrap provider to unwrap the key.
/// </summary>
internal EncryptionKeyWrapMetadata EncryptionKeyWrapMetadata { get; }
public EncryptionKeyWrapMetadata EncryptionKeyWrapMetadata { get; }
}
}

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
/// <summary>
/// Encryption types that may be supported.

Просмотреть файл

@ -2,22 +2,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
internal class InMemoryRawDek
{
public byte[] RawDek { get; }
public EncryptionAlgorithm AlgorithmUsingRawDek { get; }
public DataEncryptionKey DataEncryptionKey { get; }
public DateTime RawDekExpiry { get; }
public InMemoryRawDek(byte[] rawDek, EncryptionAlgorithm algorithmUsingRawDek, TimeSpan clientCacheTimeToLive)
public InMemoryRawDek(DataEncryptionKey dataEncryptionKey, TimeSpan clientCacheTimeToLive)
{
this.RawDek = rawDek;
this.AlgorithmUsingRawDek = algorithmUsingRawDek;
this.DataEncryptionKey = dataEncryptionKey;
this.RawDekExpiry = DateTime.UtcNow + clientCacheTimeToLive;
}
}

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Runtime.Serialization;
@ -10,17 +10,12 @@ namespace Microsoft.Azure.Cosmos
/// <summary>
/// This exception would be thrown by an <see cref="EncryptionKeyWrapProvider"/> when trying to use
/// a master key that does not exist. This allows for scenarios where the master key has been rotated
/// and <see cref="DataEncryptionKey.RewrapAsync"/> has been called to re-wrap the data encryption keys
/// and <see cref="DataEncryptionKey.RewrapDataEncryptionKeyAsync"/> has been called to re-wrap the data encryption keys
/// that were referencing the older version of the master key before removing the old version of the master key
/// but the client instance is trying to use a cached version of the metadata that references a master key
/// that no longer exists.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class KeyNotFoundException : Exception
public class KeyNotFoundException : Exception
{
/// <summary>
/// Creates a new instance of master key not found exception.

Просмотреть файл

@ -0,0 +1,77 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using Microsoft.Azure.Cosmos.Encryption.KeyVault;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Provides the default implementation for client-side encryption for Cosmos DB.
/// Azure Key Vault has keys which are used to control the data access.
/// Data Encryption Keys (which intermediate keys) are stored in a Cosmos DB container
/// that instances of this class are initialized with after wrapping (aka encrypting)
/// it using the Azure Key Vault key provided during the creation of each Data Encryption Key.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class AzureKeyVaultCosmosEncryptor : Encryptor
{
private CosmosEncryptor cosmosEncryptor;
private CosmosDataEncryptionKeyProvider cosmosDekProvider;
public DataEncryptionKeyContainer DataEncryptionKeyContainer => this.cosmosDekProvider.DataEncryptionKeyContainer;
public AzureKeyVaultCosmosEncryptor(
string clientId,
string certificateThumbprint)
{
EncryptionKeyWrapProvider wrapProvider = new AzureKeyVaultKeyWrapProvider(
clientId,
certificateThumbprint);
this.cosmosDekProvider = new CosmosDataEncryptionKeyProvider(wrapProvider);
this.cosmosEncryptor = new CosmosEncryptor(this.cosmosDekProvider);
}
public Task InitializeAsync(
Database dekStorageDatabase,
string dekStorageContainerId)
{
return this.cosmosDekProvider.InitializeAsync(dekStorageDatabase, dekStorageContainerId);
}
public override Task<byte[]> EncryptAsync(
byte[] plainText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default)
{
return this.cosmosEncryptor.EncryptAsync(
plainText,
dataEncryptionKeyId,
encryptionAlgorithm,
cancellationToken);
}
public override Task<byte[]> DecryptAsync(
byte[] cipherText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default)
{
return this.cosmosEncryptor.DecryptAsync(
cipherText,
dataEncryptionKeyId,
encryptionAlgorithm,
cancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Azure.Cosmos.Encryption.KeyVault
{
internal class AzureKeyVaultKeyWrapProvider : EncryptionKeyWrapProvider
{
public AzureKeyVaultKeyWrapProvider(
string clientId,
string certificateThumbprint)
{
}
public override Task<EncryptionKeyUnwrapResult> UnwrapKeyAsync(
byte[] wrappedKey,
EncryptionKeyWrapMetadata metadata,
CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
public override Task<EncryptionKeyWrapResult> WrapKeyAsync(
byte[] key,
EncryptionKeyWrapMetadata metadata,
CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Microsoft.Azure.Cosmos.Encryption</AssemblyName>
<RootNamespace>Microsoft.Azure.Cosmos.Encryption</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Microsoft.Azure.Cosmos\src\Microsoft.Azure.Cosmos.csproj" />
</ItemGroup>
<PropertyGroup>
<DefineConstants Condition=" '$(IsNightly)' == 'true' or '$(IsPreview)' == 'true' ">$(DefineConstants);PREVIEW</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<SigningType>Product</SigningType>
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>..\..\35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
</Project>

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Diagnostics;

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;

Просмотреть файл

@ -0,0 +1,71 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
/// <summary>
/// Converts a DateTime object to and from JSON.
/// DateTime is represented as the total number of seconds
/// that have elapsed since January 1, 1970 (midnight UTC/GMT),
/// not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
/// </summary>
internal sealed class UnixDateTimeConverter : DateTimeConverterBase
{
private static DateTime UnixStartTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
/// <summary>
/// Writes the JSON representation of the DateTime object.
/// </summary>
/// <param name="writer">The Newtonsoft.Json.JsonWriter to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is DateTime)
{
Int64 totalSeconds = (Int64)((DateTime)value - UnixStartTime).TotalSeconds;
writer.WriteValue(totalSeconds);
}
else
{
throw new ArgumentException("Invalid data; expected DateTime", "value");
}
}
/// <summary>
/// Reads the JSON representation of the DateTime object.
/// </summary>
/// <param name="reader">The Newtonsoft.Json.JsonReader to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The DateTime object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != Newtonsoft.Json.JsonToken.Integer)
{
throw new Exception("Invalid value; expected integer.");
}
double totalSeconds = 0;
try
{
totalSeconds = Convert.ToDouble(reader.Value, CultureInfo.InvariantCulture);
}
catch
{
throw new Exception("Invalid data; expected double");
}
return UnixStartTime.AddSeconds(totalSeconds);
}
}
}

Просмотреть файл

@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Emul
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Performance.Tests", "Microsoft.Azure.Cosmos\tests\Microsoft.Azure.Cosmos.Performance.Tests\Microsoft.Azure.Cosmos.Performance.Tests.csproj", "{0392A590-68EF-4283-94D8-33F350BEC9BF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Encryption", "Microsoft.Azure.Cosmos.Encryption\src\Microsoft.Azure.Cosmos.Encryption.csproj", "{4D0FBC91-268E-44DD-AC10-6851A69C52FB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Cover|Any CPU = Cover|Any CPU
@ -69,6 +71,18 @@ Global
{0392A590-68EF-4283-94D8-33F350BEC9BF}.Release|Any CPU.Build.0 = Release|Any CPU
{0392A590-68EF-4283-94D8-33F350BEC9BF}.Release|x64.ActiveCfg = Release|Any CPU
{0392A590-68EF-4283-94D8-33F350BEC9BF}.Release|x64.Build.0 = Release|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Cover|Any CPU.ActiveCfg = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Cover|Any CPU.Build.0 = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Cover|x64.ActiveCfg = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Cover|x64.Build.0 = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Debug|x64.ActiveCfg = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Debug|x64.Build.0 = Debug|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Release|Any CPU.Build.0 = Release|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Release|x64.ActiveCfg = Release|Any CPU
{4D0FBC91-268E-44DD-AC10-6851A69C52FB}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -19,6 +19,8 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Table.Tests" + AssemblyKeys.TestPublicKey)]
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Encryption.KeyVault" + AssemblyKeys.ProductPublicKey)]
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Encryption.KeyVault" + AssemblyKeys.TestPublicKey)]
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Encryption" + AssemblyKeys.ProductPublicKey)]
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Encryption" + AssemblyKeys.TestPublicKey)]
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Performance.Tests" + AssemblyKeys.ProductPublicKey)]
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Performance.Tests" + AssemblyKeys.TestPublicKey)]
[assembly: InternalsVisibleTo("Microsoft.Azure.Cosmos.Tests" + AssemblyKeys.ProductPublicKey)]

24
Microsoft.Azure.Cosmos/src/ClientResources.Designer.cs сгенерированный
Просмотреть файл

@ -268,20 +268,11 @@ namespace Microsoft.Azure.Cosmos {
}
/// <summary>
/// Looks up a localized string similar to A data encryption key with the provided name was not found - please ensure it has been created..
/// Looks up a localized string similar to The client was not configured to allow for encryption. Create the client by using cosmosClientBuilder.WithEncryptor..
/// </summary>
internal static string DataEncryptionKeyNotFound {
internal static string EncryptorNotConfigured {
get {
return ResourceManager.GetString("DataEncryptionKeyNotFound", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The client was not configured to allow for encryption. Create the client by using cosmosClientBuilder.WithEncryptionKeyWrapProvider..
/// </summary>
internal static string EncryptionKeyWrapProviderNotConfigured {
get {
return ResourceManager.GetString("EncryptionKeyWrapProviderNotConfigured", resourceCulture);
return ResourceManager.GetString("EncryptorNotConfigured", resourceCulture);
}
}
@ -456,15 +447,6 @@ namespace Microsoft.Azure.Cosmos {
}
}
/// <summary>
/// Looks up a localized string similar to The key wrapping provider configured was unable to unwrap the wrapped key correctly..
/// </summary>
internal static string KeyWrappingDidNotRoundtrip {
get {
return ResourceManager.GetString("KeyWrappingDidNotRoundtrip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to MediaLink is invalid.
/// </summary>

Просмотреть файл

@ -294,14 +294,8 @@
<data name="UnsupportedBulkRequestOptions" xml:space="preserve">
<value>Consistency, Session, and Triggers are not allowed when AllowBulkExecution is set to true.</value>
</data>
<data name="KeyWrappingDidNotRoundtrip" xml:space="preserve">
<value>The key wrapping provider configured was unable to unwrap the wrapped key correctly.</value>
</data>
<data name="DataEncryptionKeyNotFound" xml:space="preserve">
<value>A data encryption key with the provided name was not found - please ensure it has been created.</value>
</data>
<data name="EncryptionKeyWrapProviderNotConfigured" xml:space="preserve">
<value>The client was not configured to allow for encryption. Create the client by using cosmosClientBuilder.WithEncryptionKeyWrapProvider.</value>
<data name="EncryptorNotConfigured" xml:space="preserve">
<value>The client was not configured to allow for encryption. Create the client by using cosmosClientBuilder.WithEncryptor.</value>
</data>
<data name="InvalidRequestWithEncryptionOptions" xml:space="preserve">
<value>Encryption options may not be specified on this request.</value>

Просмотреть файл

@ -385,7 +385,7 @@ namespace Microsoft.Azure.Cosmos
}
/// <summary>
/// Provider to wrap/unwrap data encryption keys for client side encryption.
/// Provider that allows encrypting and decrypting data.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
[JsonIgnore]
@ -394,7 +394,7 @@ namespace Microsoft.Azure.Cosmos
#else
internal
#endif
EncryptionKeyWrapProvider EncryptionKeyWrapProvider { get; set; }
Encryptor Encryptor { get; set; }
/// <summary>
/// Limits the operations to the provided endpoint on the CosmosClient.

Просмотреть файл

@ -1,158 +0,0 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Common;
internal class DekCache
{
private readonly TimeSpan dekPropertiesTimeToLive;
// Internal for unit testing
internal AsyncCache<Uri, CachedDekProperties> DekPropertiesByNameLinkUriCache { get; } = new AsyncCache<Uri, CachedDekProperties>();
internal AsyncCache<string, CachedDekProperties> DekPropertiesByRidSelfLinkCache { get; } = new AsyncCache<string, CachedDekProperties>();
internal AsyncCache<string, InMemoryRawDek> RawDekByRidSelfLinkCache { get; } = new AsyncCache<string, InMemoryRawDek>();
public DekCache(TimeSpan? dekPropertiesTimeToLive = null)
{
if (dekPropertiesTimeToLive.HasValue)
{
this.dekPropertiesTimeToLive = dekPropertiesTimeToLive.Value;
}
else
{
this.dekPropertiesTimeToLive = TimeSpan.FromMinutes(30);
}
}
public async Task<DataEncryptionKeyProperties> GetOrAddByRidSelfLinkAsync(
string dekRidSelfLink,
string databaseId,
Func<string, CosmosDiagnosticsContext, CancellationToken, Task<DataEncryptionKeyProperties>> fetcher,
Uri dekNameLinkUri,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
CachedDekProperties cachedDekProperties = await this.DekPropertiesByRidSelfLinkCache.GetAsync(
dekRidSelfLink,
null,
() => this.FetchAsync(fetcher, dekRidSelfLink, databaseId, diagnosticsContext, cancellationToken),
cancellationToken);
if (cachedDekProperties.ServerPropertiesExpiryUtc <= DateTime.UtcNow)
{
cachedDekProperties = await this.DekPropertiesByRidSelfLinkCache.GetAsync(
dekRidSelfLink,
obsoleteValue: null,
() => this.FetchAsync(fetcher, dekRidSelfLink, databaseId, diagnosticsContext, cancellationToken),
cancellationToken,
forceRefresh: true);
}
this.DekPropertiesByNameLinkUriCache.Set(dekNameLinkUri, cachedDekProperties);
return cachedDekProperties.ServerProperties;
}
public async Task<DataEncryptionKeyProperties> GetOrAddByNameLinkUriAsync(
Uri dekNameLinkUri,
string databaseId,
Func<CosmosDiagnosticsContext, CancellationToken, Task<DataEncryptionKeyProperties>> fetcher,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
CachedDekProperties cachedDekProperties = await this.DekPropertiesByNameLinkUriCache.GetAsync(
dekNameLinkUri,
null,
() => this.FetchAsync(fetcher, databaseId, diagnosticsContext, cancellationToken),
cancellationToken);
if (cachedDekProperties.ServerPropertiesExpiryUtc <= DateTime.UtcNow)
{
cachedDekProperties = await this.DekPropertiesByNameLinkUriCache.GetAsync(
dekNameLinkUri,
null,
() => this.FetchAsync(fetcher, databaseId, diagnosticsContext, cancellationToken),
cancellationToken,
forceRefresh: true);
}
this.DekPropertiesByRidSelfLinkCache.Set(cachedDekProperties.ServerProperties.SelfLink, cachedDekProperties);
return cachedDekProperties.ServerProperties;
}
public async Task<InMemoryRawDek> GetOrAddRawDekAsync(
DataEncryptionKeyProperties dekProperties,
Func<DataEncryptionKeyProperties, CosmosDiagnosticsContext, CancellationToken, Task<InMemoryRawDek>> unwrapper,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
InMemoryRawDek inMemoryRawDek = await this.RawDekByRidSelfLinkCache.GetAsync(
dekProperties.SelfLink,
null,
() => unwrapper(dekProperties, diagnosticsContext, cancellationToken),
cancellationToken);
if (inMemoryRawDek.RawDekExpiry <= DateTime.UtcNow)
{
inMemoryRawDek = await this.RawDekByRidSelfLinkCache.GetAsync(
dekProperties.SelfLink,
null,
() => unwrapper(dekProperties, diagnosticsContext, cancellationToken),
cancellationToken,
forceRefresh: true);
}
return inMemoryRawDek;
}
public void Set(string databaseId, Uri dekNameLinkUri, DataEncryptionKeyProperties dekProperties)
{
CachedDekProperties cachedDekProperties = new CachedDekProperties(databaseId, dekProperties, DateTime.UtcNow + this.dekPropertiesTimeToLive);
this.DekPropertiesByNameLinkUriCache.Set(dekNameLinkUri, cachedDekProperties);
this.DekPropertiesByRidSelfLinkCache.Set(dekProperties.SelfLink, cachedDekProperties);
}
public void SetRawDek(string dekRidSelfLink, InMemoryRawDek inMemoryRawDek)
{
this.RawDekByRidSelfLinkCache.Set(dekRidSelfLink, inMemoryRawDek);
}
public async Task RemoveAsync(Uri linkUri)
{
CachedDekProperties cachedDekProperties = await this.DekPropertiesByNameLinkUriCache.RemoveAsync(linkUri);
if (cachedDekProperties != null)
{
this.DekPropertiesByRidSelfLinkCache.Remove(cachedDekProperties.ServerProperties.SelfLink);
this.RawDekByRidSelfLinkCache.Remove(cachedDekProperties.ServerProperties.SelfLink);
}
}
private async Task<CachedDekProperties> FetchAsync(
Func<string, CosmosDiagnosticsContext, CancellationToken, Task<DataEncryptionKeyProperties>> fetcher,
string dekRidSelfLink,
string databaseId,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
DataEncryptionKeyProperties serverProperties = await fetcher(dekRidSelfLink, diagnosticsContext, cancellationToken);
return new CachedDekProperties(databaseId, serverProperties, DateTime.UtcNow + this.dekPropertiesTimeToLive);
}
private async Task<CachedDekProperties> FetchAsync(
Func<CosmosDiagnosticsContext, CancellationToken, Task<DataEncryptionKeyProperties>> fetcher,
string databaseId,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
DataEncryptionKeyProperties serverProperties = await fetcher(diagnosticsContext, cancellationToken);
return new CachedDekProperties(databaseId, serverProperties, DateTime.UtcNow + this.dekPropertiesTimeToLive);
}
}
}

Просмотреть файл

@ -1,28 +0,0 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
/// <summary>
/// Abstract base class for all encryption algorithms.
/// </summary>
internal abstract class EncryptionAlgorithm
{
internal abstract string AlgorithmName { get; }
/// <summary>
/// Encrypts the plainText with a data encryption key.
/// </summary>
/// <param name="plainText">Plain text value to be encrypted.</param>
/// <returns>Encrypted value.</returns>
internal abstract byte[] EncryptData(byte[] plainText);
/// <summary>
/// Decrypts the cipherText with a data encryption key.
/// </summary>
/// <param name="cipherText">Ciphertext value to be decrypted.</param>
/// <returns>Plain text.</returns>
internal abstract byte[] DecryptData(byte[] cipherText);
}
}

Просмотреть файл

@ -17,11 +17,18 @@ namespace Microsoft.Azure.Cosmos
class EncryptionOptions
{
/// <summary>
/// Reference to encryption key to be used for encryption of data in the request payload.
/// The key must already be created using Database.CreateDataEncryptionKeyAsync
/// before using it in encryption options.
/// Identifier of the data encryption key to be used for encrypting the data in the request payload.
/// The data encryption key must be suitable for use with the <see cref="EncryptionAlgorithm"/> provided.
/// </summary>
public DataEncryptionKey DataEncryptionKey { get; set; }
/// <remarks>
/// The <see cref="Encryptor"/> configured on the client is used to retrieve the actual data encryption key.
/// </remarks>
public string DataEncryptionKeyId { get; set; }
/// <summary>
/// Algorithm to be used for encrypting the data in the request payload.
/// </summary>
public string EncryptionAlgorithm { get; set; }
/// <summary>
/// For the request payload, list of JSON paths to encrypt.

Просмотреть файл

@ -25,16 +25,29 @@ namespace Microsoft.Azure.Cosmos
public async Task<Stream> EncryptAsync(
Stream input,
EncryptionOptions encryptionOptions,
DatabaseCore database,
EncryptionKeyWrapProvider encryptionKeyWrapProvider,
Encryptor encryptor,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
Debug.Assert(input != null);
Debug.Assert(encryptionOptions != null);
Debug.Assert(database != null);
Debug.Assert(diagnosticsContext != null);
if (encryptor == null)
{
throw new ArgumentException(ClientResources.EncryptorNotConfigured);
}
if (string.IsNullOrEmpty(encryptionOptions.DataEncryptionKeyId))
{
throw new ArgumentNullException(nameof(encryptionOptions.DataEncryptionKeyId));
}
if (string.IsNullOrEmpty(encryptionOptions.EncryptionAlgorithm))
{
throw new ArgumentNullException(nameof(encryptionOptions.EncryptionAlgorithm));
}
if (encryptionOptions.PathsToEncrypt == null)
{
throw new ArgumentNullException(nameof(encryptionOptions.PathsToEncrypt));
@ -53,23 +66,6 @@ namespace Microsoft.Azure.Cosmos
}
}
if (encryptionOptions.DataEncryptionKey == null)
{
throw new ArgumentException("Invalid encryption options", nameof(encryptionOptions.DataEncryptionKey));
}
if (encryptionKeyWrapProvider == null)
{
throw new ArgumentException(ClientResources.EncryptionKeyWrapProviderNotConfigured);
}
DataEncryptionKey dek = database.GetDataEncryptionKey(encryptionOptions.DataEncryptionKey.Id);
DataEncryptionKeyCore dekCore = (DataEncryptionKeyInlineCore)dek;
(DataEncryptionKeyProperties dekProperties, InMemoryRawDek inMemoryRawDek) = await dekCore.FetchUnwrappedAsync(
diagnosticsContext,
cancellationToken);
JObject itemJObj = EncryptionProcessor.baseSerializer.FromStream<JObject>(input);
JObject toEncryptJObj = new JObject();
@ -92,11 +88,22 @@ namespace Microsoft.Azure.Cosmos
Debug.Assert(memoryStream.TryGetBuffer(out _));
byte[] plainText = memoryStream.GetBuffer();
byte[] cipherText = await encryptor.EncryptAsync(
plainText,
encryptionOptions.DataEncryptionKeyId,
encryptionOptions.EncryptionAlgorithm,
cancellationToken);
if (cipherText == null)
{
throw new InvalidOperationException($"{nameof(Encryptor)} returned null cipherText from {nameof(EncryptAsync)}.");
}
EncryptionProperties encryptionProperties = new EncryptionProperties(
dataEncryptionKeyRid: dekProperties.ResourceId,
encryptionFormatVersion: 1,
encryptedData: inMemoryRawDek.AlgorithmUsingRawDek.EncryptData(plainText));
encryptionFormatVersion: 2,
dataEncryptionKeyId: encryptionOptions.DataEncryptionKeyId,
encryptionAlgorithm: encryptionOptions.EncryptionAlgorithm,
encryptedData: cipherText);
itemJObj.Add(Constants.Properties.EncryptedInfo, JObject.FromObject(encryptionProperties));
return EncryptionProcessor.baseSerializer.ToStream(itemJObj);
@ -104,21 +111,15 @@ namespace Microsoft.Azure.Cosmos
public async Task<Stream> DecryptAsync(
Stream input,
DatabaseCore database,
EncryptionKeyWrapProvider encryptionKeyWrapProvider,
Encryptor encryptor,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
Debug.Assert(input != null);
Debug.Assert(database != null);
Debug.Assert(input.CanSeek);
Debug.Assert(encryptor != null);
Debug.Assert(diagnosticsContext != null);
if (encryptionKeyWrapProvider == null)
{
return input;
}
JObject itemJObj;
using (StreamReader sr = new StreamReader(input, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true))
{
@ -145,7 +146,7 @@ namespace Microsoft.Azure.Cosmos
JObject plainTextJObj = await this.DecryptContentAsync(
encryptionProperties,
database,
encryptor,
diagnosticsContext,
cancellationToken);
@ -160,20 +161,14 @@ namespace Microsoft.Azure.Cosmos
public async Task<CosmosObject> DecryptAsync(
CosmosObject document,
DatabaseCore database,
EncryptionKeyWrapProvider encryptionKeyWrapProvider,
Encryptor encryptor,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
Debug.Assert(document != null);
Debug.Assert(database != null);
Debug.Assert(encryptor != null);
Debug.Assert(diagnosticsContext != null);
if (encryptionKeyWrapProvider == null)
{
return null;
}
if (!document.TryGetValue(Constants.Properties.EncryptedInfo, out CosmosElement encryptedInfo))
{
return document;
@ -183,7 +178,7 @@ namespace Microsoft.Azure.Cosmos
JObject plainTextJObj = await this.DecryptContentAsync(
encryptionProperties,
database,
encryptor,
diagnosticsContext,
cancellationToken);
@ -200,24 +195,27 @@ namespace Microsoft.Azure.Cosmos
private async Task<JObject> DecryptContentAsync(
EncryptionProperties encryptionProperties,
DatabaseCore database,
Encryptor encryptor,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
if (encryptionProperties.EncryptionFormatVersion != 1)
if (encryptionProperties.EncryptionFormatVersion != 2)
{
throw CosmosExceptionFactory.CreateInternalServerErrorException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version.");
}
DataEncryptionKeyCore tempDek = (DataEncryptionKeyInlineCore)database.GetDataEncryptionKey(id: "unknown");
(DataEncryptionKeyProperties _, InMemoryRawDek inMemoryRawDek) = await tempDek.FetchUnwrappedByRidAsync(
encryptionProperties.DataEncryptionKeyRid,
diagnosticsContext,
byte[] plainText = await encryptor.DecryptAsync(
encryptionProperties.EncryptedData,
encryptionProperties.DataEncryptionKeyId,
encryptionProperties.EncryptionAlgorithm,
cancellationToken);
byte[] plainText = inMemoryRawDek.AlgorithmUsingRawDek.DecryptData(encryptionProperties.EncryptedData);
if (plainText == null)
{
throw new InvalidOperationException($"{nameof(Encryptor)} returned null plainText from {nameof(DecryptAsync)}.");
}
JObject plainTextJObj = null;
JObject plainTextJObj;
using (MemoryStream memoryStream = new MemoryStream(plainText))
using (StreamReader streamReader = new StreamReader(memoryStream))
using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))

Просмотреть файл

@ -12,19 +12,24 @@ namespace Microsoft.Azure.Cosmos
[JsonProperty(PropertyName = Constants.Properties.EncryptionFormatVersion)]
public int EncryptionFormatVersion { get; }
[JsonProperty(PropertyName = Constants.Properties.DataEncryptionKeyRid)]
public string DataEncryptionKeyRid { get; }
[JsonProperty(PropertyName = "_en")]
public string DataEncryptionKeyId { get; }
[JsonProperty(PropertyName = "_ea")]
public string EncryptionAlgorithm { get; }
[JsonProperty(PropertyName = Constants.Properties.EncryptedData)]
public byte[] EncryptedData { get; }
public EncryptionProperties(
int encryptionFormatVersion,
string dataEncryptionKeyRid,
string encryptionAlgorithm,
string dataEncryptionKeyId,
byte[] encryptedData)
{
this.EncryptionFormatVersion = encryptionFormatVersion;
this.DataEncryptionKeyRid = dataEncryptionKeyRid;
this.EncryptionAlgorithm = encryptionAlgorithm;
this.DataEncryptionKeyId = dataEncryptionKeyId;
this.EncryptedData = encryptedData;
}
}

Просмотреть файл

@ -0,0 +1,50 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Abstraction for performing client-side encryption.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
abstract class Encryptor
{
/// <summary>
/// Encrypts the plainText using the key and algorithm provided.
/// </summary>
/// <param name="plainText"></param>
/// <param name="dataEncryptionKeyId">Identifier of the data encryption key.</param>
/// <param name="encryptionAlgorithm">Identifier for the encryption algorithm.</param>
/// <param name="cancellationToken">Token for cancellation.</param>
/// <returns>Cipher text.</returns>
public abstract Task<byte[]> EncryptAsync(
byte[] plainText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default);
/// <summary>
/// Decrypts the cipherText using the key and algorithm provided.
/// </summary>
/// <param name="cipherText">Ciphertext to be decrypted.</param>
/// <param name="dataEncryptionKeyId">Identifier of the data encryption key.</param>
/// <param name="encryptionAlgorithm">Identifier for the encryption algorithm.</param>
/// <param name="cancellationToken">Token for cancellation.</param>
/// <returns>Plain text.</returns>
public abstract Task<byte[]> DecryptAsync(
byte[] cipherText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default);
}
}

Просмотреть файл

@ -392,19 +392,19 @@ namespace Microsoft.Azure.Cosmos.Fluent
}
/// <summary>
/// Provider to wrap/unwrap data encryption keys for client side encryption.
/// Provider that allows encrypting and decrypting data.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
/// <param name="encryptionKeyWrapProvider">Provider to wrap/unwrap data encryption keys.</param>
/// <param name="encryptor">Provider that allows encrypting and decrypting data.</param>
/// <returns>The <see cref="CosmosClientBuilder"/> object</returns>
#if PREVIEW
public
#else
internal
#endif
CosmosClientBuilder WithEncryptionKeyWrapProvider(EncryptionKeyWrapProvider encryptionKeyWrapProvider)
CosmosClientBuilder WithEncryptor(Encryptor encryptor)
{
this.clientOptions.EncryptionKeyWrapProvider = encryptionKeyWrapProvider;
this.clientOptions.Encryptor = encryptor;
return this;
}

Просмотреть файл

@ -157,7 +157,7 @@ namespace Microsoft.Azure.Cosmos
queryPageDiagnostics);
if (queryResponseCore.IsSuccess &&
this.clientContext.ClientOptions.EncryptionKeyWrapProvider != null)
this.clientContext.ClientOptions.Encryptor != null)
{
return await this.GetDecryptedElementResponseAsync(queryResponseCore, message, cancellationToken);
}
@ -183,8 +183,7 @@ namespace Microsoft.Azure.Cosmos
CosmosObject decryptedDocument = await this.clientContext.EncryptionProcessor.DecryptAsync(
documentObject,
(DatabaseCore)this.cosmosContainerCore.Database,
this.clientContext.ClientOptions.EncryptionKeyWrapProvider,
this.clientContext.ClientOptions.Encryptor,
message.DiagnosticsContext,
cancellationToken);

Просмотреть файл

@ -28,7 +28,6 @@ namespace Microsoft.Azure.Cosmos
private readonly CosmosClientOptions clientOptions;
private readonly string userAgent;
private readonly EncryptionProcessor encryptionProcessor;
private readonly DekCache dekCache;
private bool isDisposed = false;
private ClientContextCore(
@ -40,7 +39,6 @@ namespace Microsoft.Azure.Cosmos
DocumentClient documentClient,
string userAgent,
EncryptionProcessor encryptionProcessor,
DekCache dekCache,
BatchAsyncContainerExecutorCache batchExecutorCache)
{
this.client = client;
@ -51,7 +49,6 @@ namespace Microsoft.Azure.Cosmos
this.documentClient = documentClient;
this.userAgent = userAgent;
this.encryptionProcessor = encryptionProcessor;
this.dekCache = dekCache;
this.batchExecutorCache = batchExecutorCache;
}
@ -129,7 +126,6 @@ namespace Microsoft.Azure.Cosmos
documentClient: documentClient,
userAgent: documentClient.ConnectionPolicy.UserAgentContainer.UserAgent,
encryptionProcessor: new EncryptionProcessor(),
dekCache: new DekCache(),
batchExecutorCache: new BatchAsyncContainerExecutorCache());
}
@ -152,8 +148,6 @@ namespace Microsoft.Azure.Cosmos
internal override EncryptionProcessor EncryptionProcessor => this.ThrowIfDisposed(this.encryptionProcessor);
internal override DekCache DekCache => this.ThrowIfDisposed(this.dekCache);
/// <summary>
/// Generates the URI link for the resource
/// </summary>
@ -345,8 +339,7 @@ namespace Microsoft.Azure.Cosmos
return await this.EncryptionProcessor.EncryptAsync(
input,
encryptionOptions,
database,
this.ClientOptions.EncryptionKeyWrapProvider,
this.ClientOptions.Encryptor,
diagnosticsContext,
cancellationToken);
}
@ -358,7 +351,7 @@ namespace Microsoft.Azure.Cosmos
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
if (input == null || this.ClientOptions.EncryptionKeyWrapProvider == null)
if (input == null || this.ClientOptions.Encryptor == null)
{
return input;
}
@ -370,8 +363,7 @@ namespace Microsoft.Azure.Cosmos
{
return await this.EncryptionProcessor.DecryptAsync(
input,
database,
this.ClientOptions.EncryptionKeyWrapProvider,
this.ClientOptions.Encryptor,
diagnosticsContext,
cancellationToken);
}

Просмотреть файл

@ -778,7 +778,7 @@ namespace Microsoft.Azure.Cosmos
diagnosticsContext: diagnosticsContext,
cancellationToken: cancellationToken);
if (responseMessage.Content != null && this.ClientContext.ClientOptions.EncryptionKeyWrapProvider != null)
if (responseMessage.Content != null && this.ClientContext.ClientOptions.Encryptor != null)
{
responseMessage.Content = await this.ClientContext.DecryptItemAsync(
responseMessage.Content,

Просмотреть файл

@ -39,8 +39,6 @@ namespace Microsoft.Azure.Cosmos
internal abstract EncryptionProcessor EncryptionProcessor { get; }
internal abstract DekCache DekCache { get; }
internal abstract BatchAsyncContainerExecutor GetExecutorForContainer(
ContainerCore container);

Просмотреть файл

@ -149,22 +149,6 @@ namespace Microsoft.Azure.Cosmos
});
}
internal Task<DataEncryptionKeyResponse> CreateDataEncryptionKeyResponseAsync(
DataEncryptionKey dataEncryptionKey,
Task<ResponseMessage> cosmosResponseMessageTask)
{
return this.ProcessMessageAsync(cosmosResponseMessageTask, (cosmosResponseMessage) =>
{
DataEncryptionKeyProperties dekProperties = this.ToObjectInternal<DataEncryptionKeyProperties>(cosmosResponseMessage);
return new DataEncryptionKeyResponse(
cosmosResponseMessage.StatusCode,
cosmosResponseMessage.Headers,
dekProperties,
dataEncryptionKey,
cosmosResponseMessage.Diagnostics);
});
}
internal Task<DatabaseResponse> CreateDatabaseResponseAsync(
Database database,
Task<ResponseMessage> cosmosResponseMessageTask)

Просмотреть файл

@ -1,117 +0,0 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Fluent;
/// <summary>
/// Provides operations for reading, re-wrapping, or deleting a specific data encryption key by Id.
/// See <see cref="Database"/> for operations to create and enumerate data encryption keys.
/// See https://aka.ms/CosmosClientEncryption for more information on client-side encryption support in Azure Cosmos DB.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
abstract class DataEncryptionKey
{
/// <summary>
/// The unique identifier of the data encryption key.
/// </summary>
public abstract string Id { get; }
/// <summary>
/// Reads the properties of a data encryption key from the Azure Cosmos service as an asynchronous operation.
/// </summary>
/// <param name="requestOptions">(Optional) The options for the request.</param>
/// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
/// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing details of the data encryption key that was read.</returns>
/// <exception cref="CosmosException">
/// This exception can encapsulate many different types of errors.
/// To determine the specific error always look at the StatusCode property.
/// Some common codes you may get when reading a data encryption key are:
/// <list type="table">
/// <listheader>
/// <term>StatusCode</term>
/// <description>Reason for exception</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>
/// NotFound - This means the resource or parent resource you tried to read did not exist.
/// </description>
/// </item>
/// <item>
/// <term>429</term>
/// <description>
/// TooManyRequests - This means you have exceeded the number of request units per second.
/// Consult the CosmosException.RetryAfter value to see how long you should wait before retrying this operation.
/// </description>
/// </item>
/// </list>
/// </exception>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// DataEncryptionKey key = this.database.GetDataEncryptionKey("keyId");
/// DataEncryptionKeyProperties keyProperties = await key.ReadAsync();
/// ]]>
/// </code>
/// </example>
public abstract Task<DataEncryptionKeyResponse> ReadAsync(
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Wraps the raw data encryption key (after unwrapping using the old metadata if needed) using the provided
/// metadata with the help of the key wrapping provider in the EncryptionSerializer configured on the client via
/// <see cref="CosmosClientBuilder.WithCustomSerializer"/>, and saves the re-wrapped data encryption key as an asynchronous
/// operation in the Azure Cosmos service.
/// </summary>
/// <param name="newWrapMetadata">The metadata using which the data encryption key needs to now be wrapped.</param>
/// <param name="requestOptions">(Optional) The options for the request.</param>
/// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
/// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing details of the data encryption key that was re-wrapped.</returns>
/// <exception cref="CosmosException">
/// This exception can encapsulate many different types of errors.
/// To determine the specific error always look at the StatusCode property.
/// Some common codes you may get when re-wrapping a data encryption key are:
/// <list type="table">
/// <listheader>
/// <term>StatusCode</term>
/// <description>Reason for exception</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>
/// NotFound - This means the resource or parent resource you tried to replace did not exist.
/// </description>
/// </item>
/// <item>
/// <term>429</term>
/// <description>
/// TooManyRequests - This means you have exceeded the number of request units per second.
/// Consult the CosmosException.RetryAfter value to see how long you should wait before retrying this operation.
/// </description>
/// </item>
/// </list>
/// </exception>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// AzureKeyVaultKeyWrapMetadata v2Metadata = new AzureKeyVaultKeyWrapMetadata("/path/to/my/master/key/v2");
/// await key.RewrapAsync(v2Metadata);
/// ]]>
/// </code>
/// </example>
public abstract Task<DataEncryptionKeyResponse> RewrapAsync(
EncryptionKeyWrapMetadata newWrapMetadata,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken));
}
}

Просмотреть файл

@ -1,337 +0,0 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Resource.CosmosExceptions;
using Microsoft.Azure.Documents;
/// <summary>
/// Provides operations for reading, re-wrapping, or deleting a specific data encryption key by Id.
/// See <see cref="Cosmos.Database"/> for operations to create a data encryption key.
/// </summary>
internal class DataEncryptionKeyCore : DataEncryptionKey
{
/// <summary>
/// Only used for unit testing
/// </summary>
internal DataEncryptionKeyCore()
{
}
internal DataEncryptionKeyCore(
CosmosClientContext clientContext,
DatabaseCore database,
string keyId)
{
this.Id = keyId;
this.ClientContext = clientContext;
this.LinkUri = DataEncryptionKeyCore.CreateLinkUri(clientContext, database, keyId);
this.Database = database;
}
/// <inheritdoc/>
public override string Id { get; }
/// <summary>
/// Returns a reference to a database object that contains this encryption key.
/// </summary>
public Database Database { get; }
internal virtual Uri LinkUri { get; }
internal virtual CosmosClientContext ClientContext { get; }
/// <inheritdoc/>
public override async Task<DataEncryptionKeyResponse> ReadAsync(
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
DataEncryptionKeyResponse response = await this.ReadInternalAsync(requestOptions, diagnosticsContext: null, cancellationToken: cancellationToken);
this.ClientContext.DekCache.Set(this.Database.Id, this.LinkUri, response.Resource);
return response;
}
/// <inheritdoc/>
public override async Task<DataEncryptionKeyResponse> RewrapAsync(
EncryptionKeyWrapMetadata newWrapMetadata,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (newWrapMetadata == null)
{
throw new ArgumentNullException(nameof(newWrapMetadata));
}
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
(DataEncryptionKeyProperties dekProperties, InMemoryRawDek inMemoryRawDek) = await this.FetchUnwrappedAsync(
diagnosticsContext,
cancellationToken);
(byte[] wrappedDek, EncryptionKeyWrapMetadata updatedMetadata, InMemoryRawDek updatedRawDek) = await this.WrapAsync(
inMemoryRawDek.RawDek,
dekProperties.EncryptionAlgorithmId,
newWrapMetadata,
diagnosticsContext,
cancellationToken);
if (requestOptions == null)
{
requestOptions = new RequestOptions();
}
requestOptions.IfMatchEtag = dekProperties.ETag;
DataEncryptionKeyProperties newDekProperties = new DataEncryptionKeyProperties(dekProperties);
newDekProperties.WrappedDataEncryptionKey = wrappedDek;
newDekProperties.EncryptionKeyWrapMetadata = updatedMetadata;
Task<ResponseMessage> responseMessage = this.ProcessStreamAsync(
this.ClientContext.SerializerCore.ToStream(newDekProperties),
OperationType.Replace,
requestOptions,
diagnosticsContext,
cancellationToken);
DataEncryptionKeyResponse response = await this.ClientContext.ResponseFactory.CreateDataEncryptionKeyResponseAsync(this, responseMessage);
Debug.Assert(response.Resource != null);
this.ClientContext.DekCache.Set(this.Database.Id, this.LinkUri, response.Resource);
this.ClientContext.DekCache.SetRawDek(response.Resource.ResourceId, updatedRawDek);
return response;
}
internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCore database, string keyId)
{
return clientContext.CreateLink(
parentLink: database.LinkUri.OriginalString,
uriPathSegment: Paths.ClientEncryptionKeysPathSegment,
id: keyId);
}
internal async Task<(DataEncryptionKeyProperties, InMemoryRawDek)> FetchUnwrappedAsync(
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
DataEncryptionKeyProperties dekProperties = null;
try
{
dekProperties = await this.ClientContext.DekCache.GetOrAddByNameLinkUriAsync(
this.LinkUri,
this.Database.Id,
this.ReadResourceAsync,
diagnosticsContext,
cancellationToken);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
throw CosmosExceptionFactory.CreateNotFoundException(
ClientResources.DataEncryptionKeyNotFound,
diagnosticsContext: diagnosticsContext,
innerException: ex);
}
InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync(
dekProperties,
this.UnwrapAsync,
diagnosticsContext,
cancellationToken);
return (dekProperties, inMemoryRawDek);
}
internal async Task<(DataEncryptionKeyProperties, InMemoryRawDek)> FetchUnwrappedByRidAsync(
string rid,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
string dekRidSelfLink = PathsHelper.GeneratePath(ResourceType.ClientEncryptionKey, rid, isFeed: false);
// Server self links end with / but client generate links don't - match them.
if (!dekRidSelfLink.EndsWith("/"))
{
dekRidSelfLink += "/";
}
DataEncryptionKeyProperties dekProperties = null;
try
{
dekProperties = await this.ClientContext.DekCache.GetOrAddByRidSelfLinkAsync(
dekRidSelfLink,
this.Database.Id,
this.ReadResourceByRidSelfLinkAsync,
this.LinkUri,
diagnosticsContext,
cancellationToken);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
throw CosmosExceptionFactory.CreateNotFoundException(
ClientResources.DataEncryptionKeyNotFound,
diagnosticsContext: diagnosticsContext,
innerException: ex);
}
InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync(
dekProperties,
this.UnwrapAsync,
diagnosticsContext,
cancellationToken);
return (dekProperties, inMemoryRawDek);
}
internal virtual EncryptionAlgorithm GetEncryptionAlgorithm(byte[] rawDek, CosmosEncryptionAlgorithm encryptionAlgorithmId)
{
Debug.Assert(encryptionAlgorithmId == CosmosEncryptionAlgorithm.AE_AES_256_CBC_HMAC_SHA_256_RANDOMIZED, "Unexpected encryption algorithm id");
AeadAes256CbcHmac256EncryptionKey key = new AeadAes256CbcHmac256EncryptionKey(rawDek, AeadAes256CbcHmac256Algorithm.AlgorithmNameConstant);
return new AeadAes256CbcHmac256Algorithm(key, EncryptionType.Randomized, algorithmVersion: 1);
}
internal virtual byte[] GenerateKey(CosmosEncryptionAlgorithm encryptionAlgorithmId)
{
Debug.Assert(encryptionAlgorithmId == CosmosEncryptionAlgorithm.AE_AES_256_CBC_HMAC_SHA_256_RANDOMIZED, "Unexpected encryption algorithm id");
byte[] rawDek = new byte[32];
SecurityUtility.GenerateRandomBytes(rawDek);
return rawDek;
}
internal async Task<(byte[], EncryptionKeyWrapMetadata, InMemoryRawDek)> WrapAsync(
byte[] key,
CosmosEncryptionAlgorithm encryptionAlgorithmId,
EncryptionKeyWrapMetadata metadata,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
EncryptionKeyWrapProvider encryptionKeyWrapProvider = this.ClientContext.ClientOptions.EncryptionKeyWrapProvider;
if (encryptionKeyWrapProvider == null)
{
throw new ArgumentException(ClientResources.EncryptionKeyWrapProviderNotConfigured);
}
EncryptionKeyWrapResult keyWrapResponse;
using (diagnosticsContext.CreateScope("WrapDataEncryptionKey"))
{
keyWrapResponse = await encryptionKeyWrapProvider.WrapKeyAsync(key, metadata, cancellationToken);
}
// Verify
DataEncryptionKeyProperties tempDekProperties = new DataEncryptionKeyProperties(this.Id, encryptionAlgorithmId, keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata);
InMemoryRawDek roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken);
if (!roundTripResponse.RawDek.SequenceEqual(key))
{
throw CosmosExceptionFactory.CreateBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip,
diagnosticsContext: diagnosticsContext);
}
return (keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, roundTripResponse);
}
internal async Task<InMemoryRawDek> UnwrapAsync(
DataEncryptionKeyProperties dekProperties,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
EncryptionKeyWrapProvider encryptionKeyWrapProvider = this.ClientContext.ClientOptions.EncryptionKeyWrapProvider;
if (encryptionKeyWrapProvider == null)
{
throw new ArgumentException(ClientResources.EncryptionKeyWrapProviderNotConfigured);
}
EncryptionKeyUnwrapResult unwrapResult = null;
using (diagnosticsContext.CreateScope("UnwrapDataEncryptionKey"))
{
unwrapResult = await encryptionKeyWrapProvider.UnwrapKeyAsync(
dekProperties.WrappedDataEncryptionKey,
dekProperties.EncryptionKeyWrapMetadata,
cancellationToken);
}
EncryptionAlgorithm encryptionAlgorithm = this.GetEncryptionAlgorithm(unwrapResult.DataEncryptionKey, dekProperties.EncryptionAlgorithmId);
return new InMemoryRawDek(unwrapResult.DataEncryptionKey, encryptionAlgorithm, unwrapResult.ClientCacheTimeToLive);
}
private async Task<DataEncryptionKeyProperties> ReadResourceByRidSelfLinkAsync(
string ridSelfLink,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken = default(CancellationToken))
{
Task<ResponseMessage> responseMessage = this.ClientContext.ProcessResourceOperationStreamAsync(
resourceUri: new Uri(ridSelfLink, UriKind.Relative),
resourceType: ResourceType.ClientEncryptionKey,
operationType: OperationType.Read,
cosmosContainerCore: null,
partitionKey: null,
streamPayload: null,
requestOptions: null,
requestEnricher: null,
diagnosticsContext: diagnosticsContext,
cancellationToken: cancellationToken);
DataEncryptionKeyResponse response = await this.ClientContext.ResponseFactory.CreateDataEncryptionKeyResponseAsync(this, responseMessage);
Debug.Assert(response.Resource != null);
return response;
}
private async Task<DataEncryptionKeyProperties> ReadResourceAsync(
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
using (diagnosticsContext.CreateScope("ReadDataEncryptionKey"))
{
return await this.ReadInternalAsync(
requestOptions: null,
diagnosticsContext: diagnosticsContext,
cancellationToken: cancellationToken);
}
}
private async Task<DataEncryptionKeyResponse> ReadInternalAsync(
RequestOptions requestOptions,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken)
{
Task<ResponseMessage> responseMessage = this.ProcessStreamAsync(
streamPayload: null,
operationType: OperationType.Read,
requestOptions: requestOptions,
diagnosticsContext: diagnosticsContext,
cancellationToken: cancellationToken);
DataEncryptionKeyResponse response = await this.ClientContext.ResponseFactory.CreateDataEncryptionKeyResponseAsync(this, responseMessage);
Debug.Assert(response.Resource != null);
return response;
}
private Task<ResponseMessage> ProcessStreamAsync(
Stream streamPayload,
OperationType operationType,
RequestOptions requestOptions,
CosmosDiagnosticsContext diagnosticsContext,
CancellationToken cancellationToken = default(CancellationToken))
{
return this.ClientContext.ProcessResourceOperationStreamAsync(
resourceUri: this.LinkUri,
resourceType: ResourceType.ClientEncryptionKey,
operationType: operationType,
cosmosContainerCore: null,
partitionKey: null,
streamPayload: streamPayload,
requestOptions: requestOptions,
requestEnricher: null,
diagnosticsContext: diagnosticsContext,
cancellationToken: cancellationToken);
}
}
}

Просмотреть файл

@ -1,59 +0,0 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// This class acts as a wrapper over <see cref="DataEncryptionKeyCore"/> for environments that use SynchronizationContext.
/// </summary>
internal class DataEncryptionKeyInlineCore : DataEncryptionKey
{
private readonly DataEncryptionKeyCore dataEncryptionKey;
internal DataEncryptionKeyInlineCore(DataEncryptionKeyCore dataEncryptionKey)
{
if (dataEncryptionKey == null)
{
throw new ArgumentException(nameof(dataEncryptionKey));
}
this.dataEncryptionKey = dataEncryptionKey;
}
/// <inheritdoc/>
public override string Id => this.dataEncryptionKey.Id;
internal Uri LinkUri => this.dataEncryptionKey.LinkUri;
/// <inheritdoc/>
public override Task<DataEncryptionKeyResponse> ReadAsync(
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return TaskHelper.RunInlineIfNeededAsync(() =>
this.dataEncryptionKey.ReadAsync(requestOptions, cancellationToken));
}
/// <inheritdoc/>
public override Task<DataEncryptionKeyResponse> RewrapAsync(
EncryptionKeyWrapMetadata newWrapMetadata,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (newWrapMetadata == null)
{
throw new ArgumentNullException(nameof(newWrapMetadata));
}
return TaskHelper.RunInlineIfNeededAsync(() =>
this.dataEncryptionKey.RewrapAsync(newWrapMetadata, requestOptions, cancellationToken));
}
public static implicit operator DataEncryptionKeyCore(DataEncryptionKeyInlineCore dekInlineCore) => dekInlineCore.dataEncryptionKey;
}
}

Просмотреть файл

@ -1,78 +0,0 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System.Net;
/// <summary>
/// Response from the Cosmos DB service for a <see cref="DataEncryptionKey"/> related request.
/// </summary>
#if PREVIEW
public
#else
internal
#endif
class DataEncryptionKeyResponse : Response<DataEncryptionKeyProperties>
{
/// <summary>
/// Creates a data encryption key response as a no-op for mock testing.
/// </summary>
protected DataEncryptionKeyResponse()
: base()
{
}
// A non-public constructor to ensure the factory is used to create the object.
// This will prevent memory leaks when handling the HttpResponseMessage.
internal DataEncryptionKeyResponse(
HttpStatusCode httpStatusCode,
Headers headers,
DataEncryptionKeyProperties keyProperties,
DataEncryptionKey key,
CosmosDiagnostics diagnostics)
{
this.StatusCode = httpStatusCode;
this.Headers = headers;
this.Resource = keyProperties;
this.DataEncryptionKey = key;
this.Diagnostics = diagnostics;
}
/// <summary>
/// The reference to the data encryption key that allows additional operations on it.
/// </summary>
public virtual DataEncryptionKey DataEncryptionKey { get; }
/// <inheritdoc/>
public override Headers Headers { get; }
/// <inheritdoc/>
public override DataEncryptionKeyProperties Resource { get; }
/// <inheritdoc/>
public override HttpStatusCode StatusCode { get; }
/// <inheritdoc/>
public override CosmosDiagnostics Diagnostics { get; }
/// <inheritdoc/>
public override double RequestCharge => this.Headers?.RequestCharge ?? 0;
/// <inheritdoc/>
public override string ActivityId => this.Headers?.ActivityId;
/// <inheritdoc/>
public override string ETag => this.Headers?.ETag;
/// <summary>
/// Get the data encryption key implicitly from an encryption key response.
/// </summary>
/// <param name="response">Response from which to get the data encryption key.</param>
public static implicit operator DataEncryptionKey(DataEncryptionKeyResponse response)
{
return response.DataEncryptionKey;
}
}
}

Просмотреть файл

@ -967,105 +967,5 @@ namespace Microsoft.Azure.Cosmos
public abstract ContainerBuilder DefineContainer(
string name,
string partitionKeyPath);
#if PREVIEW
/// <summary>
/// Returns a reference to a data encryption key object.
/// </summary>
/// <param name="id">Unique identifier for the data encryption key.</param>
/// <returns>Data encryption key reference.</returns>
/// <remarks>
/// The reference returned doesn't guarantee existence of the data encryption key.
/// Please ensure it already exists or is created through <see cref="CreateDataEncryptionKeyAsync"/>.
/// </remarks>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// Database db = this.cosmosClient.GetDatabase("myDatabaseId");
/// DataEncryptionKey key = await db.GetDataEncryptionKey("keyId");
/// DataEncryptionKeyProperties keyProperties = await key.ReadAsync();
/// ]]>
/// </code>
/// </example>
public abstract DataEncryptionKey GetDataEncryptionKey(string id);
/// <summary>
/// Returns an iterator that can be iterated to get properties of data encryption keys.
/// </summary>
/// <param name="startId">(Optional) Starting value of the range (inclusive) of ids of data encryption keys for which properties needs to be returned.</param>
/// <param name="endId">(Optional) Ending value of the range (inclusive) of ids of data encryption keys for which properties needs to be returned.</param>
/// <param name="isDescending">Whether the results should be returned sorted in descending order of id.</param>
/// <param name="continuationToken">(Optional) The continuation token in the Azure Cosmos DB service.</param>
/// <param name="requestOptions">(Optional) The options for the request. Set <see cref="QueryRequestOptions.MaxItemCount"/> to restrict the number of results returned.</param>
/// <returns>An iterator over data encryption keys.</returns>
/// <example>
/// This create the type feed iterator for containers with queryDefinition as input.
/// <code language="c#">
/// <![CDATA[
/// FeedIterator<DataEncryptionKeyProperties> resultSet = this.cosmosDatabase.GetDataEncryptionKeyIterator();
/// while (feedIterator.HasMoreResults)
/// {
/// foreach (DataEncryptionKeyProperties properties in await feedIterator.ReadNextAsync())
/// {
/// Console.WriteLine(properties.Id);
/// }
/// }
/// ]]>
/// </code>
/// </example>
/// <remarks>
/// <see cref="DataEncryptionKey.ReadAsync" /> is recommended for single data encryption key look-up.
/// </remarks>
public abstract FeedIterator<DataEncryptionKeyProperties> GetDataEncryptionKeyIterator(
string startId = null,
string endId = null,
bool isDescending = false,
string continuationToken = null,
QueryRequestOptions requestOptions = null);
/// <summary>
/// Generates a data encryption key, wraps it using the key wrap metadata provided
/// with the key wrapping provider in the EncryptionSerializer configured on the client via <see cref="CosmosClientBuilder.WithCustomSerializer"/>,
/// and saves the wrapped data encryption key as an asynchronous operation in the Azure Cosmos service.
/// </summary>
/// <param name="id">Unique identifier for the data encryption key.</param>
/// <param name="encryptionAlgorithm">Encryption algorithm that will be used along with this data encryption key to encrypt/decrypt data.</param>
/// <param name="encryptionKeyWrapMetadata">Metadata used by the configured key wrapping provider in order to wrap the key.</param>
/// <param name="requestOptions">(Optional) The options for the request.</param>
/// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
/// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing the read resource record.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="id"/> is not set.</exception>
/// <exception cref="CosmosException">
/// This exception can encapsulate many different types of errors.
/// To determine the specific error always look at the StatusCode property.
/// Some common codes you may get when creating a data encryption key are:
/// <list type="table">
/// <listheader>
/// <term>StatusCode</term><description>Reason for exception</description>
/// </listheader>
/// <item>
/// <term>400</term><description>BadRequest - This means something was wrong with the request supplied. It is likely that an id was not supplied for the new encryption key.</description>
/// </item>
/// <item>
/// <term>409</term><description>Conflict - This means an <see cref="DataEncryptionKeyProperties"/> with an id matching the id you supplied already existed.</description>
/// </item>
/// </list>
/// </exception>
/// <example>
///
/// <code language="c#">
/// <![CDATA[
/// AzureKeyVaultKeyWrapMetadata wrapMetadata = new AzureKeyVaultKeyWrapMetadata("/path/to/my/akv/secret/v1");
/// await this.cosmosDatabase.CreateDataEncryptionKeyAsync("myKey", wrapMetadata);
/// ]]>
/// </code>
/// </example>
public abstract Task<DataEncryptionKeyResponse> CreateDataEncryptionKeyAsync(
string id,
CosmosEncryptionAlgorithm encryptionAlgorithm,
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default(CancellationToken));
#endif
}
}

Просмотреть файл

@ -614,147 +614,6 @@ namespace Microsoft.Azure.Cosmos
return new ContainerBuilder(this, this.ClientContext, name, partitionKeyPath);
}
#if PREVIEW
public override
#else
internal virtual
#endif
DataEncryptionKey GetDataEncryptionKey(string id)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
return new DataEncryptionKeyInlineCore(
new DataEncryptionKeyCore(
this.ClientContext,
this,
id));
}
#if PREVIEW
public override
#else
internal virtual
#endif
FeedIterator<DataEncryptionKeyProperties> GetDataEncryptionKeyIterator(
string startId = null,
string endId = null,
bool isDescending = false,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
if (!(this.GetDataEncryptionKeyStreamIterator(
startId,
endId,
isDescending,
continuationToken,
requestOptions) is FeedIteratorInternal dekStreamIterator))
{
throw new InvalidOperationException($"Expected FeedIteratorInternal.");
}
return new FeedIteratorCore<DataEncryptionKeyProperties>(
dekStreamIterator,
(responseMessage) =>
{
FeedResponse<DataEncryptionKeyProperties> results = this.ClientContext.ResponseFactory.CreateQueryFeedResponse<DataEncryptionKeyProperties>(responseMessage, ResourceType.ClientEncryptionKey);
foreach (DataEncryptionKeyProperties result in results)
{
Uri dekUri = DataEncryptionKeyCore.CreateLinkUri(this.ClientContext, this, result.Id);
this.ClientContext.DekCache.Set(this.Id, dekUri, result);
}
return results;
});
}
internal FeedIterator GetDataEncryptionKeyStreamIterator(
string startId = null,
string endId = null,
bool isDescending = false,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
if (startId != null || endId != null)
{
if (requestOptions == null)
{
requestOptions = new QueryRequestOptions();
}
requestOptions.StartId = startId;
requestOptions.EndId = endId;
requestOptions.EnumerationDirection = isDescending ? EnumerationDirection.Reverse : EnumerationDirection.Forward;
}
return FeedIteratorCore.CreateForNonPartitionedResource(
clientContext: this.ClientContext,
resourceLink: this.LinkUri,
resourceType: ResourceType.ClientEncryptionKey,
queryDefinition: null,
continuationToken: continuationToken,
options: requestOptions);
}
#if PREVIEW
public override
#else
internal virtual
#endif
async Task<DataEncryptionKeyResponse> CreateDataEncryptionKeyAsync(
string id,
CosmosEncryptionAlgorithm encryptionAlgorithmId,
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
if (encryptionAlgorithmId != CosmosEncryptionAlgorithm.AE_AES_256_CBC_HMAC_SHA_256_RANDOMIZED)
{
throw new ArgumentException(string.Format("Unsupported Encryption Algorithm {0}", encryptionAlgorithmId), nameof(encryptionAlgorithmId));
}
if (encryptionKeyWrapMetadata == null)
{
throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata));
}
this.ClientContext.ValidateResource(id);
DataEncryptionKeyCore newDek = (DataEncryptionKeyInlineCore)this.GetDataEncryptionKey(id);
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
byte[] rawDek = newDek.GenerateKey(encryptionAlgorithmId);
(byte[] wrappedDek, EncryptionKeyWrapMetadata updatedMetadata, InMemoryRawDek inMemoryRawDek) = await newDek.WrapAsync(
rawDek,
encryptionAlgorithmId,
encryptionKeyWrapMetadata,
diagnosticsContext,
cancellationToken);
DataEncryptionKeyProperties dekProperties = new DataEncryptionKeyProperties(id, encryptionAlgorithmId, wrappedDek, updatedMetadata);
Stream streamPayload = this.ClientContext.SerializerCore.ToStream(dekProperties);
Task<ResponseMessage> responseMessage = this.CreateDataEncryptionKeyStreamAsync(
streamPayload,
requestOptions,
cancellationToken);
DataEncryptionKeyResponse dekResponse = await this.ClientContext.ResponseFactory.CreateDataEncryptionKeyResponseAsync(newDek, responseMessage);
Debug.Assert(dekResponse.Resource != null);
this.ClientContext.DekCache.Set(this.Id, newDek.LinkUri, dekResponse.Resource);
this.ClientContext.DekCache.SetRawDek(dekResponse.Resource.SelfLink, inMemoryRawDek);
return dekResponse;
}
internal void ValidateContainerProperties(ContainerProperties containerProperties)
{
containerProperties.ValidateRequiredProperties();

Просмотреть файл

@ -250,46 +250,6 @@ namespace Microsoft.Azure.Cosmos
return TaskHelper.RunInlineIfNeededAsync(() => this.database.UpsertUserAsync(id, requestOptions, cancellationToken));
}
#if PREVIEW
public override
#else
internal
#endif
DataEncryptionKey GetDataEncryptionKey(string id)
{
return this.database.GetDataEncryptionKey(id);
}
#if PREVIEW
public override
#else
internal
#endif
FeedIterator<DataEncryptionKeyProperties> GetDataEncryptionKeyIterator(
string startId = null,
string endId = null,
bool isDescending = false,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
return this.database.GetDataEncryptionKeyIterator(startId, endId, isDescending, continuationToken, requestOptions);
}
#if PREVIEW
public override
#else
internal
#endif
Task<DataEncryptionKeyResponse> CreateDataEncryptionKeyAsync(
string id,
CosmosEncryptionAlgorithm encryptionAlgorithm,
EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
return TaskHelper.RunInlineIfNeededAsync(() => this.database.CreateDataEncryptionKeyAsync(id, encryptionAlgorithm, encryptionKeyWrapMetadata, requestOptions, cancellationToken));
}
public static implicit operator DatabaseCore(DatabaseInlineCore databaseInlineCore) => databaseInlineCore.database;
}
}

Просмотреть файл

@ -12,7 +12,6 @@ namespace Microsoft.Azure.Cosmos
using Microsoft.Azure.Cosmos.Query.Core.QueryPlan;
using Microsoft.Azure.Cosmos.Scripts;
using Microsoft.Azure.Documents;
using Newtonsoft.Json.Linq;
/// <summary>
/// This is an interface to allow a custom serializer to be used by the CosmosClient
@ -121,7 +120,6 @@ namespace Microsoft.Azure.Cosmos
inputType == typeof(TriggerProperties) ||
inputType == typeof(UserDefinedFunctionProperties) ||
inputType == typeof(UserProperties) ||
inputType == typeof(DataEncryptionKeyProperties) ||
inputType == typeof(ConflictProperties) ||
inputType == typeof(ThroughputProperties) ||
inputType == typeof(OfferV2) ||

Просмотреть файл

@ -20,6 +20,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
using Newtonsoft.Json;
using JsonWriter = Json.JsonWriter;
using JsonReader = Json.JsonReader;
using Microsoft.Azure.Cosmos.Encryption;
[TestClass]
public class EncryptionTests
@ -35,17 +36,29 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
private static DatabaseCore databaseCore;
private static DataEncryptionKeyProperties dekProperties;
private static ContainerCore containerCore;
private static Container container;
private static ContainerCore itemContainerCore;
private static Container itemContainer;
private static Container keyContainer;
private static CosmosDataEncryptionKeyProvider dekProvider;
private static TestEncryptor encryptor;
[ClassInitialize]
public static async Task ClassInitialize(TestContext context)
{
EncryptionTests.client = EncryptionTests.GetClient();
EncryptionTests.dekProvider = new CosmosDataEncryptionKeyProvider(new TestKeyWrapProvider());
EncryptionTests.encryptor = new TestEncryptor(EncryptionTests.dekProvider);
EncryptionTests.client = EncryptionTests.GetClient(EncryptionTests.encryptor);
EncryptionTests.databaseCore = (DatabaseInlineCore)await EncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString());
EncryptionTests.container = await EncryptionTests.databaseCore.CreateContainerAsync(Guid.NewGuid().ToString(), "/PK", 400);
EncryptionTests.containerCore = (ContainerInlineCore)EncryptionTests.container;
EncryptionTests.dekProperties = await CreateDekAsync(EncryptionTests.databaseCore, EncryptionTests.dekId);
EncryptionTests.keyContainer = await EncryptionTests.databaseCore.CreateContainerAsync(Guid.NewGuid().ToString(), "/id", 400);
await EncryptionTests.dekProvider.InitializeAsync(EncryptionTests.databaseCore, EncryptionTests.keyContainer.Id);
EncryptionTests.itemContainer = await EncryptionTests.databaseCore.CreateContainerAsync(Guid.NewGuid().ToString(), "/PK", 400);
EncryptionTests.itemContainerCore = (ContainerInlineCore)EncryptionTests.itemContainer;
EncryptionTests.dekProperties = await EncryptionTests.CreateDekAsync(EncryptionTests.dekProvider, EncryptionTests.dekId);
}
[ClassCleanup]
@ -66,119 +79,80 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
public async Task EncryptionCreateDek()
{
string dekId = "anotherDek";
DataEncryptionKeyProperties dekProperties = await EncryptionTests.CreateDekAsync(EncryptionTests.databaseCore, dekId);
DataEncryptionKeyProperties dekProperties = await EncryptionTests.CreateDekAsync(EncryptionTests.dekProvider, dekId);
Assert.IsNotNull(dekProperties);
Assert.IsNotNull(dekProperties.CreatedTime);
Assert.IsNotNull(dekProperties.LastModified);
Assert.IsNotNull(dekProperties.SelfLink);
Assert.IsNotNull(dekProperties.ResourceId);
// Assert.IsNotNull(dekProperties.ResourceId);
// Assert.AreEqual(dekProperties.LastModified, dekProperties.CreatedTime);
Assert.AreEqual(
new EncryptionKeyWrapMetadata(EncryptionTests.metadata1.Value + EncryptionTests.metadataUpdateSuffix),
dekProperties.EncryptionKeyWrapMetadata);
// Use a different client instance to avoid (unintentional) cache impact
using (CosmosClient client = EncryptionTests.GetClient())
{
DataEncryptionKeyProperties readProperties =
await ((DatabaseCore)(DatabaseInlineCore)client.GetDatabase(EncryptionTests.databaseCore.Id)).GetDataEncryptionKey(dekId).ReadAsync();
Assert.AreEqual(dekProperties, readProperties);
}
// Use different DEK provider to avoid (unintentional) cache impact
CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestKeyWrapProvider());
await dekProvider.InitializeAsync(EncryptionTests.databaseCore, EncryptionTests.keyContainer.Id);
DataEncryptionKeyProperties readProperties = await dekProvider.DataEncryptionKeyContainer.ReadDataEncryptionKeyAsync(dekId);
Assert.AreEqual(dekProperties, readProperties);
}
[TestMethod]
public async Task EncryptionDekReadFeed()
{
DatabaseCore databaseCore = null;
Container newKeyContainer = await EncryptionTests.databaseCore.CreateContainerAsync(Guid.NewGuid().ToString(), "/id", 400);
try
{
databaseCore = (DatabaseInlineCore)await EncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString());
ContainerCore containerCore = (ContainerInlineCore)await EncryptionTests.databaseCore.CreateContainerAsync(Guid.NewGuid().ToString(), "/PK", 400);
CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestKeyWrapProvider());
await dekProvider.InitializeAsync(EncryptionTests.databaseCore, newKeyContainer.Id);
string contosoV1 = "Contoso_v001";
string contosoV2 = "Contoso_v002";
string fabrikamV1 = "Fabrikam_v001";
string fabrikamV2 = "Fabrikam_v002";
await EncryptionTests.CreateDekAsync(databaseCore, contosoV1);
await EncryptionTests.CreateDekAsync(databaseCore, contosoV2);
await EncryptionTests.CreateDekAsync(databaseCore, fabrikamV1);
await EncryptionTests.CreateDekAsync(databaseCore, fabrikamV2);
await EncryptionTests.CreateDekAsync(dekProvider, contosoV1);
await EncryptionTests.CreateDekAsync(dekProvider, contosoV2);
await EncryptionTests.CreateDekAsync(dekProvider, fabrikamV1);
await EncryptionTests.CreateDekAsync(dekProvider, fabrikamV2);
// Test getting all keys
await EncryptionTests.IterateDekFeedAsync(
databaseCore,
new List<string> { contosoV1, contosoV2, fabrikamV1, fabrikamV2 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: false);
// Test getting specific subset of keys
await EncryptionTests.IterateDekFeedAsync(
databaseCore,
new List<string> { contosoV2 },
isExpectedDeksCompleteSetForRequest: false,
isResultOrderExpected: true,
startId: "Contoso_v000",
endId: "Contoso_v999",
isDescending: true,
itemCountInPage: 1);
// Ensure only required results are returned (ascending)
await EncryptionTests.IterateDekFeedAsync(
databaseCore,
new List<string> { contosoV1, contosoV2 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: true,
startId: "Contoso_v000",
endId: "Contoso_v999",
isDescending: false);
// Test startId inclusive and endId inclusive (ascending)
await EncryptionTests.IterateDekFeedAsync(
databaseCore,
new List<string> { contosoV1, contosoV2 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: true,
startId: "Contoso_v001",
endId: "Contoso_v002",
isDescending: false);
// Ensure only required results are returned (descending)
await EncryptionTests.IterateDekFeedAsync(
databaseCore,
new List<string> { contosoV2, contosoV1 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: true,
startId: "Contoso_v000",
endId: "Contoso_v999",
isDescending: true);
// Test startId inclusive and endId inclusive (descending)
await EncryptionTests.IterateDekFeedAsync(
databaseCore,
new List<string> { contosoV2, contosoV1 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: true,
startId: "Contoso_v001",
endId: "Contoso_v002",
isDescending: true);
// Test pagination
await EncryptionTests.IterateDekFeedAsync(
databaseCore,
dekProvider,
new List<string> { contosoV1, contosoV2, fabrikamV1, fabrikamV2 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: false,
"SELECT * from c");
// Test getting specific subset of keys
await EncryptionTests.IterateDekFeedAsync(
dekProvider,
new List<string> { contosoV2 },
isExpectedDeksCompleteSetForRequest: false,
isResultOrderExpected: true,
"SELECT TOP 1 * from c where c.id >= 'Contoso_v000' and c.id <= 'Contoso_v999' ORDER BY c.id DESC");
// Ensure only required results are returned
await EncryptionTests.IterateDekFeedAsync(
dekProvider,
new List<string> { contosoV1, contosoV2 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: true,
"SELECT * from c where c.id >= 'Contoso_v000' and c.id <= 'Contoso_v999' ORDER BY c.id ASC");
// Test pagination
await EncryptionTests.IterateDekFeedAsync(
dekProvider,
new List<string> { contosoV1, contosoV2, fabrikamV1, fabrikamV2 },
isExpectedDeksCompleteSetForRequest: true,
isResultOrderExpected: false,
"SELECT * from c",
itemCountInPage: 3);
}
finally
{
if(databaseCore != null)
{
await databaseCore.DeleteStreamAsync();
}
await newKeyContainer.DeleteContainerStreamAsync();
}
}
@ -186,7 +160,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
public async Task EncryptionCreateItemWithoutEncryptionOptions()
{
TestDoc testDoc = TestDoc.Create();
ItemResponse<TestDoc> createResponse = await EncryptionTests.containerCore.CreateItemAsync(
ItemResponse<TestDoc> createResponse = await EncryptionTests.itemContainerCore.CreateItemAsync(
testDoc,
new PartitionKey(testDoc.PK));
Assert.AreEqual(HttpStatusCode.Created, createResponse.StatusCode);
@ -196,21 +170,21 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
[TestMethod]
public async Task EncryptionCreateItem()
{
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, testDoc);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, testDoc);
await EncryptionTests.VerifyItemByReadStreamAsync(EncryptionTests.containerCore, testDoc);
await EncryptionTests.VerifyItemByReadStreamAsync(EncryptionTests.itemContainerCore, testDoc);
TestDoc expectedDoc = new TestDoc(testDoc);
await EncryptionTests.ValidateQueryResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
"SELECT * FROM c",
expectedDoc);
await EncryptionTests.ValidateQueryResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
string.Format(
"SELECT * FROM c where c.PK = '{0}' and c.id = '{1}' and c.NonSensitive = '{2}'",
expectedDoc.PK,
@ -219,12 +193,12 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
expectedDoc);
await EncryptionTests.ValidateQueryResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
string.Format("SELECT * FROM c where c.Sensitive = '{0}'", testDoc.Sensitive),
expectedDoc: null);
await EncryptionTests.ValidateQueryResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
queryDefinition: new QueryDefinition(
"select * from c where c.id = @theId and c.PK = @thePK")
.WithParameter("@theId", expectedDoc.Id)
@ -234,61 +208,61 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
expectedDoc.Sensitive = null;
await EncryptionTests.ValidateQueryResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
"SELECT c.id, c.PK, c.Sensitive, c.NonSensitive FROM c",
expectedDoc);
await EncryptionTests.ValidateQueryResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
"SELECT c.id, c.PK, c.NonSensitive FROM c",
expectedDoc);
await EncryptionTests.ValidateSprocResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
expectedDoc);
}
[TestMethod]
public async Task DecryptQueryResultMultipleDocsTest()
public async Task EncryptionDecryptQueryResultMultipleDocs()
{
TestDoc testDoc1 = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc2 = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc1 = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc2 = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
await ValidateQueryResultsMultipleDocumentsAsync(EncryptionTests.containerCore, testDoc1, testDoc2);
await ValidateQueryResultsMultipleDocumentsAsync(EncryptionTests.itemContainerCore, testDoc1, testDoc2);
}
[TestMethod]
public async Task DecryptQueryResultDifferentDeksTest()
public async Task EncryptionDecryptQueryResultDifferentDeks()
{
string dekId1 = "mydek1";
EncryptionTests.dekProperties = await CreateDekAsync(EncryptionTests.databaseCore, dekId1);
await EncryptionTests.CreateDekAsync(EncryptionTests.dekProvider, dekId1);
TestDoc testDoc1 = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc2 = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, dekId1, TestDoc.PathsToEncrypt);
TestDoc testDoc1 = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc2 = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, dekId1, TestDoc.PathsToEncrypt);
await ValidateQueryResultsMultipleDocumentsAsync(EncryptionTests.containerCore, testDoc1, testDoc2);
await ValidateQueryResultsMultipleDocumentsAsync(EncryptionTests.itemContainerCore, testDoc1, testDoc2);
}
[TestMethod]
public async Task DecryptQueryResultMultipleEncryptedPropertiesTest()
public async Task EncryptionDecryptQueryResultMultipleEncryptedProperties()
{
TestDoc testDoc = await EncryptionTests.CreateItemAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
EncryptionTests.dekId,
new List<string>(){ "/Sensitive", "/NonSensitive" });
new List<string>() { "/Sensitive", "/NonSensitive" });
TestDoc expectedDoc = new TestDoc(testDoc);
await EncryptionTests.ValidateQueryResultsAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
"SELECT * FROM c",
expectedDoc);
}
[TestMethod]
public async Task DecryptQueryBinaryResponse()
public async Task EncryptionDecryptQueryBinaryResponse()
{
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
CosmosSerializationFormatOptions options = new CosmosSerializationFormatOptions(
Documents.ContentSerializationFormat.CosmosBinary.ToString(),
@ -304,7 +278,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
string query = "SELECT * FROM c";
FeedIterator feedIterator = EncryptionTests.containerCore.GetItemQueryStreamIterator(
FeedIterator feedIterator = EncryptionTests.itemContainerCore.GetItemQueryStreamIterator(
query,
requestOptions: requestOptions);
@ -335,12 +309,12 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
}
[TestMethod]
public async Task DecryptQueryValueResponse()
public async Task EncryptionDecryptQueryValueResponse()
{
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
string query = "SELECT VALUE COUNT(1) FROM c";
FeedIterator feedIterator = EncryptionTests.containerCore.GetItemQueryStreamIterator(query);
FeedIterator feedIterator = EncryptionTests.itemContainerCore.GetItemQueryStreamIterator(query);
while (feedIterator.HasMoreResults)
{
ResponseMessage response = await feedIterator.ReadNextAsync();
@ -353,69 +327,99 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
public async Task EncryptionRudItem()
{
TestDoc testDoc = await EncryptionTests.UpsertItemAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
TestDoc.Create(),
EncryptionTests.dekId,
TestDoc.PathsToEncrypt,
HttpStatusCode.Created);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, testDoc);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, testDoc);
testDoc.NonSensitive = Guid.NewGuid().ToString();
testDoc.Sensitive = Guid.NewGuid().ToString();
ItemResponse<TestDoc> upsertResponse = await EncryptionTests.UpsertItemAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
testDoc,
EncryptionTests.dekId,
TestDoc.PathsToEncrypt,
HttpStatusCode.OK);
TestDoc updatedDoc = upsertResponse.Resource;
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, updatedDoc);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, updatedDoc);
updatedDoc.NonSensitive = Guid.NewGuid().ToString();
updatedDoc.Sensitive = Guid.NewGuid().ToString();
TestDoc replacedDoc = await EncryptionTests.ReplaceItemAsync(
EncryptionTests.containerCore,
EncryptionTests.itemContainerCore,
updatedDoc,
EncryptionTests.dekId,
TestDoc.PathsToEncrypt,
upsertResponse.ETag);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, replacedDoc);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, replacedDoc);
await EncryptionTests.DeleteItemAsync(EncryptionTests.containerCore, replacedDoc);
await EncryptionTests.DeleteItemAsync(EncryptionTests.itemContainerCore, replacedDoc);
}
[TestMethod]
public async Task EncryptionResourceTokenAuth()
public async Task EncryptionResourceTokenAuthRestricted()
{
User user = EncryptionTests.databaseCore.GetUser(Guid.NewGuid().ToString());
await EncryptionTests.databaseCore.CreateUserAsync(user.Id);
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
PermissionProperties permission = await user.CreatePermissionAsync(
new PermissionProperties(Guid.NewGuid().ToString(), PermissionMode.All, EncryptionTests.container));
User restrictedUser = EncryptionTests.databaseCore.GetUser(Guid.NewGuid().ToString());
await EncryptionTests.databaseCore.CreateUserAsync(restrictedUser.Id);
TestDoc testDoc = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
PermissionProperties restrictedUserPermission = await restrictedUser.CreatePermissionAsync(
new PermissionProperties(Guid.NewGuid().ToString(), PermissionMode.All, EncryptionTests.itemContainer));
CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestKeyWrapProvider());
TestEncryptor encryptor = new TestEncryptor(dekProvider);
(string endpoint, string _) = TestCommon.GetAccountInfo();
CosmosClient resourceTokenBasedClient = new CosmosClientBuilder(endpoint, permission.Token)
.WithEncryptionKeyWrapProvider(new TestKeyWrapProvider())
CosmosClient clientForRestrictedUser = new CosmosClientBuilder(endpoint, restrictedUserPermission.Token)
.WithEncryptor(encryptor)
.Build();
DatabaseCore databaseForTokenClient = (DatabaseInlineCore)resourceTokenBasedClient.GetDatabase(EncryptionTests.databaseCore.Id);
Container containerForTokenClient = databaseForTokenClient.GetContainer(EncryptionTests.container.Id);
Database databaseForRestrictedUser = clientForRestrictedUser.GetDatabase(EncryptionTests.databaseCore.Id);
Container containerForRestrictedUser = databaseForRestrictedUser.GetContainer(EncryptionTests.itemContainer.Id);
await EncryptionTests.PerformForbiddenOperationAsync(() =>
databaseForTokenClient.GetDataEncryptionKey(EncryptionTests.dekId).ReadAsync(), "DEK.ReadAsync");
dekProvider.InitializeAsync(databaseForRestrictedUser, EncryptionTests.keyContainer.Id), "CosmosDekProvider.InitializeAsync");
await EncryptionTests.PerformForbiddenOperationAsync(() =>
containerForTokenClient.ReadItemAsync<TestDoc>(testDoc.Id, new PartitionKey(testDoc.PK)), "ReadItemAsync");
await EncryptionTests.PerformOperationOnUninitializedDekProviderAsync(() =>
dekProvider.DataEncryptionKeyContainer.ReadDataEncryptionKeyAsync(EncryptionTests.dekId), "DEK.ReadAsync");
await EncryptionTests.PerformForbiddenOperationAsync(() =>
containerForTokenClient.ReadItemStreamAsync(testDoc.Id, new PartitionKey(testDoc.PK)), "ReadItemStreamAsync");
await EncryptionTests.PerformOperationOnUninitializedDekProviderAsync(() =>
containerForRestrictedUser.ReadItemAsync<TestDoc>(testDoc.Id, new PartitionKey(testDoc.PK)), "ReadItemAsync");
await EncryptionTests.PerformOperationOnUninitializedDekProviderAsync(() =>
containerForRestrictedUser.ReadItemStreamAsync(testDoc.Id, new PartitionKey(testDoc.PK)), "ReadItemStreamAsync");
}
[TestMethod]
public async Task EncryptionResourceTokenAuthAllowed()
{
User keyManagerUser = EncryptionTests.databaseCore.GetUser(Guid.NewGuid().ToString());
await EncryptionTests.databaseCore.CreateUserAsync(keyManagerUser.Id);
PermissionProperties keyManagerUserPermission = await keyManagerUser.CreatePermissionAsync(
new PermissionProperties(Guid.NewGuid().ToString(), PermissionMode.All, EncryptionTests.keyContainer));
CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestKeyWrapProvider());
TestEncryptor encryptor = new TestEncryptor(dekProvider);
(string endpoint, string _) = TestCommon.GetAccountInfo();
CosmosClient clientForKeyManagerUser = new CosmosClientBuilder(endpoint, keyManagerUserPermission.Token)
.WithEncryptor(encryptor)
.Build();
Database databaseForKeyManagerUser = clientForKeyManagerUser.GetDatabase(EncryptionTests.databaseCore.Id);
await dekProvider.InitializeAsync(databaseForKeyManagerUser, EncryptionTests.keyContainer.Id);
DataEncryptionKeyProperties readDekProperties = await dekProvider.DataEncryptionKeyContainer.ReadDataEncryptionKeyAsync(EncryptionTests.dekId);
Assert.AreEqual(EncryptionTests.dekProperties, readDekProperties);
}
[TestMethod]
@ -425,7 +429,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
try
{
await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, new List<string>() { "/id" });
await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, new List<string>() { "/id" });
Assert.Fail("Expected item creation with id specified to be encrypted to fail.");
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.BadRequest)
@ -434,7 +438,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
try
{
await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, new List<string>() { "/PK" });
await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, new List<string>() { "/PK" });
Assert.Fail("Expected item creation with PK specified to be encrypted to fail.");
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.BadRequest)
@ -445,24 +449,24 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
[TestMethod]
public async Task EncryptionBulkCrud()
{
TestDoc docToReplace = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc docToReplace = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
docToReplace.NonSensitive = Guid.NewGuid().ToString();
docToReplace.Sensitive = Guid.NewGuid().ToString();
TestDoc docToUpsert = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc docToUpsert = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
docToUpsert.NonSensitive = Guid.NewGuid().ToString();
docToUpsert.Sensitive = Guid.NewGuid().ToString();
TestDoc docToDelete = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
TestDoc docToDelete = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, EncryptionTests.dekId, TestDoc.PathsToEncrypt);
(string endpoint, string authKey) = TestCommon.GetAccountInfo();
CosmosClient clientWithBulk = new CosmosClientBuilder(endpoint, authKey)
.WithEncryptionKeyWrapProvider(new TestKeyWrapProvider())
.WithEncryptor(EncryptionTests.encryptor)
.WithBulkExecution(true)
.Build();
DatabaseCore databaseWithBulk = (DatabaseInlineCore)clientWithBulk.GetDatabase(EncryptionTests.databaseCore.Id);
ContainerCore containerWithBulk = (ContainerInlineCore)databaseWithBulk.GetContainer(EncryptionTests.container.Id);
ContainerCore containerWithBulk = (ContainerInlineCore)databaseWithBulk.GetContainer(EncryptionTests.itemContainer.Id);
List<Task> tasks = new List<Task>();
tasks.Add(EncryptionTests.CreateItemAsync(containerWithBulk, EncryptionTests.dekId, TestDoc.PathsToEncrypt));
@ -479,53 +483,53 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
string partitionKey = "thePK";
string dek1 = EncryptionTests.dekId;
string dek2 = "dek2Forbatch";
await EncryptionTests.CreateDekAsync(EncryptionTests.databaseCore, dek2);
await EncryptionTests.CreateDekAsync(EncryptionTests.dekProvider, dek2);
TestDoc doc1ToCreate = TestDoc.Create(partitionKey);
TestDoc doc2ToCreate = TestDoc.Create(partitionKey);
TestDoc doc3ToCreate = TestDoc.Create(partitionKey);
ItemResponse<TestDoc> doc1ToReplaceCreateResponse = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, dek1, TestDoc.PathsToEncrypt, partitionKey);
ItemResponse<TestDoc> doc1ToReplaceCreateResponse = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, dek1, TestDoc.PathsToEncrypt, partitionKey);
TestDoc doc1ToReplace = doc1ToReplaceCreateResponse.Resource;
doc1ToReplace.NonSensitive = Guid.NewGuid().ToString();
doc1ToReplace.Sensitive = Guid.NewGuid().ToString();
TestDoc doc2ToReplace = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, dek2, TestDoc.PathsToEncrypt, partitionKey);
TestDoc doc2ToReplace = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, dek2, TestDoc.PathsToEncrypt, partitionKey);
doc2ToReplace.NonSensitive = Guid.NewGuid().ToString();
doc2ToReplace.Sensitive = Guid.NewGuid().ToString();
TestDoc doc1ToUpsert = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, dek2, TestDoc.PathsToEncrypt, partitionKey);
TestDoc doc1ToUpsert = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, dek2, TestDoc.PathsToEncrypt, partitionKey);
doc1ToUpsert.NonSensitive = Guid.NewGuid().ToString();
doc1ToUpsert.Sensitive = Guid.NewGuid().ToString();
TestDoc doc2ToUpsert = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, dek1, TestDoc.PathsToEncrypt, partitionKey);
TestDoc doc2ToUpsert = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, dek1, TestDoc.PathsToEncrypt, partitionKey);
doc2ToUpsert.NonSensitive = Guid.NewGuid().ToString();
doc2ToUpsert.Sensitive = Guid.NewGuid().ToString();
TestDoc docToDelete = await EncryptionTests.CreateItemAsync(EncryptionTests.containerCore, dek1, TestDoc.PathsToEncrypt, partitionKey);
TestDoc docToDelete = await EncryptionTests.CreateItemAsync(EncryptionTests.itemContainerCore, dek1, TestDoc.PathsToEncrypt, partitionKey);
TransactionalBatchResponse batchResponse = await EncryptionTests.container.CreateTransactionalBatch(new Cosmos.PartitionKey(partitionKey))
.CreateItem(doc1ToCreate, EncryptionTests.GetBatchItemRequestOptions(containerCore, dek1, TestDoc.PathsToEncrypt))
.CreateItemStream(doc2ToCreate.ToStream(), EncryptionTests.GetBatchItemRequestOptions(containerCore, dek2, TestDoc.PathsToEncrypt))
.ReplaceItem(doc1ToReplace.Id, doc1ToReplace, EncryptionTests.GetBatchItemRequestOptions(containerCore, dek2, TestDoc.PathsToEncrypt, doc1ToReplaceCreateResponse.ETag))
TransactionalBatchResponse batchResponse = await EncryptionTests.itemContainer.CreateTransactionalBatch(new Cosmos.PartitionKey(partitionKey))
.CreateItem(doc1ToCreate, EncryptionTests.GetBatchItemRequestOptions(EncryptionTests.itemContainerCore, dek1, TestDoc.PathsToEncrypt))
.CreateItemStream(doc2ToCreate.ToStream(), EncryptionTests.GetBatchItemRequestOptions(EncryptionTests.itemContainerCore, dek2, TestDoc.PathsToEncrypt))
.ReplaceItem(doc1ToReplace.Id, doc1ToReplace, EncryptionTests.GetBatchItemRequestOptions(EncryptionTests.itemContainerCore, dek2, TestDoc.PathsToEncrypt, doc1ToReplaceCreateResponse.ETag))
.CreateItem(doc3ToCreate)
.ReplaceItemStream(doc2ToReplace.Id, doc2ToReplace.ToStream(), EncryptionTests.GetBatchItemRequestOptions(containerCore, dek2, TestDoc.PathsToEncrypt))
.UpsertItem(doc1ToUpsert, EncryptionTests.GetBatchItemRequestOptions(containerCore, dek1, TestDoc.PathsToEncrypt))
.ReplaceItemStream(doc2ToReplace.Id, doc2ToReplace.ToStream(), EncryptionTests.GetBatchItemRequestOptions(EncryptionTests.itemContainerCore, dek2, TestDoc.PathsToEncrypt))
.UpsertItem(doc1ToUpsert, EncryptionTests.GetBatchItemRequestOptions(EncryptionTests.itemContainerCore, dek1, TestDoc.PathsToEncrypt))
.DeleteItem(docToDelete.Id)
.UpsertItemStream(doc2ToUpsert.ToStream(), EncryptionTests.GetBatchItemRequestOptions(containerCore, dek2, TestDoc.PathsToEncrypt))
.UpsertItemStream(doc2ToUpsert.ToStream(), EncryptionTests.GetBatchItemRequestOptions(EncryptionTests.itemContainerCore, dek2, TestDoc.PathsToEncrypt))
.ExecuteAsync();
Assert.AreEqual(HttpStatusCode.OK, batchResponse.StatusCode);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, doc1ToCreate);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, doc2ToCreate);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, doc3ToCreate);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, doc1ToReplace);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, doc2ToReplace);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, doc1ToUpsert);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.containerCore, doc2ToUpsert);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, doc1ToCreate);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, doc2ToCreate);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, doc3ToCreate);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, doc1ToReplace);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, doc2ToReplace);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, doc1ToUpsert);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainerCore, doc2ToUpsert);
ResponseMessage readResponseMessage = await container.ReadItemStreamAsync(docToDelete.Id, new PartitionKey(docToDelete.PK));
ResponseMessage readResponseMessage = await EncryptionTests.itemContainer.ReadItemStreamAsync(docToDelete.Id, new PartitionKey(docToDelete.PK));
Assert.AreEqual(HttpStatusCode.NotFound, readResponseMessage.StatusCode);
}
@ -605,36 +609,36 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
}
}
private static CosmosClient GetClient()
private static CosmosClient GetClient(Encryptor encryptor)
{
(string endpoint, string authKey) = TestCommon.GetAccountInfo();
return new CosmosClientBuilder(endpoint, authKey)
.WithEncryptionKeyWrapProvider(new TestKeyWrapProvider())
.WithEncryptor(encryptor)
.Build();
}
private static async Task IterateDekFeedAsync(
DatabaseCore databaseCore,
CosmosDataEncryptionKeyProvider dekProvider,
List<string> expectedDekIds,
bool isExpectedDeksCompleteSetForRequest,
bool isResultOrderExpected,
string startId = null,
string endId = null,
bool isDescending = false,
string query,
int? itemCountInPage = null)
{
int remainingItemCount = expectedDekIds.Count;
QueryRequestOptions options = null;
QueryRequestOptions requestOptions = null;
if (itemCountInPage.HasValue)
{
options = new QueryRequestOptions()
requestOptions = new QueryRequestOptions()
{
MaxItemCount = itemCountInPage
};
}
FeedIterator<DataEncryptionKeyProperties> dekIterator = databaseCore.GetDataEncryptionKeyIterator(
startId, endId, isDescending, requestOptions: options);
FeedIterator<DataEncryptionKeyProperties> dekIterator = dekProvider.DataEncryptionKeyContainer
.GetDataEncryptionKeyQueryIterator<DataEncryptionKeyProperties>(
query,
requestOptions: requestOptions);
Assert.IsTrue(dekIterator.HasMoreResults);
@ -754,7 +758,8 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
{
EncryptionOptions = new EncryptionOptions
{
DataEncryptionKey = ((DatabaseCore)containerCore.Database).GetDataEncryptionKey(dekId),
DataEncryptionKeyId = dekId,
EncryptionAlgorithm = CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized,
PathsToEncrypt = pathsToEncrypt
},
IfMatchEtag = ifMatchEtag
@ -771,7 +776,8 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
{
EncryptionOptions = new EncryptionOptions
{
DataEncryptionKey = ((DatabaseCore)containerCore.Database).GetDataEncryptionKey(dekId),
DataEncryptionKeyId = dekId,
EncryptionAlgorithm = CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized,
PathsToEncrypt = pathsToEncrypt
},
IfMatchEtag = ifMatchEtag
@ -794,11 +800,11 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
Assert.AreEqual(testDoc, readResponse.Resource);
}
private static async Task<DataEncryptionKeyProperties> CreateDekAsync(DatabaseCore databaseCore, string dekId)
private static async Task<DataEncryptionKeyProperties> CreateDekAsync(CosmosDataEncryptionKeyProvider dekProvider, string dekId)
{
DataEncryptionKeyResponse dekResponse = await databaseCore.CreateDataEncryptionKeyAsync(
ItemResponse<DataEncryptionKeyProperties> dekResponse = await dekProvider.DataEncryptionKeyContainer.CreateDataEncryptionKeyAsync(
dekId,
CosmosEncryptionAlgorithm.AE_AES_256_CBC_HMAC_SHA_256_RANDOMIZED,
CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized,
EncryptionTests.metadata1);
Assert.AreEqual(HttpStatusCode.Created, dekResponse.StatusCode);
@ -811,7 +817,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
return dekProperties;
}
private static async Task PerformForbiddenOperationAsync<T>(Func<Task<T>> func, string operationName)
private static async Task PerformForbiddenOperationAsync(Func<Task> func, string operationName)
{
try
{
@ -823,6 +829,19 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
}
}
private static async Task PerformOperationOnUninitializedDekProviderAsync(Func<Task> func, string operationName)
{
try
{
await func();
Assert.Fail($"Expected {operationName} to not work on uninitialized CosmosDataEncryptionKeyProvider.");
}
catch (InvalidOperationException ex)
{
Assert.IsTrue(ex.Message.Contains("The CosmosDataEncryptionKeyProvider was not initialized."));
}
}
public class TestDoc
{
public static List<string> PathsToEncrypt { get; } = new List<string>() { "/Sensitive" };
@ -899,5 +918,50 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
return Task.FromResult(new EncryptionKeyWrapResult(key.Select(b => (byte)(b + moveBy)).ToArray(), responseMetadata));
}
}
// This class is same as CosmosEncryptor but copied since the emulator tests don't
// have internal visibility into Cosmos.Encryption assembly.
private class TestEncryptor : Encryptor
{
public DataEncryptionKeyProvider DataEncryptionKeyProvider { get; }
public TestEncryptor(DataEncryptionKeyProvider dataEncryptionKeyProvider)
{
this.DataEncryptionKeyProvider = dataEncryptionKeyProvider;
}
public override async Task<byte[]> DecryptAsync(
byte[] cipherText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default)
{
DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync(
dataEncryptionKeyId,
encryptionAlgorithm,
cancellationToken);
if (dek == null)
{
throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync)}.");
}
return dek.DecryptData(cipherText);
}
public override async Task<byte[]> EncryptAsync(
byte[] plainText,
string dataEncryptionKeyId,
string encryptionAlgorithm,
CancellationToken cancellationToken = default)
{
DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync(
dataEncryptionKeyId,
encryptionAlgorithm,
cancellationToken);
return dek.EncryptData(plainText);
}
}
}
}

Просмотреть файл

@ -222,6 +222,7 @@
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Microsoft.Azure.Cosmos.Encryption\src\Microsoft.Azure.Cosmos.Encryption.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Azure.Cosmos.csproj" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
namespace Microsoft.Azure.Cosmos.Encryption.DataEncryptionKeyProvider.Tests
{
using System;
using System.Collections.Concurrent;
@ -10,7 +10,6 @@ namespace Microsoft.Azure.Cosmos
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Tests;
@ -25,190 +24,39 @@ namespace Microsoft.Azure.Cosmos
{
private const string DatabaseId = "mockDatabase";
private const string ContainerId = "mockContainer";
private const string DekId = "mockDek";
private const string Algo = "testAlgo";
private const double requestCharge = 0.6;
private const CosmosEncryptionAlgorithm Algo = CosmosEncryptionAlgorithm.AE_AES_256_CBC_HMAC_SHA_256_RANDOMIZED;
private TimeSpan cacheTTL = TimeSpan.FromDays(1);
private byte[] dek = new byte[] { 1, 2, 3, 4 };
private EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata("metadata1");
private EncryptionKeyWrapMetadata metadata2 = new EncryptionKeyWrapMetadata("metadata2");
private string metadataUpdateSuffix = "updated";
private Mock<Encryptor> mockEncryptor;
private EncryptionTestHandler testHandler;
private Mock<EncryptionKeyWrapProvider> mockKeyWrapProvider;
private Mock<EncryptionAlgorithm> mockEncryptionAlgorithm;
private Mock<DatabaseCore> mockDatabaseCore;
[TestMethod]
public async Task EncryptionUTCreateDekWithoutEncryptionSerializer()
{
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)this.GetContainer()).Database;
try
{
await database.CreateDataEncryptionKeyAsync("mydek", EncryptionUnitTests.Algo, this.metadata1);
Assert.Fail();
}
catch (ArgumentException ex)
{
Assert.AreEqual(ClientResources.EncryptionKeyWrapProviderNotConfigured, ex.Message);
}
}
[TestMethod]
public async Task EncryptionUTRewrapDekWithoutEncryptionSerializer()
{
string dekId = "mydek";
EncryptionTestHandler testHandler = new EncryptionTestHandler();
// Create a DEK using a properly setup client first
Container container = this.GetContainerWithMockSetup(testHandler);
DatabaseCore databaseWithSerializer = (DatabaseCore)((ContainerCore)(ContainerInlineCore)container).Database;
DataEncryptionKeyResponse dekResponse = await databaseWithSerializer.CreateDataEncryptionKeyAsync(dekId, EncryptionUnitTests.Algo, this.metadata1);
Assert.AreEqual(HttpStatusCode.Created, dekResponse.StatusCode);
// Clear the handler pipeline that would have got setup
testHandler.InnerHandler = null;
// Ensure rewrap for this key fails on improperly configured client
try
{
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)this.GetContainer(testHandler)).Database;
DataEncryptionKey dek = database.GetDataEncryptionKey(dekId);
await dek.RewrapAsync(this.metadata2);
Assert.Fail();
}
catch (ArgumentException ex)
{
Assert.AreEqual(ClientResources.EncryptionKeyWrapProviderNotConfigured, ex.Message);
}
}
[TestMethod]
public async Task EncryptionUTCreateDek()
{
Container container = this.GetContainerWithMockSetup();
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)container).Database;
string dekId = "mydek";
DataEncryptionKeyResponse dekResponse = await database.CreateDataEncryptionKeyAsync(dekId, EncryptionUnitTests.Algo, this.metadata1);
Assert.AreEqual(HttpStatusCode.Created, dekResponse.StatusCode);
Assert.AreEqual(requestCharge, dekResponse.RequestCharge);
Assert.IsNotNull(dekResponse.ETag);
DataEncryptionKeyProperties dekProperties = dekResponse.Resource;
Assert.IsNotNull(dekProperties);
Assert.AreEqual(dekResponse.ETag, dekProperties.ETag);
Assert.AreEqual(dekId, dekProperties.Id);
Assert.AreEqual(1, this.testHandler.Received.Count);
RequestMessage createDekRequestMessage = this.testHandler.Received[0];
Assert.AreEqual(ResourceType.ClientEncryptionKey, createDekRequestMessage.ResourceType);
Assert.AreEqual(OperationType.Create, createDekRequestMessage.OperationType);
Assert.IsTrue(this.testHandler.Deks.ContainsKey(dekId));
DataEncryptionKeyProperties serverDekProperties = this.testHandler.Deks[dekId];
Assert.IsTrue(serverDekProperties.Equals(dekProperties));
// Make sure we didn't push anything else in the JSON (such as raw DEK) by comparing JSON properties
// to properties exposed in DataEncryptionKeyProperties.
createDekRequestMessage.Content.Position = 0; // it is a test assumption that the client uses MemoryStream
JObject jObj = JObject.Parse(await new StreamReader(createDekRequestMessage.Content).ReadToEndAsync());
IEnumerable<string> dekPropertiesPropertyNames = GetJsonPropertyNamesForType(typeof(DataEncryptionKeyProperties));
foreach (JProperty property in jObj.Properties())
{
Assert.IsTrue(dekPropertiesPropertyNames.Contains(property.Name));
}
// Key wrap metadata should be the only "object" child in the JSON (given current properties in DataEncryptionKeyProperties)
IEnumerable<JToken> objectChildren = jObj.PropertyValues().Where(v => v.Type == JTokenType.Object);
Assert.AreEqual(1, objectChildren.Count());
JObject keyWrapMetadataJObj = (JObject)objectChildren.First();
Assert.AreEqual(Constants.Properties.KeyWrapMetadata, ((JProperty)keyWrapMetadataJObj.Parent).Name);
IEnumerable<string> keyWrapMetadataPropertyNames = GetJsonPropertyNamesForType(typeof(EncryptionKeyWrapMetadata));
foreach (JProperty property in keyWrapMetadataJObj.Properties())
{
Assert.IsTrue(keyWrapMetadataPropertyNames.Contains(property.Name));
}
IEnumerable<byte> expectedWrappedKey = this.VerifyWrap(this.dek, this.metadata1);
this.mockKeyWrapProvider.VerifyNoOtherCalls();
Assert.IsTrue(expectedWrappedKey.SequenceEqual(dekProperties.WrappedDataEncryptionKey));
}
[TestMethod]
public async Task EncryptionUTRewrapDek()
{
Container container = this.GetContainerWithMockSetup();
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)container).Database;
string dekId = "mydek";
DataEncryptionKeyResponse createResponse = await database.CreateDataEncryptionKeyAsync(dekId, EncryptionUnitTests.Algo, this.metadata1);
DataEncryptionKeyProperties createdProperties = createResponse.Resource;
Assert.AreEqual(HttpStatusCode.Created, createResponse.StatusCode);
this.VerifyWrap(this.dek, this.metadata1);
DataEncryptionKey dek = database.GetDataEncryptionKey(dekId);
DataEncryptionKeyResponse rewrapResponse = await dek.RewrapAsync(this.metadata2);
DataEncryptionKeyProperties rewrappedProperties = rewrapResponse.Resource;
Assert.IsNotNull(rewrappedProperties);
Assert.AreEqual(dekId, rewrappedProperties.Id);
Assert.AreEqual(createdProperties.CreatedTime, rewrappedProperties.CreatedTime);
Assert.IsNotNull(rewrappedProperties.LastModified);
Assert.AreEqual(createdProperties.ResourceId, rewrappedProperties.ResourceId);
Assert.AreEqual(createdProperties.SelfLink, rewrappedProperties.SelfLink);
IEnumerable<byte> expectedRewrappedKey = this.dek.Select(b => (byte)(b + 2));
Assert.IsTrue(expectedRewrappedKey.SequenceEqual(rewrappedProperties.WrappedDataEncryptionKey));
Assert.AreEqual(new EncryptionKeyWrapMetadata(this.metadata2.Value + this.metadataUpdateSuffix), rewrappedProperties.EncryptionKeyWrapMetadata);
Assert.AreEqual(2, this.testHandler.Received.Count);
RequestMessage rewrapRequestMessage = this.testHandler.Received[1];
Assert.AreEqual(ResourceType.ClientEncryptionKey, rewrapRequestMessage.ResourceType);
Assert.AreEqual(OperationType.Replace, rewrapRequestMessage.OperationType);
Assert.AreEqual(createResponse.ETag, rewrapRequestMessage.Headers[HttpConstants.HttpHeaders.IfMatch]);
Assert.IsTrue(this.testHandler.Deks.ContainsKey(dekId));
DataEncryptionKeyProperties serverDekProperties = this.testHandler.Deks[dekId];
Assert.IsTrue(serverDekProperties.Equals(rewrappedProperties));
this.VerifyWrap(this.dek, this.metadata2);
this.mockKeyWrapProvider.VerifyNoOtherCalls();
}
[TestMethod]
public async Task EncryptionUTCreateItemWithUnknownDek()
{
Container container = this.GetContainerWithMockSetup();
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)container).Database;
MyItem item = EncryptionUnitTests.GetNewItem();
try
{
await container.CreateItemAsync(
item,
new PartitionKey(item.PK),
new Cosmos.PartitionKey(item.PK),
new ItemRequestOptions
{
EncryptionOptions = new EncryptionOptions
{
DataEncryptionKey = database.GetDataEncryptionKey("random"),
DataEncryptionKeyId = "random",
EncryptionAlgorithm = EncryptionUnitTests.Algo,
PathsToEncrypt = MyItem.PathsToEncrypt
}
});
Assert.Fail();
Assert.Fail("Expected CreateItemAsync with unknown data encryption key to fail");
}
catch(CosmosException ex)
catch(Exception)
{
Assert.IsTrue(ex.Message.Contains(ClientResources.DataEncryptionKeyNotFound));
// todo: Should we expose a exception class in the contract too
}
}
@ -216,12 +64,7 @@ namespace Microsoft.Azure.Cosmos
public async Task EncryptionUTCreateItem()
{
Container container = this.GetContainerWithMockSetup();
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)container).Database;
string dekId = "mydek";
DataEncryptionKeyResponse dekResponse = await database.CreateDataEncryptionKeyAsync(dekId, EncryptionUnitTests.Algo, this.metadata1);
Assert.AreEqual(HttpStatusCode.Created, dekResponse.StatusCode);
MyItem item = await EncryptionUnitTests.CreateItemAsync(container, dekId, MyItem.PathsToEncrypt);
MyItem item = await EncryptionUnitTests.CreateItemAsync(container, EncryptionUnitTests.DekId, MyItem.PathsToEncrypt);
// Validate server state
Assert.IsTrue(this.testHandler.Items.TryGetValue(item.Id, out JObject serverItem));
@ -238,11 +81,11 @@ namespace Microsoft.Azure.Cosmos
EncryptionProperties encryptionPropertiesAtServer = ((JObject)eiJProp.Value).ToObject<EncryptionProperties>();
Assert.IsNotNull(encryptionPropertiesAtServer);
Assert.AreEqual(dekResponse.Resource.ResourceId, encryptionPropertiesAtServer.DataEncryptionKeyRid);
Assert.AreEqual(1, encryptionPropertiesAtServer.EncryptionFormatVersion);
Assert.AreEqual(EncryptionUnitTests.DekId, encryptionPropertiesAtServer.DataEncryptionKeyId);
Assert.AreEqual(2, encryptionPropertiesAtServer.EncryptionFormatVersion);
Assert.IsNotNull(encryptionPropertiesAtServer.EncryptedData);
JObject decryptedJObj = EncryptionUnitTests.ParseStream(new MemoryStream(encryptionPropertiesAtServer.EncryptedData.Reverse().ToArray()));
JObject decryptedJObj = EncryptionUnitTests.ParseStream(new MemoryStream(EncryptionUnitTests.DecryptData(encryptionPropertiesAtServer.EncryptedData)));
Assert.AreEqual(2, decryptedJObj.Properties().Count());
Assert.AreEqual(item.EncStr1, decryptedJObj.Property(nameof(MyItem.EncStr1)).Value.Value<string>());
Assert.AreEqual(item.EncInt, decryptedJObj.Property(nameof(MyItem.EncInt)).Value.Value<int>());
@ -252,30 +95,23 @@ namespace Microsoft.Azure.Cosmos
public async Task EncryptionUTReadItem()
{
Container container = this.GetContainerWithMockSetup();
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)container).Database;
MyItem item = await EncryptionUnitTests.CreateItemAsync(container, EncryptionUnitTests.DekId, MyItem.PathsToEncrypt);
string dekId = "mydek";
DataEncryptionKeyResponse dekResponse = await database.CreateDataEncryptionKeyAsync(dekId, EncryptionUnitTests.Algo, this.metadata1);
Assert.AreEqual(HttpStatusCode.Created, dekResponse.StatusCode);
MyItem item = await EncryptionUnitTests.CreateItemAsync(container, dekId, MyItem.PathsToEncrypt);
ItemResponse<MyItem> readResponse = await container.ReadItemAsync<MyItem>(item.Id, new PartitionKey(item.PK));
ItemResponse<MyItem> readResponse = await container.ReadItemAsync<MyItem>(item.Id, new Cosmos.PartitionKey(item.PK));
Assert.AreEqual(item, readResponse.Resource);
}
private static async Task<MyItem> CreateItemAsync(Container container, string dekId, List<string> pathsToEncrypt)
{
DatabaseCore database = (DatabaseCore)((ContainerCore)(ContainerInlineCore)container).Database;
MyItem item = EncryptionUnitTests.GetNewItem();
ItemResponse<MyItem> response = await container.CreateItemAsync<MyItem>(
item,
requestOptions: new ItemRequestOptions
{
EncryptionOptions = new EncryptionOptions
{
DataEncryptionKey = database.GetDataEncryptionKey(dekId),
DataEncryptionKeyId = dekId,
EncryptionAlgorithm = EncryptionUnitTests.Algo,
PathsToEncrypt = pathsToEncrypt
}
});
@ -296,105 +132,22 @@ namespace Microsoft.Azure.Cosmos
};
}
private static IEnumerable<string> GetJsonPropertyNamesForType(Type type)
{
return type
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(p => Attribute.GetCustomAttribute(p, typeof(JsonPropertyAttribute)) != null)
.Select(p => ((JsonPropertyAttribute)Attribute.GetCustomAttribute(p, typeof(JsonPropertyAttribute))).PropertyName);
}
private IEnumerable<byte> VerifyWrap(IEnumerable<byte> dek, EncryptionKeyWrapMetadata inputMetadata)
{
this.mockKeyWrapProvider.Verify(m => m.WrapKeyAsync(
It.Is<byte[]>(key => key.SequenceEqual(dek)),
inputMetadata,
It.IsAny<CancellationToken>()),
Times.Exactly(1));
IEnumerable<byte> expectedWrappedKey = null;
if (inputMetadata == this.metadata1)
{
expectedWrappedKey = dek.Select(b => (byte)(b + 1));
}
else if (inputMetadata == this.metadata2)
{
expectedWrappedKey = dek.Select(b => (byte)(b + 2));
}
else
{
Assert.Fail();
}
// Verify we did unwrap to check on the wrapping
EncryptionKeyWrapMetadata expectedUpdatedMetadata = new EncryptionKeyWrapMetadata(inputMetadata.Value + this.metadataUpdateSuffix);
this.VerifyUnwrap(expectedWrappedKey, expectedUpdatedMetadata);
return expectedWrappedKey;
}
private void VerifyUnwrap(IEnumerable<byte> wrappedDek, EncryptionKeyWrapMetadata inputMetadata)
{
this.mockKeyWrapProvider.Verify(m => m.UnwrapKeyAsync(
It.Is<byte[]>(wrappedKey => wrappedKey.SequenceEqual(wrappedDek)),
inputMetadata,
It.IsAny<CancellationToken>()),
Times.Exactly(1));
}
private Container GetContainer(EncryptionTestHandler encryptionTestHandler = null)
{
this.testHandler = encryptionTestHandler ?? new EncryptionTestHandler();
CosmosClient client = MockCosmosUtil.CreateMockCosmosClient((builder) => builder.AddCustomHandlers(this.testHandler));
DatabaseCore database = new DatabaseCore(client.ClientContext, EncryptionUnitTests.DatabaseId);
return new ContainerInlineCore(new ContainerCore(client.ClientContext, database, EncryptionUnitTests.ContainerId));
}
private Container GetContainerWithMockSetup(EncryptionTestHandler encryptionTestHandler = null)
{
this.testHandler = encryptionTestHandler ?? new EncryptionTestHandler();
this.mockKeyWrapProvider = new Mock<EncryptionKeyWrapProvider>();
this.mockKeyWrapProvider.Setup(m => m.WrapKeyAsync(It.IsAny<byte[]>(), It.IsAny<EncryptionKeyWrapMetadata>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((byte[] key, EncryptionKeyWrapMetadata metadata, CancellationToken cancellationToken) =>
{
EncryptionKeyWrapMetadata responseMetadata = new EncryptionKeyWrapMetadata(metadata.Value + this.metadataUpdateSuffix);
int moveBy = metadata.Value == this.metadata1.Value ? 1 : 2;
return new EncryptionKeyWrapResult(key.Select(b => (byte)(b + moveBy)).ToArray(), responseMetadata);
});
this.mockKeyWrapProvider.Setup(m => m.UnwrapKeyAsync(It.IsAny<byte[]>(), It.IsAny<EncryptionKeyWrapMetadata>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((byte[] wrappedKey, EncryptionKeyWrapMetadata metadata, CancellationToken cancellationToken) =>
{
int moveBy = metadata.Value == this.metadata1.Value + this.metadataUpdateSuffix ? 1 : 2;
return new EncryptionKeyUnwrapResult(wrappedKey.Select(b => (byte)(b - moveBy)).ToArray(), this.cacheTTL);
});
this.mockEncryptor = new Mock<Encryptor>();
this.mockEncryptor.Setup(m => m.EncryptAsync(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((byte[] plainText, string dekId, string algo, CancellationToken t) => EncryptionUnitTests.EncryptData(plainText));
this.mockEncryptor.Setup(m => m.DecryptAsync(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((byte[] cipherText, string dekId, string algo, CancellationToken t) => EncryptionUnitTests.DecryptData(cipherText));
CosmosClient client = MockCosmosUtil.CreateMockCosmosClient((builder) => builder
.AddCustomHandlers(this.testHandler)
.WithEncryptionKeyWrapProvider(this.mockKeyWrapProvider.Object));
.WithEncryptor(this.mockEncryptor.Object));
this.mockEncryptionAlgorithm = new Mock<EncryptionAlgorithm>();
this.mockEncryptionAlgorithm.Setup(m => m.EncryptData(It.IsAny<byte[]>()))
.Returns((byte[] plainText) => plainText.Reverse().ToArray());
this.mockEncryptionAlgorithm.Setup(m => m.DecryptData(It.IsAny<byte[]>()))
.Returns((byte[] cipherText) => cipherText.Reverse().ToArray());
this.mockEncryptionAlgorithm.SetupGet(m => m.AlgorithmName).Returns(AeadAes256CbcHmac256Algorithm.AlgorithmNameConstant);
this.mockDatabaseCore = new Mock<DatabaseCore>(client.ClientContext, EncryptionUnitTests.DatabaseId);
this.mockDatabaseCore.CallBase = true;
this.mockDatabaseCore.Setup(m => m.GetDataEncryptionKey(It.IsAny<string>()))
.Returns((string id) =>
{
Mock<DataEncryptionKeyCore> mockDekCore = new Mock<DataEncryptionKeyCore>(client.ClientContext, this.mockDatabaseCore.Object, id);
mockDekCore.CallBase = true;
mockDekCore.Setup(m => m.GenerateKey(EncryptionUnitTests.Algo)).Returns(this.dek);
mockDekCore.Setup(m => m.GetEncryptionAlgorithm(It.IsAny<byte[]>(), EncryptionUnitTests.Algo))
.Returns(this.mockEncryptionAlgorithm.Object);
return new DataEncryptionKeyInlineCore(mockDekCore.Object);
});
return new ContainerInlineCore(new ContainerCore(client.ClientContext, this.mockDatabaseCore.Object, EncryptionUnitTests.ContainerId));
DatabaseCore database = new DatabaseCore(client.ClientContext, EncryptionUnitTests.DatabaseId);
return new ContainerInlineCore(new ContainerCore(client.ClientContext, database, EncryptionUnitTests.ContainerId));
}
private static JObject ParseStream(Stream stream)
@ -402,6 +155,16 @@ namespace Microsoft.Azure.Cosmos
return JObject.Load(new JsonTextReader(new StreamReader(stream)));
}
private static byte[] EncryptData(byte[] plainText)
{
return plainText.Select(b => (byte)(b + 1)).ToArray();
}
private static byte[] DecryptData(byte[] plainText)
{
return plainText.Select(b => (byte)(b - 1)).ToArray();
}
private class MyItem
{
public static List<string> PathsToEncrypt { get; } = new List<string>() { "/EncStr1", "/EncInt" };
@ -445,7 +208,7 @@ namespace Microsoft.Azure.Cosmos
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return x.EncryptionFormatVersion == y.EncryptionFormatVersion
&& x.DataEncryptionKeyRid == y.DataEncryptionKeyRid
&& x.DataEncryptionKeyId == y.DataEncryptionKeyId
&& x.EncryptedData.SequenceEqual(y.EncryptedData);
}
@ -468,9 +231,6 @@ namespace Microsoft.Azure.Cosmos
this.func = func;
}
public ConcurrentDictionary<string, DataEncryptionKeyProperties> Deks { get; } = new ConcurrentDictionary<string, DataEncryptionKeyProperties>();
public ConcurrentDictionary<string, JObject> Items { get; } = new ConcurrentDictionary<string, JObject>();
public List<RequestMessage> Received { get; } = new List<RequestMessage>();
@ -489,73 +249,7 @@ namespace Microsoft.Azure.Cosmos
HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
if (request.ResourceType == ResourceType.ClientEncryptionKey)
{
DataEncryptionKeyProperties dekProperties = null;
if (request.OperationType == OperationType.Create)
{
dekProperties = this.serializer.FromStream<DataEncryptionKeyProperties>(request.Content);
string databaseRid = ResourceId.NewDatabaseId(1).ToString();
dekProperties.ResourceId = ResourceId.NewClientEncryptionKeyId(databaseRid, (uint)this.Received.Count).ToString();
dekProperties.CreatedTime = EncryptionTestHandler.ReducePrecisionToSeconds(DateTime.UtcNow);
dekProperties.LastModified = dekProperties.CreatedTime;
dekProperties.ETag = Guid.NewGuid().ToString();
dekProperties.SelfLink = string.Format(
"dbs/{0}/{1}/{2}/",
databaseRid,
Paths.ClientEncryptionKeysPathSegment,
dekProperties.ResourceId);
httpStatusCode = HttpStatusCode.Created;
if (!this.Deks.TryAdd(dekProperties.Id, dekProperties))
{
httpStatusCode = HttpStatusCode.Conflict;
}
}
else if (request.OperationType == OperationType.Read)
{
string dekId = EncryptionTestHandler.ParseDekUri(request.RequestUri);
httpStatusCode = HttpStatusCode.OK;
if (!this.Deks.TryGetValue(dekId, out dekProperties))
{
httpStatusCode = HttpStatusCode.NotFound;
}
}
else if(request.OperationType == OperationType.Replace)
{
string dekId = EncryptionTestHandler.ParseDekUri(request.RequestUri);
dekProperties = this.serializer.FromStream<DataEncryptionKeyProperties>(request.Content);
dekProperties.LastModified = EncryptionTestHandler.ReducePrecisionToSeconds(DateTime.UtcNow);
dekProperties.ETag = Guid.NewGuid().ToString();
httpStatusCode = HttpStatusCode.OK;
if (!this.Deks.TryGetValue(dekId, out DataEncryptionKeyProperties existingDekProperties))
{
httpStatusCode = HttpStatusCode.NotFound;
}
if(!this.Deks.TryUpdate(dekId, dekProperties, existingDekProperties)) { throw new InvalidOperationException("Concurrency not handled in tests."); }
}
else if (request.OperationType == OperationType.Delete)
{
string dekId = EncryptionTestHandler.ParseDekUri(request.RequestUri);
httpStatusCode = HttpStatusCode.NoContent;
if (!this.Deks.TryRemove(dekId, out _))
{
httpStatusCode = HttpStatusCode.NotFound;
}
}
ResponseMessage responseMessage = new ResponseMessage(httpStatusCode, request)
{
Content = dekProperties != null ? this.serializer.ToStream(dekProperties) : null,
};
responseMessage.Headers.RequestCharge = EncryptionUnitTests.requestCharge;
responseMessage.Headers.ETag = dekProperties?.ETag;
return responseMessage;
}
else if(request.ResourceType == ResourceType.Document)
if(request.ResourceType == ResourceType.Document)
{
JObject item = null;
if (request.OperationType == OperationType.Create)
@ -633,9 +327,9 @@ namespace Microsoft.Azure.Cosmos
Content = contentClone
};
foreach (string x in request.Headers)
foreach (string headerName in request.Headers)
{
clone.Headers.Set(x, request.Headers[x]);
clone.Headers.Set(headerName, request.Headers[headerName]);
}
return clone;
@ -652,21 +346,6 @@ namespace Microsoft.Azure.Cosmos
Assert.AreEqual(Paths.DocumentsPathSegment, segments[4]);
return segments[5];
}
private static string ParseDekUri(Uri requestUri)
{
string[] segments = requestUri.OriginalString.Split("/", StringSplitOptions.RemoveEmptyEntries);
Assert.AreEqual(4, segments.Length);
Assert.AreEqual(Paths.DatabasesPathSegment, segments[0]);
Assert.AreEqual(EncryptionUnitTests.DatabaseId, segments[1]);
Assert.AreEqual(Paths.ClientEncryptionKeysPathSegment, segments[2]);
return segments[3];
}
private static DateTime ReducePrecisionToSeconds(DateTime input)
{
return new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, DateTimeKind.Utc);
}
}
}
}