[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:
Родитель
a2c1f7aa71
Коммит
50ebd8c8ff
|
@ -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)]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче