public override async Task <ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default) { EncryptionSettings encryptionSettings = await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings : null, cancellationToken : cancellationToken); await this.GetIteratorWithEncryptionHeaderAndEncryptPartitionKeyIfRequiredAsync(encryptionSettings); ResponseMessage responseMessage = await this.FeedIterator.ReadNextAsync(cancellationToken); EncryptionDiagnosticsContext encryptionDiagnosticsContext = new EncryptionDiagnosticsContext(); // check for Bad Request and Wrong intended RID and update the cached RID and Client Encryption Policy. await this.encryptionContainer.ThrowIfRequestNeedsARetryPostPolicyRefreshAsync(responseMessage, encryptionSettings, encryptionDiagnosticsContext, cancellationToken); if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null) { Stream decryptedContent = await EncryptionProcessor.DeserializeAndDecryptResponseAsync( responseMessage.Content, encryptionSettings, encryptionDiagnosticsContext, cancellationToken); encryptionDiagnosticsContext.AddEncryptionDiagnosticsToResponseMessage(responseMessage); return(new DecryptedResponseMessage(responseMessage, decryptedContent)); } return(responseMessage); }
public override async Task <TransactionalBatchResponse> ExecuteAsync( CancellationToken cancellationToken = default) { CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(options: null); using (diagnosticsContext.CreateScope("TransactionalBatch.ExecuteAsync")) { TransactionalBatchResponse response = await this.transactionalBatch.ExecuteAsync(cancellationToken); if (response.IsSuccessStatusCode) { for (int index = 0; index < response.Count; index++) { TransactionalBatchOperationResult result = response[index]; if (result.ResourceStream != null) { (result.ResourceStream, _) = await EncryptionProcessor.DecryptAsync( result.ResourceStream, this.encryptor, diagnosticsContext, cancellationToken); } } } return(response); } }
private async Task <TransactionalBatchResponse> DecryptTransactionalBatchResponseAsync( TransactionalBatchResponse response, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { List <TransactionalBatchOperationResult> decryptedTransactionalBatchOperationResults = new List <TransactionalBatchOperationResult>(); if (response.IsSuccessStatusCode) { for (int index = 0; index < response.Count; index++) { TransactionalBatchOperationResult result = response[index]; if (result.ResourceStream != null) { (Stream decryptedStream, _) = await EncryptionProcessor.DecryptAsync( result.ResourceStream, this.encryptor, diagnosticsContext, cancellationToken); result = new EncryptionTransactionalBatchOperationResult(response[index], decryptedStream); } decryptedTransactionalBatchOperationResults.Add(result); } } return(new EncryptionTransactionalBatchResponse( decryptedTransactionalBatchOperationResults, response, this.cosmosSerializer)); }
private async Task <ResponseMessage> ReadItemHelperAsync( string id, PartitionKey partitionKey, ItemRequestOptions requestOptions, bool decryptResponse, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { ResponseMessage responseMessage = await this.container.ReadItemStreamAsync( id, partitionKey, requestOptions, cancellationToken); if (decryptResponse) { (responseMessage.Content, _) = await EncryptionProcessor.DecryptAsync( responseMessage.Content, this.Encryptor, diagnosticsContext, cancellationToken); } return(responseMessage); }
public static async Task <JObject> DecryptAsync( JObject document, Encryptor encryptor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(document != null); Debug.Assert(encryptor != null); Debug.Assert(diagnosticsContext != null); if (!document.TryGetValue(Constants.EncryptedInfo, out JToken encryptedInfo)) { return(document); } EncryptionProperties encryptionProperties = JsonConvert.DeserializeObject <EncryptionProperties>(encryptedInfo.ToString()); JObject plainTextJObj = await EncryptionProcessor.DecryptContentAsync( encryptionProperties, encryptor, diagnosticsContext, cancellationToken); document.Remove(Constants.EncryptedInfo); foreach (JProperty property in plainTextJObj.Properties()) { document.Add(property.Name, property.Value); } return(document); }
public EncryptionFeedIterator( FeedIterator feedIterator, EncryptionProcessor encryptionProcessor) { this.feedIterator = feedIterator ?? throw new ArgumentNullException(nameof(feedIterator)); this.encryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); }
public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( string processorName, ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) { return(this.container.GetChangeFeedProcessorBuilderWithManualCheckpoint( processorName, async( ChangeFeedProcessorContext context, Stream changes, Func <Task> tryCheckpointAsync, CancellationToken cancellationToken) => { EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); Stream decryptedChanges = await EncryptionProcessor.DeserializeAndDecryptResponseAsync( changes, encryptionSettings, operationDiagnostics: null, cancellationToken); // Call the original passed in delegate await onChangesDelegate(context, decryptedChanges, tryCheckpointAsync, cancellationToken); })); }
public override TransactionalBatch CreateItemStream( Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions = null) { if (requestOptions is EncryptionTransactionalBatchItemRequestOptions encryptionItemRequestOptions && encryptionItemRequestOptions.EncryptionOptions != null) { CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); using (diagnosticsContext.CreateScope("EncryptItemStream")) { streamPayload = EncryptionProcessor.EncryptAsync( streamPayload, this.encryptor, encryptionItemRequestOptions.EncryptionOptions, diagnosticsContext, cancellationToken: default).Result; } } this.transactionalBatch = this.transactionalBatch.CreateItemStream( streamPayload, requestOptions); return(this); }
internal async Task <EncryptionSettings> GetEncryptionSettingForPropertyAsync( string propertyName, EncryptionProcessor encryptionProcessor, CancellationToken cancellationToken) { CachedEncryptionSettings cachedEncryptionSettings = await this.EncryptionSettingCacheByPropertyName.GetAsync( propertyName, obsoleteValue : null, async() => await this.FetchCachedEncryptionSettingsAsync(propertyName, encryptionProcessor, cancellationToken), cancellationToken); if (cachedEncryptionSettings == null) { return(null); } // we just cache the algo for the property for a duration of 1 hour and when it expires we try to fetch the cached Encrypted key // from the Cosmos Client and try to create a Protected Data Encryption Key which tries to unwrap the key. // 1) Try to check if the KEK has been revoked may be post rotation. If the request fails this could mean the KEK was revoked, // the user might have rewraped the Key and that is when we try to force fetch it from the Backend. // So we only read back from the backend only when an operation like wrap/unwrap with the Master Key fails. if (cachedEncryptionSettings.EncryptionSettingsExpiryUtc <= DateTime.UtcNow) { cachedEncryptionSettings = await this.EncryptionSettingCacheByPropertyName.GetAsync( propertyName, obsoleteValue : null, async() => await this.FetchCachedEncryptionSettingsAsync(propertyName, encryptionProcessor, cancellationToken), cancellationToken, forceRefresh : true); } return(cachedEncryptionSettings.EncryptionSettings); }
public async override Task <ResponseMessage> ReplaceItemStreamAsync( Stream streamPayload, string id, PartitionKey partitionKey, ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { if (id == null) { throw new ArgumentNullException(nameof(id)); } if (streamPayload == null) { throw new ArgumentNullException(nameof(streamPayload)); } CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); using (diagnosticsContext.CreateScope("ReplaceItemStream")) { if (requestOptions is EncryptionItemRequestOptions encryptionItemRequestOptions) { if (partitionKey == null) { throw new NotSupportedException($"{nameof(partitionKey)} cannot be null for operations using {nameof(EncryptionContainer)}."); } streamPayload = await EncryptionProcessor.EncryptAsync( streamPayload, this.encryptor, encryptionItemRequestOptions.EncryptionOptions, diagnosticsContext, cancellationToken); ResponseMessage responseMessage = await this.container.ReplaceItemStreamAsync(streamPayload, id, partitionKey, requestOptions, cancellationToken); responseMessage.Content = await this.DecryptResponseAsync( responseMessage.Content, encryptionItemRequestOptions.DecryptionResultHandler, diagnosticsContext, cancellationToken); return(responseMessage); } else { return(await this.container.ReplaceItemStreamAsync(streamPayload, id, partitionKey, requestOptions, cancellationToken)); } } }
public EncryptionTransactionalBatch( TransactionalBatch transactionalBatch, EncryptionProcessor encryptionProcessor, CosmosSerializer cosmosSerializer) { this.transactionalBatch = transactionalBatch ?? throw new ArgumentNullException(nameof(transactionalBatch)); this.encryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); this.cosmosSerializer = cosmosSerializer ?? throw new ArgumentNullException(nameof(cosmosSerializer)); }
public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder <T>( string processorName, ChangesHandler <T> onChangesDelegate) { CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(null); using (diagnosticsContext.CreateScope("GetChangeFeedProcessorBuilder")) { return(this.container.GetChangeFeedProcessorBuilder( processorName, async(IReadOnlyCollection <JObject> documents, CancellationToken cancellationToken) => { List <T> decryptedItems = new List <T>(documents.Count); foreach (JObject document in documents) { EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); try { JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( document, encryptionSettings, diagnosticsContext, cancellationToken); decryptedItems.Add(decryptedDocument.ToObject <T>()); } // we cannot rely currently on a specific exception, this is due to the fact that the run time issue can be variable, // we can hit issue with either Json serialization say an item was not encrypted but the policy shows it as encrypted, // or we could hit a MicrosoftDataEncryptionException from MDE lib etc. catch (Exception) { // most likely the encryption policy has changed. encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( obsoleteEncryptionSettings: encryptionSettings, cancellationToken: cancellationToken); JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( document, encryptionSettings, diagnosticsContext, cancellationToken); decryptedItems.Add(decryptedDocument.ToObject <T>()); } } // Call the original passed in delegate await onChangesDelegate(decryptedItems, cancellationToken); })); } }
private async Task <Stream> DeserializeAndDecryptResponseAsync( Stream content, EncryptionSettings encryptionSettings, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (!encryptionSettings.PropertiesToEncrypt.Any()) { return(content); } JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(content); JArray results = new JArray(); if (!(contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is JArray documents)) { throw new InvalidOperationException("Feed Response body contract was violated. Feed response did not have an array of Documents. "); } foreach (JToken value in documents) { if (value is not JObject document) { results.Add(value); continue; } JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( document, encryptionSettings, diagnosticsContext, cancellationToken); results.Add(decryptedDocument); } JObject decryptedResponse = new JObject(); foreach (JProperty property in contentJObj.Properties()) { if (property.Name.Equals(Constants.DocumentsResourcePropertyName)) { decryptedResponse.Add(property.Name, (JToken)results); } else { decryptedResponse.Add(property.Name, property.Value); } } return(EncryptionProcessor.BaseSerializer.ToStream(decryptedResponse)); }
/// <remarks> /// If there isn't any data that needs to be decrypted, input stream will be returned without any modification. /// Else input stream will be disposed, and a new stream is returned. /// In case of an exception, input stream won't be disposed, but position will be end of stream. /// </remarks> public static async Task <Stream> DecryptAsync( Stream input, Encryptor encryptor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(input != null); Debug.Assert(input.CanSeek); Debug.Assert(encryptor != null); Debug.Assert(diagnosticsContext != null); JObject itemJObj; using (StreamReader sr = new StreamReader(input, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) { using (JsonTextReader jsonTextReader = new JsonTextReader(sr)) { itemJObj = JsonSerializer.Create().Deserialize <JObject>(jsonTextReader); } } JProperty encryptionPropertiesJProp = itemJObj.Property(Constants.EncryptedInfo); JObject encryptionPropertiesJObj = null; if (encryptionPropertiesJProp != null && encryptionPropertiesJProp.Value != null && encryptionPropertiesJProp.Value.Type == JTokenType.Object) { encryptionPropertiesJObj = (JObject)encryptionPropertiesJProp.Value; } if (encryptionPropertiesJObj == null) { input.Position = 0; return(input); } EncryptionProperties encryptionProperties = encryptionPropertiesJObj.ToObject <EncryptionProperties>(); JObject plainTextJObj = await EncryptionProcessor.DecryptContentAsync( encryptionProperties, encryptor, diagnosticsContext, cancellationToken); foreach (JProperty property in plainTextJObj.Properties()) { itemJObj.Add(property.Name, property.Value); } itemJObj.Remove(Constants.EncryptedInfo); input.Dispose(); return(EncryptionProcessor.baseSerializer.ToStream(itemJObj)); }
private async Task <ResponseMessage> ReplaceItemHelperAsync( Stream streamPayload, string id, PartitionKey partitionKey, ItemRequestOptions requestOptions, bool decryptResponse, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (!(requestOptions is EncryptionItemRequestOptions encryptionItemRequestOptions) || encryptionItemRequestOptions.EncryptionOptions == null) { return(await this.container.ReplaceItemStreamAsync( streamPayload, id, partitionKey, requestOptions, cancellationToken)); } if (partitionKey == null) { throw new NotSupportedException($"{nameof(partitionKey)} cannot be null for operations using {nameof(EncryptionContainer)}."); } streamPayload = await EncryptionProcessor.EncryptAsync( streamPayload, this.Encryptor, encryptionItemRequestOptions.EncryptionOptions, diagnosticsContext, cancellationToken); ResponseMessage responseMessage = await this.container.ReplaceItemStreamAsync( streamPayload, id, partitionKey, requestOptions, cancellationToken); if (decryptResponse) { (responseMessage.Content, _) = await EncryptionProcessor.DecryptAsync( responseMessage.Content, this.Encryptor, diagnosticsContext, cancellationToken); } return(responseMessage); }
public override async Task <ResponseMessage> CreateItemStreamAsync( Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { if (streamPayload == null) { throw new ArgumentNullException(nameof(streamPayload)); } CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); using (diagnosticsContext.CreateScope("CreateItemStream")) { if (requestOptions is EncryptionItemRequestOptions encryptionItemRequestOptions) { streamPayload = await EncryptionProcessor.EncryptAsync( streamPayload, this.encryptor, encryptionItemRequestOptions.EncryptionOptions, diagnosticsContext, cancellationToken); ResponseMessage responseMessage = await this.container.CreateItemStreamAsync( streamPayload, partitionKey, requestOptions, cancellationToken); responseMessage.Content = await this.DecryptResponseAsync( responseMessage.Content, encryptionItemRequestOptions.DecryptionResultHandler, diagnosticsContext, cancellationToken); return(responseMessage); } else { return(await this.container.CreateItemStreamAsync( streamPayload, partitionKey, requestOptions, cancellationToken)); } } }
public static async Task <(JObject, DecryptionContext)> DecryptAsync( JObject document, Encryptor encryptor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(document != null); Debug.Assert(encryptor != null); Debug.Assert(diagnosticsContext != null); if (!document.TryGetValue(Constants.EncryptedInfo, out JToken encryptedInfo)) { return(document, null); } EncryptionProperties encryptionProperties = JsonConvert.DeserializeObject <EncryptionProperties>(encryptedInfo.ToString()); JObject plainTextJObj = await EncryptionProcessor.DecryptContentAsync( encryptionProperties, encryptor, diagnosticsContext, cancellationToken); document.Remove(Constants.EncryptedInfo); List <string> pathsDecrypted = new List <string>(); foreach (JProperty property in plainTextJObj.Properties()) { document.Add(property.Name, property.Value); pathsDecrypted.Add("/" + property.Name); } DecryptionInfo decryptionInfo = new DecryptionInfo( pathsDecrypted, encryptionProperties.DataEncryptionKeyId); DecryptionContext decryptionContext = new DecryptionContext( new List <DecryptionInfo>() { decryptionInfo }); return(document, decryptionContext); }
private async Task <Stream> DecryptResponseAsync( Stream input, Action <DecryptionResult> DecryptionResultHandler, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (input == null) { return(input); } try { return(await EncryptionProcessor.DecryptAsync( input, this.encryptor, diagnosticsContext, cancellationToken)); } catch (Exception exception) { input.Position = 0; if (DecryptionResultHandler == null) { throw; } using (MemoryStream memoryStream = new MemoryStream((int)input.Length)) { input.CopyTo(memoryStream); ArraySegment <byte> encryptedStream; bool wasBufferReturned = memoryStream.TryGetBuffer(out encryptedStream); Debug.Assert(wasBufferReturned); DecryptionResultHandler( DecryptionResult.CreateFailure( encryptedStream, exception)); } input.Position = 0; return(input); } }
private async Task <ResponseMessage> CreateItemHelperAsync( Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions, bool decryptResponse, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (!(requestOptions is EncryptionItemRequestOptions encryptionItemRequestOptions) || encryptionItemRequestOptions.EncryptionOptions == null) { return(await this.container.CreateItemStreamAsync( streamPayload, partitionKey, requestOptions, cancellationToken)); } streamPayload = await EncryptionProcessor.EncryptAsync( streamPayload, this.Encryptor, encryptionItemRequestOptions.EncryptionOptions, diagnosticsContext, cancellationToken); ResponseMessage responseMessage = await this.container.CreateItemStreamAsync( streamPayload, partitionKey, requestOptions, cancellationToken); if (decryptResponse) { (responseMessage.Content, _) = await EncryptionProcessor.DecryptAsync( responseMessage.Content, this.Encryptor, diagnosticsContext, cancellationToken); } return(responseMessage); }
internal static async Task <Stream> DeserializeAndDecryptResponseAsync( Stream content, EncryptionSettings encryptionSettings, EncryptionDiagnosticsContext operationDiagnostics, CancellationToken cancellationToken) { if (!encryptionSettings.PropertiesToEncrypt.Any()) { return(content); } operationDiagnostics?.Begin(Constants.DiagnosticsDecryptOperation); JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(content); if (!(contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is JArray documents)) { throw new InvalidOperationException("Feed Response body contract was violated. Feed response did not have an array of Documents. "); } int totalPropertiesDecryptedCount = 0; foreach (JToken value in documents) { if (value is not JObject document) { continue; } (_, int propertiesDecrypted) = await EncryptionProcessor.DecryptAsync( document, encryptionSettings, cancellationToken); totalPropertiesDecryptedCount += propertiesDecrypted; } operationDiagnostics?.End(totalPropertiesDecryptedCount); // the contents get decrypted in place by DecryptAsync. return(EncryptionProcessor.BaseSerializer.ToStream(contentJObj)); }
public override async Task <ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default) { EncryptionSettings encryptionSettings = await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings : null, cancellationToken : cancellationToken); encryptionSettings.SetRequestHeaders(this.requestOptions); ResponseMessage responseMessage = await this.feedIterator.ReadNextAsync(cancellationToken); // check for Bad Request and Wrong RID intended and update the cached RID and Client Encryption Policy. if (responseMessage.StatusCode == HttpStatusCode.BadRequest && string.Equals(responseMessage.Headers.Get(Constants.SubStatusHeader), Constants.IncorrectContainerRidSubStatus)) { await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync( obsoleteEncryptionSettings : encryptionSettings, cancellationToken : cancellationToken); throw new CosmosException( "Operation has failed due to a possible mismatch in Client Encryption Policy configured on the container. Please refer to https://aka.ms/CosmosClientEncryption for more details. " + responseMessage.ErrorMessage, responseMessage.StatusCode, int.Parse(Constants.IncorrectContainerRidSubStatus), responseMessage.Headers.ActivityId, responseMessage.Headers.RequestCharge); } if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null) { EncryptionDiagnosticsContext decryptDiagnostics = new EncryptionDiagnosticsContext(); Stream decryptedContent = await EncryptionProcessor.DeserializeAndDecryptResponseAsync( responseMessage.Content, encryptionSettings, decryptDiagnostics, cancellationToken); decryptDiagnostics.AddEncryptionDiagnosticsToResponseMessage(responseMessage); return(new DecryptedResponseMessage(responseMessage, decryptedContent)); } return(responseMessage); }
/// <summary> /// Gets a QueryDefinition with Encrypted Parameters. /// </summary> /// <param name="queryDefinition"> Query Definition to be replaced with Encrypted Values.</param> /// <param name="name"> Query Paramerter Name. </param> /// <param name="value"> Query Paramerter Value.</param> /// <param name="path"> Encrypted Property Path. </param> /// <param name="cancellationToken"> cancellation token </param> /// <returns> QueryDefinition with encrypted parameters. </returns> /// <example> /// This example shows how to pass in a QueryDefinition with Encryption support to AddParameterAsync /// to encrypt the required Property for running Query on encrypted data. /// /// <code language="c#"> /// <![CDATA[ /// containerWithEncryption = await this.cosmosDatabase.GetContainer("id").InitializeEncryptionAsync(); /// QueryDefinition withEncryptedParameter = containerWithEncryption.CreateQueryDefinition( /// "SELECT * FROM c where c.PropertyName = @PropertyValue"); /// await withEncryptedParameter.AddParameterAsync( /// "@PropertyName", /// PropertyValue, /// "/PropertyName"); /// ]]> /// </code> /// </example> public static async Task <QueryDefinition> AddParameterAsync( this QueryDefinition queryDefinition, string name, object value, string path, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (queryDefinition == null) { throw new ArgumentNullException(nameof(queryDefinition)); } if (string.IsNullOrWhiteSpace(path) || path[0] != '/' || path.LastIndexOf('/') != 0) { throw new InvalidOperationException($"Invalid path {path ?? string.Empty}, {nameof(path)}. "); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } QueryDefinition queryDefinitionwithEncryptedValues = queryDefinition; if (queryDefinition is EncryptionQueryDefinition encryptionQueryDefinition) { EncryptionContainer encryptionContainer = (EncryptionContainer)encryptionQueryDefinition.Container; // get the path's encryption setting. EncryptionSettings encryptionSettings = await encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings : null, cancellationToken : cancellationToken); EncryptionSettingForProperty settingsForProperty = encryptionSettings.GetEncryptionSettingForProperty(path.Substring(1)); if (settingsForProperty == null) { // property not encrypted. queryDefinitionwithEncryptedValues.WithParameter(name, value); return(queryDefinitionwithEncryptedValues); } if (settingsForProperty.EncryptionType == EncryptionType.Randomized) { throw new ArgumentException($"Unsupported argument with Path: {path} for query. For executing queries on encrypted path requires the use of deterministic encryption type. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } Stream valueStream = encryptionContainer.CosmosSerializer.ToStream(value); Stream encryptedValueStream = await EncryptionProcessor.EncryptValueStreamAsync(valueStream, settingsForProperty, cancellationToken); queryDefinitionwithEncryptedValues.WithParameterStream(name, encryptedValueStream); return(queryDefinitionwithEncryptedValues); } else { throw new ArgumentException("Executing queries on encrypted path requires the use of an encryption - enabled client. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } }
private async Task <Stream> DeserializeAndDecryptResponseAsync( Stream content, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(content); JArray result = new JArray(); if (!(contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is JArray documents)) { throw new InvalidOperationException("Feed response Body Contract was violated. Feed response did not have an array of Documents"); } foreach (JToken value in documents) { if (!(value is JObject document)) { result.Add(value); continue; } try { JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( document, this.encryptor, diagnosticsContext, cancellationToken); result.Add(decryptedDocument); } catch (Exception exception) { if (this.decryptionResultHandler == null) { throw; } result.Add(document); MemoryStream memoryStream = EncryptionProcessor.BaseSerializer.ToStream(document); Debug.Assert(memoryStream != null); bool wasBufferReturned = memoryStream.TryGetBuffer(out ArraySegment <byte> encryptedStream); Debug.Assert(wasBufferReturned); this.decryptionResultHandler( DecryptionResult.CreateFailure( encryptedStream, exception)); } } JObject decryptedResponse = new JObject(); foreach (JProperty property in contentJObj.Properties()) { if (property.Name.Equals(Constants.DocumentsResourcePropertyName)) { decryptedResponse.Add(property.Name, (JToken)result); } else { decryptedResponse.Add(property.Name, property.Value); } } return(EncryptionProcessor.BaseSerializer.ToStream(decryptedResponse)); }
/// <summary> /// Gets a QueryDefinition with Encrypted Parameters. /// </summary> /// <param name="queryDefinition"> Query Definition to be replaced with Encrypted Values.</param> /// <param name="name"> Query Paramerter Name. </param> /// <param name="value"> Query Paramerter Value.</param> /// <param name="path"> Encrypted Property Path. </param> /// <param name="cancellationToken"> cancellation token </param> /// <returns> QueryDefinition with encrypted parameters. </returns> /// <example> /// This example shows how to pass in a QueryDefinition with Encryption support to AddParameterAsync /// to encrypt the required Property for running Query on encrypted data. /// /// <code language="c#"> /// <![CDATA[ /// containerWithEncryption = await this.cosmosDatabase.GetContainer("id").InitializeEncryptionAsync(); /// QueryDefinition withEncryptedParameter = containerWithEncryption.CreateQueryDefinition( /// "SELECT * FROM c where c.PropertyName = @PropertyValue"); /// await withEncryptedParameter.AddParameterAsync( /// "@PropertyName", /// PropertyValue, /// "/PropertyName"); /// ]]> /// </code> /// </example> public static async Task <QueryDefinition> AddParameterAsync( this QueryDefinition queryDefinition, string name, object value, string path, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (queryDefinition == null) { throw new ArgumentNullException(nameof(queryDefinition)); } if (string.IsNullOrWhiteSpace(path) || path[0] != '/' || path.LastIndexOf('/') != 0) { throw new InvalidOperationException($"Invalid path {path ?? string.Empty}, {nameof(path)}. "); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } QueryDefinition queryDefinitionwithEncryptedValues = queryDefinition; if (queryDefinition is EncryptionQueryDefinition encryptionQueryDefinition) { EncryptionContainer encryptionContainer = (EncryptionContainer)encryptionQueryDefinition.Container; Stream valueStream = encryptionContainer.CosmosSerializer.ToStream(value); // not really required, but will have things setup for subsequent queries or operations on this Container if the Container was never init // or if this was the first operation carried out on this container. await encryptionContainer.EncryptionProcessor.InitEncryptionSettingsIfNotInitializedAsync(cancellationToken); // get the path's encryption setting. EncryptionSettingForProperty settingsForProperty = await encryptionContainer.EncryptionProcessor.EncryptionSettings.GetEncryptionSettingForPropertyAsync( path.Substring(1), cancellationToken); if (settingsForProperty == null) { // property not encrypted. queryDefinitionwithEncryptedValues.WithParameter(name, value); return(queryDefinitionwithEncryptedValues); } if (settingsForProperty.EncryptionType == EncryptionType.Randomized) { throw new ArgumentException($"Unsupported argument with Path: {path} for query. For executing queries on encrypted path requires the use of deterministic encryption type. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingsForProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken : cancellationToken); JToken propertyValueToEncrypt = EncryptionProcessor.BaseSerializer.FromStream <JToken>(valueStream); (EncryptionProcessor.TypeMarker typeMarker, byte[] serializedData) = EncryptionProcessor.Serialize(propertyValueToEncrypt); byte[] cipherText = aeadAes256CbcHmac256EncryptionAlgorithm.Encrypt(serializedData); if (cipherText == null) { throw new InvalidOperationException($"{nameof(AddParameterAsync)} returned null cipherText from {nameof(aeadAes256CbcHmac256EncryptionAlgorithm.Encrypt)}. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } byte[] cipherTextWithTypeMarker = new byte[cipherText.Length + 1]; cipherTextWithTypeMarker[0] = (byte)typeMarker; Buffer.BlockCopy(cipherText, 0, cipherTextWithTypeMarker, 1, cipherText.Length); queryDefinitionwithEncryptedValues.WithParameter(name, cipherTextWithTypeMarker); return(queryDefinitionwithEncryptedValues); } else { throw new ArgumentException("Executing queries on encrypted path requires the use of an encryption - enabled client. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } }
public EncryptionSettingForProperty(string clientEncryptionKeyId, EncryptionType encryptionType, EncryptionProcessor encryptionProcessor) { this.ClientEncryptionKeyId = clientEncryptionKeyId ?? throw new ArgumentNullException(nameof(clientEncryptionKeyId)); this.EncryptionType = encryptionType; this.EncryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); }
public EncryptionSettings(EncryptionProcessor encryptionProcessor) { this.EncryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); }
private async Task <CachedEncryptionSettings> FetchCachedEncryptionSettingsAsync( string propertyName, EncryptionProcessor encryptionProcessor, CancellationToken cancellationToken) { ClientEncryptionPolicy clientEncryptionPolicy = await encryptionProcessor.EncryptionCosmosClient.GetClientEncryptionPolicyAsync( encryptionProcessor.Container, cancellationToken, false); if (clientEncryptionPolicy != null) { foreach (ClientEncryptionIncludedPath propertyToEncrypt in clientEncryptionPolicy.IncludedPaths) { if (string.Equals(propertyToEncrypt.Path.Substring(1), propertyName)) { ClientEncryptionKeyProperties clientEncryptionKeyProperties = await encryptionProcessor.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( clientEncryptionKeyId : propertyToEncrypt.ClientEncryptionKeyId, container : encryptionProcessor.Container, cancellationToken : cancellationToken, shouldForceRefresh : false); ProtectedDataEncryptionKey protectedDataEncryptionKey = null; try { protectedDataEncryptionKey = this.BuildProtectedDataEncryptionKey( clientEncryptionKeyProperties, encryptionProcessor.EncryptionKeyStoreProvider, propertyToEncrypt.ClientEncryptionKeyId); } catch (RequestFailedException ex) { // the key was revoked. Try to fetch the latest EncryptionKeyProperties from the backend. // This should succeed provided the user has rewraped the key with right set of meta data. if (ex.Status == (int)HttpStatusCode.Forbidden) { clientEncryptionKeyProperties = await encryptionProcessor.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( clientEncryptionKeyId : propertyToEncrypt.ClientEncryptionKeyId, container : encryptionProcessor.Container, cancellationToken : cancellationToken, shouldForceRefresh : true); protectedDataEncryptionKey = this.BuildProtectedDataEncryptionKey( clientEncryptionKeyProperties, encryptionProcessor.EncryptionKeyStoreProvider, propertyToEncrypt.ClientEncryptionKeyId); } else { throw; } } EncryptionSettings encryptionSettings = new EncryptionSettings { EncryptionSettingTimeToLive = DateTime.UtcNow + TimeSpan.FromMinutes(Constants.CachedEncryptionSettingsDefaultTTLInMinutes), ClientEncryptionKeyId = propertyToEncrypt.ClientEncryptionKeyId, DataEncryptionKey = protectedDataEncryptionKey, }; EncryptionType encryptionType = EncryptionType.Plaintext; switch (propertyToEncrypt.EncryptionType) { case CosmosEncryptionType.Deterministic: encryptionType = EncryptionType.Deterministic; break; case CosmosEncryptionType.Randomized: encryptionType = EncryptionType.Randomized; break; case CosmosEncryptionType.Plaintext: encryptionType = EncryptionType.Plaintext; break; default: throw new ArgumentException($"Invalid encryption type {propertyToEncrypt.EncryptionType}. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } encryptionSettings = EncryptionSettings.Create(encryptionSettings, encryptionType); return(new CachedEncryptionSettings(encryptionSettings, encryptionSettings.EncryptionSettingTimeToLive)); } } } return(null); }