public override async Task <ItemResponse <T> > CreateItemAsync <T>( T item, PartitionKey?partitionKey = null, ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { if (item == null) { throw new ArgumentNullException(nameof(item)); } if (!(requestOptions is EncryptionItemRequestOptions encryptionItemRequestOptions) || encryptionItemRequestOptions.EncryptionOptions == null) { return(await this.container.CreateItemAsync <T>( item, partitionKey, requestOptions, cancellationToken)); } if (partitionKey == null) { throw new NotSupportedException($"{nameof(partitionKey)} cannot be null for operations using {nameof(EncryptionContainer)}."); } CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); using (diagnosticsContext.CreateScope("CreateItem")) { ResponseMessage responseMessage; if (item is EncryptableItem encryptableItem) { using (Stream streamPayload = encryptableItem.ToStream(this.CosmosSerializer)) { responseMessage = await this.CreateItemHelperAsync( streamPayload, partitionKey.Value, requestOptions, decryptResponse : false, diagnosticsContext, cancellationToken); } encryptableItem.SetDecryptableItem( EncryptionProcessor.BaseSerializer.FromStream <JObject>(responseMessage.Content), this.Encryptor, this.CosmosSerializer); return(new EncryptionItemResponse <T>( responseMessage, item)); } else { using (Stream itemStream = this.CosmosSerializer.ToStream <T>(item)) { responseMessage = await this.CreateItemHelperAsync( itemStream, partitionKey.Value, requestOptions, decryptResponse : true, diagnosticsContext, cancellationToken); } return(this.ResponseFactory.CreateItemResponse <T>(responseMessage)); } } }
/// <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); }