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); }
/// <inheritdoc /> public override async Task <EncryptionKeyWrapResult> WrapKeyAsync( byte[] key, EncryptionKeyWrapMetadata metadata, CancellationToken cancellationToken) { if (metadata.Type != AzureKeyVaultKeyWrapMetadata.TypeConstant) { throw new ArgumentException("Invalid metadata", nameof(metadata)); } if (!KeyVaultKeyUriProperties.TryParse(new Uri(metadata.Value), out KeyVaultKeyUriProperties keyVaultUriProperties)) { throw new ArgumentException("KeyVault Key Uri {0} is invalid.", metadata.Value); } if (!await this.keyVaultAccessClient.ValidatePurgeProtectionAndSoftDeleteSettingsAsync(keyVaultUriProperties, cancellationToken)) { throw new ArgumentException(string.Format("Key Vault {0} provided must have soft delete and purge protection enabled.", keyVaultUriProperties.KeyUri)); } byte[] result = await this.keyVaultAccessClient.WrapKeyAsync(key, keyVaultUriProperties, cancellationToken); EncryptionKeyWrapMetadata responseMetadata = new EncryptionKeyWrapMetadata(metadata.Type, metadata.Value, KeyVaultConstants.RsaOaep256); return(new EncryptionKeyWrapResult(result, responseMetadata)); }
/// <inheritdoc /> public override async Task <EncryptionKeyUnwrapResult> UnwrapKeyAsync( byte[] wrappedKey, EncryptionKeyWrapMetadata metadata, CancellationToken cancellationToken) { if (metadata.Type != AzureKeyVaultKeyWrapMetadata.TypeConstant) { throw new ArgumentException("Invalid metadata", nameof(metadata)); } if (metadata.Algorithm != KeyVaultConstants.RsaOaep256) { throw new ArgumentException( string.Format("Unknown encryption key wrap algorithm {0}", metadata.Algorithm), nameof(metadata)); } if (!KeyVaultKeyUriProperties.TryParse(new Uri(metadata.Value), out KeyVaultKeyUriProperties keyVaultUriProperties)) { throw new ArgumentException("KeyVault Key Uri {0} is invalid.", metadata.Value); } byte[] result = await this.keyVaultAccessClient.UnwrapKeyAsync(wrappedKey, keyVaultUriProperties, cancellationToken); return(new EncryptionKeyUnwrapResult(result, this.rawDekCacheTimeToLive)); }
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 async Task <ItemResponse <DataEncryptionKeyProperties> > RewrapDataEncryptionKeyAsync( string id, EncryptionKeyWrapMetadata newWrapMetadata, ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { 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) { WrappedDataEncryptionKey = wrappedDek, 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); }
/// <summary> /// Initializes a new instance of <see cref="DataEncryptionKeyProperties"/>. /// </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="wrappedDataEncryptionKey">Wrapped (encrypted) form of the data encryption key.</param> /// <param name="encryptionKeyWrapMetadata">Metadata used by the configured key wrapping provider in order to unwrap the key.</param> /// <param name="createdTime">Time at which this data encryption key was created.</param> public DataEncryptionKeyProperties( string id, string encryptionAlgorithm, byte[] wrappedDataEncryptionKey, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata, DateTime createdTime) { this.Id = !string.IsNullOrEmpty(id) ? id : throw new ArgumentNullException(nameof(id)); this.EncryptionAlgorithm = encryptionAlgorithm; this.WrappedDataEncryptionKey = wrappedDataEncryptionKey ?? throw new ArgumentNullException(nameof(wrappedDataEncryptionKey)); this.EncryptionKeyWrapMetadata = encryptionKeyWrapMetadata ?? throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata)); this.CreatedTime = createdTime; }
/// <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))); }
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); }
/// <summary> /// Create a Client Encryption Key used to Encrypt data. /// </summary> /// <param name="database">Regular cosmos database.</param> /// <param name="clientEncryptionKeyId"> Client Encryption Key id.</param> /// <param name="dataEncryptionKeyAlgorithm"> Encryption Algorthm. </param> /// <param name="encryptionKeyWrapMetadata"> EncryptionKeyWrapMetadata.</param> /// <param name="cancellationToken"> cancellation token </param> /// <returns>Container to perform operations supporting client-side encryption / decryption.</returns> /// <example> /// This example shows how to create a new Client Encryption Key. /// /// <code language="c#"> /// <![CDATA[ /// ClientEncryptionKeyResponse response = await this.cosmosDatabase.CreateClientEncryptionKeyAsync( /// "testKey", /// DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, /// new EncryptionKeyWrapMetadata("metadataName", "MetadataValue")); /// ]]> /// </code> /// </example> public static async Task <ClientEncryptionKeyResponse> CreateClientEncryptionKeyAsync( this Database database, string clientEncryptionKeyId, DataEncryptionKeyAlgorithm dataEncryptionKeyAlgorithm, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(clientEncryptionKeyId)) { throw new ArgumentNullException(nameof(clientEncryptionKeyId)); } string encryptionAlgorithm = dataEncryptionKeyAlgorithm.ToString(); if (!string.Equals(encryptionAlgorithm, DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.ToString())) { throw new ArgumentException($"Invalid Encryption Algorithm '{encryptionAlgorithm}' passed. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } if (encryptionKeyWrapMetadata == null) { throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata)); } EncryptionCosmosClient encryptionCosmosClient; if (database is EncryptionDatabase encryptionDatabase) { encryptionCosmosClient = encryptionDatabase.EncryptionCosmosClient; } else { throw new ArgumentException("Creating a ClientEncryptionKey resource requires the use of an encryption - enabled client. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } EncryptionKeyStoreProvider encryptionKeyStoreProvider = encryptionCosmosClient.EncryptionKeyStoreProvider; KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.GetOrCreate( encryptionKeyWrapMetadata.Name, encryptionKeyWrapMetadata.Value, encryptionKeyStoreProvider); ProtectedDataEncryptionKey protectedDataEncryptionKey = new ProtectedDataEncryptionKey( clientEncryptionKeyId, keyEncryptionKey); byte[] wrappedDataEncryptionKey = protectedDataEncryptionKey.EncryptedValue; ClientEncryptionKeyProperties clientEncryptionKeyProperties = new ClientEncryptionKeyProperties( clientEncryptionKeyId, encryptionAlgorithm, wrappedDataEncryptionKey, encryptionKeyWrapMetadata); ClientEncryptionKeyResponse clientEncryptionKeyResponse = await database.CreateClientEncryptionKeyAsync( clientEncryptionKeyProperties, cancellationToken : cancellationToken); return(clientEncryptionKeyResponse); }
/// <summary> /// Rewrap an existing Client Encryption Key. /// </summary> /// <param name="database">Regular cosmos database.</param> /// <param name="clientEncryptionKeyId"> Client Encryption Key id.</param> /// <param name="newEncryptionKeyWrapMetadata"> EncryptionKeyWrapMetadata.</param> /// <param name="cancellationToken"> cancellation token </param> /// <returns>Container to perform operations supporting client-side encryption / decryption.</returns> /// <example> /// This example shows how to rewrap a Client Encryption Key. /// /// <code language="c#"> /// <![CDATA[ /// ClientEncryptionKeyResponse response = await this.cosmosDatabase.RewrapClientEncryptionKeyAsync( /// "keyToRewrap", /// new EncryptionKeyWrapMetadata("metadataName", "UpdatedMetadataValue"))); /// ]]> /// </code> /// </example> public static async Task <ClientEncryptionKeyResponse> RewrapClientEncryptionKeyAsync( this Database database, string clientEncryptionKeyId, EncryptionKeyWrapMetadata newEncryptionKeyWrapMetadata, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(clientEncryptionKeyId)) { throw new ArgumentNullException(nameof(clientEncryptionKeyId)); } if (newEncryptionKeyWrapMetadata == null) { throw new ArgumentNullException(nameof(newEncryptionKeyWrapMetadata)); } ClientEncryptionKey clientEncryptionKey = database.GetClientEncryptionKey(clientEncryptionKeyId); EncryptionCosmosClient encryptionCosmosClient; if (database is EncryptionDatabase encryptionDatabase) { encryptionCosmosClient = encryptionDatabase.EncryptionCosmosClient; } else { throw new ArgumentException("Rewraping a ClientEncryptionKey requires the use of an encryption - enabled client. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } EncryptionKeyStoreProvider encryptionKeyStoreProvider = encryptionCosmosClient.EncryptionKeyStoreProvider; ClientEncryptionKeyProperties clientEncryptionKeyProperties = await clientEncryptionKey.ReadAsync(cancellationToken : cancellationToken); RequestOptions requestOptions = new RequestOptions { IfMatchEtag = clientEncryptionKeyProperties.ETag, }; KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.GetOrCreate( clientEncryptionKeyProperties.EncryptionKeyWrapMetadata.Name, clientEncryptionKeyProperties.EncryptionKeyWrapMetadata.Value, encryptionKeyStoreProvider); byte[] unwrappedKey = keyEncryptionKey.DecryptEncryptionKey(clientEncryptionKeyProperties.WrappedDataEncryptionKey); keyEncryptionKey = KeyEncryptionKey.GetOrCreate( newEncryptionKeyWrapMetadata.Name, newEncryptionKeyWrapMetadata.Value, encryptionKeyStoreProvider); byte[] rewrappedKey = keyEncryptionKey.EncryptEncryptionKey(unwrappedKey); clientEncryptionKeyProperties = new ClientEncryptionKeyProperties( clientEncryptionKeyId, clientEncryptionKeyProperties.EncryptionAlgorithm, rewrappedKey, newEncryptionKeyWrapMetadata); ClientEncryptionKeyResponse clientEncryptionKeyResponse = await clientEncryptionKey.ReplaceAsync( clientEncryptionKeyProperties, requestOptions, cancellationToken : cancellationToken); return(clientEncryptionKeyResponse); }
/// <summary> /// Generates a data encryption key, wraps it using the key wrap metadata provided /// with the key wrapping provider in the <see cref="CosmosDataEncryptionKeyProvider"/> for encryption, /// 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> /// Rewraps a client encryption key. /// This wraps the existing data encryption key using information provided in the newEncryptionKeyWrapMetadata /// using the IKeyEncryptionKeyResolver instance configured on the client, /// and saves the wrapped data encryption key along with the newEncryptionKeyWrapMetadata at the Cosmos DB service. /// </summary> /// <param name="database">Database supporting encryption in which the client encryption key properties will be updated.</param> /// <param name="clientEncryptionKeyId">Identifier for the client encryption key.</param> /// <param name="newEncryptionKeyWrapMetadata">Metadata used to wrap the data encryption key with a key encryption key.</param> /// <param name="cancellationToken">Token for request cancellation.</param> /// <returns>Response from the Cosmos DB service with updated <see cref="ClientEncryptionKeyProperties"/>.</returns> /// <example> /// This example shows how to rewrap a client encryption key. /// /// <code language="c#"> /// <![CDATA[ /// Azure.Core.TokenCredential tokenCredential = new Azure.Identity.DefaultAzureCredential(); /// Azure.Core.Cryptography.IKeyEncryptionKeyResolver keyResolver = new Azure.Security.KeyVault.Keys.Cryptography.KeyResolver(tokenCredential); /// CosmosClient client = (new CosmosClient(endpoint, authKey)).WithEncryption(keyResolver, KeyEncryptionKeyResolverName.AzureKeyVault); /// /// EncryptionKeyWrapMetadata wrapMetadataForNewKeyVersion = new EncryptionKeyWrapMetadata( /// type: KeyEncryptionKeyResolverName.AzureKeyVault, /// name: "myKek", /// value: "https://contoso.vault.azure.net/keys/myKek/0698c2156c1a4e1da5b6bab6f6422fd6", /// algorithm: Azure.Security.KeyVault.Keys.Cryptography.EncryptionAlgorithm.RsaOaep.ToString()); /// /// ClientEncryptionKeyResponse response = await client.GetDatabase("databaseId").RewrapClientEncryptionKeyAsync( /// "myCek", /// wrapMetadataForNewKeyVersion); /// ]]> /// </code> /// </example> /// <remarks> /// See <see href="https://aka.ms/CosmosClientEncryption">client-side encryption documentation</see> for more details. /// </remarks> public static async Task <ClientEncryptionKeyResponse> RewrapClientEncryptionKeyAsync( this Database database, string clientEncryptionKeyId, EncryptionKeyWrapMetadata newEncryptionKeyWrapMetadata, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(clientEncryptionKeyId)) { throw new ArgumentNullException(nameof(clientEncryptionKeyId)); } if (newEncryptionKeyWrapMetadata == null) { throw new ArgumentNullException(nameof(newEncryptionKeyWrapMetadata)); } if (newEncryptionKeyWrapMetadata.Algorithm != EncryptionKeyStoreProviderImpl.RsaOaepWrapAlgorithm) { throw new ArgumentException($"Invalid key wrap algorithm '{newEncryptionKeyWrapMetadata.Algorithm}' passed. Please refer to https://aka.ms/CosmosClientEncryption for more details."); } ClientEncryptionKey clientEncryptionKey = database.GetClientEncryptionKey(clientEncryptionKeyId); EncryptionCosmosClient encryptionCosmosClient = database is EncryptionDatabase encryptionDatabase ? encryptionDatabase.EncryptionCosmosClient : throw new ArgumentException("Rewrapping a client encryption key requires the use of an encryption-enabled client. Please refer to https://aka.ms/CosmosClientEncryption for more details."); if (!string.Equals(newEncryptionKeyWrapMetadata.Type, encryptionCosmosClient.KeyEncryptionKeyResolverName)) { throw new ArgumentException($"The Type of the EncryptionKeyWrapMetadata '{newEncryptionKeyWrapMetadata.Type}' does not match" + $" with the keyEncryptionKeyResolverName '{encryptionCosmosClient.KeyEncryptionKeyResolverName}' configured." + " Please refer to https://aka.ms/CosmosClientEncryption for more details."); } ClientEncryptionKeyProperties clientEncryptionKeyProperties = await clientEncryptionKey.ReadAsync(cancellationToken : cancellationToken); RequestOptions requestOptions = new RequestOptions { IfMatchEtag = clientEncryptionKeyProperties.ETag, }; KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.GetOrCreate( clientEncryptionKeyProperties.EncryptionKeyWrapMetadata.Name, clientEncryptionKeyProperties.EncryptionKeyWrapMetadata.Value, encryptionCosmosClient.EncryptionKeyStoreProviderImpl); byte[] unwrappedKey = keyEncryptionKey.DecryptEncryptionKey(clientEncryptionKeyProperties.WrappedDataEncryptionKey); keyEncryptionKey = KeyEncryptionKey.GetOrCreate( newEncryptionKeyWrapMetadata.Name, newEncryptionKeyWrapMetadata.Value, encryptionCosmosClient.EncryptionKeyStoreProviderImpl); byte[] rewrappedKey = keyEncryptionKey.EncryptEncryptionKey(unwrappedKey); clientEncryptionKeyProperties = new ClientEncryptionKeyProperties( clientEncryptionKeyId, clientEncryptionKeyProperties.EncryptionAlgorithm, rewrappedKey, newEncryptionKeyWrapMetadata); ClientEncryptionKeyResponse clientEncryptionKeyResponse = await clientEncryptionKey.ReplaceAsync( clientEncryptionKeyProperties, requestOptions, cancellationToken : cancellationToken); return(clientEncryptionKeyResponse); }
/// <summary> /// Unwraps (i.e. decrypts) the provided wrapped data encryption key. /// </summary> /// <param name="wrappedKey">Wrapped form of data encryption key that needs to be unwrapped.</param> /// <param name="metadata">Metadata for the wrap provider that should be used to wrap / unwrap the key.</param> /// <param name="cancellationToken">Cancellation token allowing for cancellation of this operation.</param> /// <returns>Awaitable unwrapped (i.e. unencrypted) version of data encryption key passed in and how long the raw data encryption key can be cached on the client.</returns> public abstract Task <EncryptionKeyUnwrapResult> UnwrapKeyAsync(byte[] wrappedKey, EncryptionKeyWrapMetadata metadata, CancellationToken cancellationToken);
/// <summary> /// Initializes a new instance of the result of wrapping a data encryption key. /// </summary> /// <param name="wrappedDataEncryptionKey"> /// Wrapped form of data encryption key. /// The byte array passed in must not be modified after this call by the <see cref="EncryptionKeyWrapProvider"/>. /// </param> /// <param name="encryptionKeyWrapMetadata">Metadata that can be used by the wrap provider to unwrap the data encryption key.</param> public EncryptionKeyWrapResult(byte[] wrappedDataEncryptionKey, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata) { this.WrappedDataEncryptionKey = wrappedDataEncryptionKey ?? throw new ArgumentNullException(nameof(wrappedDataEncryptionKey)); this.EncryptionKeyWrapMetadata = encryptionKeyWrapMetadata ?? throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata)); }
/// <summary> /// Creates a client encryption key. /// This generates a cryptographically random data encryption key, wraps it using /// information provided in the encryptionKeyWrapMetadata using the IKeyEncryptionKeyResolver instance configured on the client, /// and saves the wrapped data encryption key along with the encryptionKeyWrapMetadata at the Cosmos DB service. /// </summary> /// <param name="database">Database supporting encryption in which the client encryption key properties will be saved.</param> /// <param name="clientEncryptionKeyId">Identifier for the client encryption key.</param> /// <param name="encryptionAlgorithm">Algorithm which will be used for encryption with this key.</param> /// <param name="encryptionKeyWrapMetadata">Metadata used to wrap the data encryption key with a key encryption key.</param> /// <param name="cancellationToken">Token for request cancellation.</param> /// <returns>Response from the Cosmos DB service with <see cref="ClientEncryptionKeyProperties"/>.</returns> /// <example> /// This example shows how to create a client encryption key. /// /// <code language="c#"> /// <![CDATA[ /// Azure.Core.TokenCredential tokenCredential = new Azure.Identity.DefaultAzureCredential(); /// Azure.Core.Cryptography.IKeyEncryptionKeyResolver keyResolver = new Azure.Security.KeyVault.Keys.Cryptography.KeyResolver(tokenCredential); /// CosmosClient client = (new CosmosClient(endpoint, authKey)).WithEncryption(keyResolver, KeyEncryptionKeyResolverName.AzureKeyVault); /// /// EncryptionKeyWrapMetadata wrapMetadata = new EncryptionKeyWrapMetadata( /// type: KeyEncryptionKeyResolverName.AzureKeyVault, /// name: "myKek", /// value: "https://contoso.vault.azure.net/keys/myKek/78deebed173b48e48f55abf87ed4cf71", /// algorithm: Azure.Security.KeyVault.Keys.Cryptography.EncryptionAlgorithm.RsaOaep.ToString()); /// /// ClientEncryptionKeyResponse response = await client.GetDatabase("databaseId").CreateClientEncryptionKeyAsync( /// "myCek", /// DataEncryptionAlgorithm.AeadAes256CbcHmacSha256, /// wrapMetadata); /// ]]> /// </code> /// </example> /// <remarks> /// See <see href="https://aka.ms/CosmosClientEncryption">client-side encryption documentation</see> for more details. /// </remarks> public static async Task <ClientEncryptionKeyResponse> CreateClientEncryptionKeyAsync( this Database database, string clientEncryptionKeyId, string encryptionAlgorithm, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(clientEncryptionKeyId)) { throw new ArgumentNullException(nameof(clientEncryptionKeyId)); } if (!string.Equals(encryptionAlgorithm, DataEncryptionAlgorithm.AeadAes256CbcHmacSha256)) { throw new ArgumentException($"Invalid encryption algorithm '{encryptionAlgorithm}' passed. Please refer to https://aka.ms/CosmosClientEncryption for more details."); } if (encryptionKeyWrapMetadata == null) { throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata)); } if (encryptionKeyWrapMetadata.Algorithm != EncryptionKeyStoreProviderImpl.RsaOaepWrapAlgorithm) { throw new ArgumentException($"Invalid key wrap algorithm '{encryptionKeyWrapMetadata.Algorithm}' passed. Please refer to https://aka.ms/CosmosClientEncryption for more details."); } EncryptionCosmosClient encryptionCosmosClient = database is EncryptionDatabase encryptionDatabase ? encryptionDatabase.EncryptionCosmosClient : throw new ArgumentException("Creating a client encryption key requires the use of an encryption-enabled client. Please refer to https://aka.ms/CosmosClientEncryption for more details."); if (!string.Equals(encryptionKeyWrapMetadata.Type, encryptionCosmosClient.KeyEncryptionKeyResolverName)) { throw new ArgumentException($"The Type of the EncryptionKeyWrapMetadata '{encryptionKeyWrapMetadata.Type}' does not match" + $" with the keyEncryptionKeyResolverName '{encryptionCosmosClient.KeyEncryptionKeyResolverName}' configured." + " Please refer to https://aka.ms/CosmosClientEncryption for more details."); } KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.GetOrCreate( encryptionKeyWrapMetadata.Name, encryptionKeyWrapMetadata.Value, encryptionCosmosClient.EncryptionKeyStoreProviderImpl); ProtectedDataEncryptionKey protectedDataEncryptionKey = new ProtectedDataEncryptionKey( clientEncryptionKeyId, keyEncryptionKey); byte[] wrappedDataEncryptionKey = protectedDataEncryptionKey.EncryptedValue; // cache it. ProtectedDataEncryptionKey.GetOrCreate( clientEncryptionKeyId, keyEncryptionKey, wrappedDataEncryptionKey); ClientEncryptionKeyProperties clientEncryptionKeyProperties = new ClientEncryptionKeyProperties( clientEncryptionKeyId, encryptionAlgorithm, wrappedDataEncryptionKey, encryptionKeyWrapMetadata); ClientEncryptionKeyResponse clientEncryptionKeyResponse = await database.CreateClientEncryptionKeyAsync( clientEncryptionKeyProperties, cancellationToken : cancellationToken); return(clientEncryptionKeyResponse); }
/// <inheritdoc/> public override async Task <ItemResponse <DataEncryptionKeyProperties> > RewrapDataEncryptionKeyAsync( string id, EncryptionKeyWrapMetadata newWrapMetadata, ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { 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) { WrappedDataEncryptionKey = wrappedDek, EncryptionKeyWrapMetadata = updatedMetadata, }; ItemResponse <DataEncryptionKeyProperties> response; try { response = await this.DekProvider.Container.ReplaceItemAsync( newDekProperties, newDekProperties.Id, new PartitionKey(newDekProperties.Id), requestOptions, cancellationToken); Debug.Assert(response.Resource != null); } catch (CosmosException ex) { if (!ex.StatusCode.Equals(HttpStatusCode.PreconditionFailed)) { throw; } // Handle if exception is due to etag mismatch. The scenario is as follows - say there are 2 clients A and B that both have the DEK properties cached. // From A, rewrap worked and the DEK is updated. Now from B, rewrap was attempted later based on the cached properties which will fail due to etag mismatch. // To address this, we do an explicit read, which reads the key from storage and updates the cached properties; and then attempt rewrap again. await this.ReadDataEncryptionKeyAsync( newDekProperties.Id, requestOptions, cancellationToken); return(await this.RewrapDataEncryptionKeyAsync( id, newWrapMetadata, requestOptions, cancellationToken)); } this.DekProvider.DekCache.SetDekProperties(id, response.Resource); this.DekProvider.DekCache.SetRawDek(id, updatedRawDek); return(response); }