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);
            }
        }
Esempio n. 3
0
        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);
        }
Esempio n. 5
0
        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);
        }
Esempio n. 6
0
 public EncryptionFeedIterator(
     FeedIterator feedIterator,
     EncryptionProcessor encryptionProcessor)
 {
     this.feedIterator        = feedIterator ?? throw new ArgumentNullException(nameof(feedIterator));
     this.encryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor));
 }
Esempio n. 7
0
        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);
        }
Esempio n. 9
0
        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);
        }
Esempio n. 10
0
        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));
                }
            }
        }
Esempio n. 11
0
 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);
        }
Esempio n. 16
0
        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);
        }
Esempio n. 18
0
        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));
        }
Esempio n. 21
0
        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);
        }
Esempio n. 22
0
        /// <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. ");
            }
        }
Esempio n. 23
0
        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));
        }
Esempio n. 24
0
        /// <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. ");
            }
        }
Esempio n. 25
0
 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));
 }
Esempio n. 26
0
 public EncryptionSettings(EncryptionProcessor encryptionProcessor)
 {
     this.EncryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor));
 }
Esempio n. 27
0
        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);
        }