internal static async Task <Stream> EncryptValueStreamAsync( Stream valueStream, EncryptionSettingForProperty settingsForProperty, CancellationToken cancellationToken) { if (valueStream == null) { throw new ArgumentNullException(nameof(valueStream)); } if (settingsForProperty == null) { throw new ArgumentNullException(nameof(settingsForProperty)); } 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(EncryptValueStreamAsync)} 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); return(EncryptionProcessor.BaseSerializer.ToStream(cipherTextWithTypeMarker)); }
internal static async Task <Stream> EncryptValueStreamAsync( Stream valueStream, EncryptionSettingForProperty settingsForProperty, CancellationToken cancellationToken) { if (valueStream == null) { throw new ArgumentNullException(nameof(valueStream)); } if (settingsForProperty == null) { throw new ArgumentNullException(nameof(settingsForProperty)); } JToken propertyValueToEncrypt = EncryptionProcessor.BaseSerializer.FromStream <JToken>(valueStream); JToken encryptedPropertyValue = propertyValueToEncrypt; if (propertyValueToEncrypt.Type == JTokenType.Object || propertyValueToEncrypt.Type == JTokenType.Array) { await EncryptJTokenAsync(encryptedPropertyValue, settingsForProperty, cancellationToken); } else { encryptedPropertyValue = await SerializeAndEncryptValueAsync(propertyValueToEncrypt, settingsForProperty, cancellationToken); } return(EncryptionProcessor.BaseSerializer.ToStream(encryptedPropertyValue)); }
private async Task DecryptObjectAsync( JObject document, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(diagnosticsContext != null); foreach (ClientEncryptionIncludedPath path in this.ClientEncryptionPolicy.IncludedPaths) { if (document.TryGetValue(path.Path.Substring(1), out JToken propertyValue)) { string propertyName = path.Path.Substring(1); EncryptionSettingForProperty settingsForProperty = await this.EncryptionSettings.GetEncryptionSettingForPropertyAsync(propertyName, cancellationToken); if (settingsForProperty == null) { throw new ArgumentException($"Invalid Encryption Setting for Property:{propertyName}. "); } AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingsForProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken : cancellationToken); this.DecryptProperty( document, aeadAes256CbcHmac256EncryptionAlgorithm, propertyName, propertyValue); } } return; }
/// <remarks> /// If there isn't any PathsToEncrypt, 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 async Task <Stream> EncryptAsync( Stream input, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (input == null) { throw new ArgumentNullException(nameof(input)); } Debug.Assert(diagnosticsContext != null); await this.InitEncryptionSettingsIfNotInitializedAsync(cancellationToken); if (this.ClientEncryptionPolicy == null) { return(input); } JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(input); foreach (ClientEncryptionIncludedPath pathToEncrypt in this.ClientEncryptionPolicy.IncludedPaths) { string propertyName = pathToEncrypt.Path.Substring(1); // possibly a wrong path configured in the Client Encryption Policy, ignore. if (!itemJObj.TryGetValue(propertyName, out JToken propertyValue)) { continue; } EncryptionSettingForProperty settingforProperty = await this.EncryptionSettings.GetEncryptionSettingForPropertyAsync(propertyName, cancellationToken); if (settingforProperty == null) { throw new ArgumentException($"Invalid Encryption Setting for the Property:{propertyName}. "); } AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingforProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken : cancellationToken); this.EncryptProperty( itemJObj, propertyValue, aeadAes256CbcHmac256EncryptionAlgorithm); } input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj)); }
/// <remarks> /// If there isn't any PathsToEncrypt, 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> EncryptAsync( Stream input, EncryptionSettings encryptionSettings, EncryptionDiagnosticsContext operationDiagnostics, CancellationToken cancellationToken) { if (input == null) { throw new ArgumentNullException(nameof(input)); } operationDiagnostics?.Begin(Constants.DiagnosticsEncryptOperation); int propertiesEncryptedCount = 0; JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(input); foreach (string propertyName in encryptionSettings.PropertiesToEncrypt) { // possibly a wrong path configured in the Client Encryption Policy, ignore. JProperty propertyToEncrypt = itemJObj.Property(propertyName); if (propertyToEncrypt == null) { continue; } EncryptionSettingForProperty settingforProperty = encryptionSettings.GetEncryptionSettingForProperty(propertyName); if (settingforProperty == null) { throw new ArgumentException($"Invalid Encryption Setting for the Property:{propertyName}. "); } await EncryptJTokenAsync( propertyToEncrypt.Value, settingforProperty, propertyName == "id", cancellationToken); propertiesEncryptedCount++; } Stream result = EncryptionProcessor.BaseSerializer.ToStream(itemJObj); input.Dispose(); operationDiagnostics?.End(propertiesEncryptedCount); return(result); }
internal async Task <EncryptionSettingForProperty> GetEncryptionSettingForPropertyAsync( string propertyName, CancellationToken cancellationToken) { EncryptionSettingForProperty encryptionSettingsForProperty = await this.EncryptionSettingCacheByPropertyName.GetAsync( propertyName, obsoleteValue : null, async() => await this.FetchEncryptionSettingForPropertyAsync(propertyName, cancellationToken), cancellationToken); if (encryptionSettingsForProperty == null) { return(null); } return(encryptionSettingsForProperty); }
/// <remarks> /// If there isn't any PathsToEncrypt, 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> EncryptAsync( Stream input, EncryptionSettings encryptionSettings, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (input == null) { throw new ArgumentNullException(nameof(input)); } Debug.Assert(diagnosticsContext != null); JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(input); foreach (string propertyName in encryptionSettings.PropertiesToEncrypt) { // possibly a wrong path configured in the Client Encryption Policy, ignore. if (!itemJObj.TryGetValue(propertyName, out JToken propertyValue)) { continue; } EncryptionSettingForProperty settingforProperty = encryptionSettings.GetEncryptionSettingForProperty(propertyName); if (settingforProperty == null) { throw new ArgumentException($"Invalid Encryption Setting for the Property:{propertyName}. "); } AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingforProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken : cancellationToken); EncryptProperty( itemJObj, propertyValue, aeadAes256CbcHmac256EncryptionAlgorithm); } input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj)); }
/// <summary> /// Builds up and caches the Encryption Setting by getting the cached entries of Client Encryption Policy and the corresponding keys. /// Sets up the MDE Algorithm for encryption and decryption by initializing the KeyEncryptionKey and ProtectedDataEncryptionKey. /// </summary> /// <param name="cancellationToken"> cancellation token </param> /// <returns> Task </returns> internal async Task InitializeEncryptionSettingsAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (this.isEncryptionSettingsInitDone) { throw new InvalidOperationException("The Encrypton Processor has already been initialized. "); } // fetch the cached policy. this.ClientEncryptionPolicy = await this.EncryptionCosmosClient.GetClientEncryptionPolicyAsync( container : this.Container, cancellationToken : cancellationToken, shouldForceRefresh : false); // no policy was configured. if (this.ClientEncryptionPolicy == null) { this.isEncryptionSettingsInitDone = true; return; } // update the property level setting. foreach (ClientEncryptionIncludedPath propertyToEncrypt in this.ClientEncryptionPolicy.IncludedPaths) { EncryptionType encryptionType = this.EncryptionSettings.GetEncryptionTypeForProperty(propertyToEncrypt); EncryptionSettingForProperty encryptionSettingsForProperty = new EncryptionSettingForProperty( propertyToEncrypt.ClientEncryptionKeyId, encryptionType, this); string propertyName = propertyToEncrypt.Path.Substring(1); this.EncryptionSettings.SetEncryptionSettingForProperty( propertyName, encryptionSettingsForProperty); } this.isEncryptionSettingsInitDone = true; }
/// <remarks> /// If there isn't any PathsToEncrypt, 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> EncryptAsync( Stream input, EncryptionSettings encryptionSettings, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (input == null) { throw new ArgumentNullException(nameof(input)); } Debug.Assert(diagnosticsContext != null); JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(input); foreach (string propertyName in encryptionSettings.PropertiesToEncrypt) { // possibly a wrong path configured in the Client Encryption Policy, ignore. JProperty propertyToEncrypt = itemJObj.Property(propertyName); if (propertyToEncrypt == null) { continue; } EncryptionSettingForProperty settingforProperty = encryptionSettings.GetEncryptionSettingForProperty(propertyName); if (settingforProperty == null) { throw new ArgumentException($"Invalid Encryption Setting for the Property:{propertyName}. "); } await EncryptJTokenAsync( propertyToEncrypt.Value, settingforProperty, cancellationToken); } input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj)); }
/// <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. "); } }
internal void SetEncryptionSettingForProperty( string propertyName, EncryptionSettingForProperty encryptionSettingsForProperty) { this.EncryptionSettingCacheByPropertyName.Set(propertyName, encryptionSettingsForProperty); }
/// <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. "); } }