/// <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, Encryptor encryptor, EncryptionOptions encryptionOptions, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(diagnosticsContext != null); if (input == null) { throw new ArgumentNullException(nameof(input)); } if (encryptor == null) { throw new ArgumentNullException(nameof(encryptor)); } if (encryptionOptions == null) { throw new ArgumentNullException(nameof(encryptionOptions)); } if (string.IsNullOrWhiteSpace(encryptionOptions.DataEncryptionKeyId)) { throw new ArgumentNullException(nameof(encryptionOptions.DataEncryptionKeyId)); } if (string.IsNullOrWhiteSpace(encryptionOptions.EncryptionAlgorithm)) { throw new ArgumentNullException(nameof(encryptionOptions.EncryptionAlgorithm)); } if (encryptionOptions.PathsToEncrypt == null) { throw new ArgumentNullException(nameof(encryptionOptions.PathsToEncrypt)); } if (!encryptionOptions.PathsToEncrypt.Any()) { return(input); } foreach (string path in encryptionOptions.PathsToEncrypt) { if (string.IsNullOrWhiteSpace(path) || path[0] != '/' || path.LastIndexOf('/') != 0) { throw new ArgumentException($"Invalid path {path ?? string.Empty}", nameof(encryptionOptions.PathsToEncrypt)); } } JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(input); JObject toEncryptJObj = new JObject(); foreach (string pathToEncrypt in encryptionOptions.PathsToEncrypt) { string propertyName = pathToEncrypt.Substring(1); if (!itemJObj.TryGetValue(propertyName, out JToken propertyValue)) { throw new ArgumentException($"{nameof(encryptionOptions.PathsToEncrypt)} includes a path: '{pathToEncrypt}' which was not found."); } toEncryptJObj.Add(propertyName, propertyValue.Value <JToken>()); itemJObj.Remove(propertyName); } MemoryStream memoryStream = EncryptionProcessor.BaseSerializer.ToStream <JObject>(toEncryptJObj); Debug.Assert(memoryStream != null); Debug.Assert(memoryStream.TryGetBuffer(out _)); byte[] plainText = memoryStream.ToArray(); byte[] cipherText = await encryptor.EncryptAsync( plainText, encryptionOptions.DataEncryptionKeyId, encryptionOptions.EncryptionAlgorithm, cancellationToken); if (cipherText == null) { throw new InvalidOperationException($"{nameof(Encryptor)} returned null cipherText from {nameof(EncryptAsync)}."); } EncryptionProperties encryptionProperties = new EncryptionProperties( encryptionFormatVersion: 2, encryptionOptions.EncryptionAlgorithm, encryptionOptions.DataEncryptionKeyId, encryptedData: cipherText); itemJObj.Add(Constants.EncryptedInfo, JObject.FromObject(encryptionProperties)); input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj)); }
/// <summary> /// Populates the DecryptableItem that can be used getting the decryption result. /// </summary> /// <param name="decryptableContent">The encrypted content which is yet to be decrypted.</param> /// <param name="encryptor">Encryptor instance which will be used for decryption.</param> /// <param name="cosmosSerializer">Serializer instance which will be used for deserializing the content after decryption.</param> protected internal abstract void SetDecryptableItem( JToken decryptableContent, Encryptor encryptor, CosmosSerializer cosmosSerializer);
/// <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, Encryptor encryptor, List <EncryptionOptions> propertyEncryptionOptions, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(diagnosticsContext != null); if (input == null) { throw new ArgumentNullException(nameof(input)); } if (encryptor == null) { throw new ArgumentNullException(nameof(encryptor)); } JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(input); foreach (EncryptionOptions encryptionOptions in propertyEncryptionOptions) { if (encryptionOptions == null) { throw new ArgumentNullException(nameof(encryptionOptions)); } if (string.IsNullOrWhiteSpace(encryptionOptions.DataEncryptionKeyId)) { throw new ArgumentNullException(nameof(encryptionOptions.DataEncryptionKeyId)); } if (string.IsNullOrWhiteSpace(encryptionOptions.EncryptionAlgorithm)) { throw new ArgumentNullException(nameof(encryptionOptions.EncryptionAlgorithm)); } if (encryptionOptions.PathsToEncrypt == null) { throw new ArgumentNullException(nameof(encryptionOptions.PathsToEncrypt)); } if (!encryptionOptions.PathsToEncrypt.Any()) { return(input); } foreach (string path in encryptionOptions.PathsToEncrypt) { if (string.IsNullOrWhiteSpace(path) || path[0] != '/' || path.LastIndexOf('/') != 0) { throw new ArgumentException($"Invalid path {path ?? string.Empty}", nameof(encryptionOptions.PathsToEncrypt)); } } if (itemJObj != null) { foreach (string pathToEncrypt in encryptionOptions.PathsToEncrypt) { string propertyName = pathToEncrypt.Substring(1); if (!itemJObj.TryGetValue(propertyName, out JToken propertyValue)) { throw new ArgumentException($"{nameof(encryptionOptions.PathsToEncrypt)} includes a path: '{pathToEncrypt}' which was not found."); } string value = propertyValue.Value <string>(); byte[] plainText = System.Text.Encoding.UTF8.GetBytes(value); byte[] cipherText = await encryptor.EncryptAsync( plainText, encryptionOptions.DataEncryptionKeyId, encryptionOptions.EncryptionAlgorithm, cancellationToken); if (cipherText == null) { throw new InvalidOperationException($"{nameof(Encryptor)} returned null cipherText from {nameof(EncryptAsync)}."); } itemJObj[propertyName] = cipherText; } } } input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj)); }
/// <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, DecryptionContext)> DecryptAsync( Stream input, Encryptor encryptor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (input == null) { return(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, null); } EncryptionProperties encryptionProperties = encryptionPropertiesJObj.ToObject <EncryptionProperties>(); JObject plainTextJObj = await EncryptionProcessor.DecryptContentAsync( encryptionProperties, encryptor, diagnosticsContext, cancellationToken); List <string> pathsDecrypted = new List <string>(); foreach (JProperty property in plainTextJObj.Properties()) { itemJObj.Add(property.Name, property.Value); pathsDecrypted.Add("/" + property.Name); } DecryptionInfo decryptionInfo = new DecryptionInfo( pathsDecrypted, encryptionProperties.DataEncryptionKeyId); DecryptionContext decryptionContext = new DecryptionContext( new List <DecryptionInfo>() { decryptionInfo }); itemJObj.Remove(Constants.EncryptedInfo); input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj), decryptionContext); }
/// <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, IReadOnlyDictionary <List <string>, string> pathsToEncrypt, 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); } if (pathsToEncrypt != null) { foreach (List <string> paths in pathsToEncrypt.Keys) { foreach (string path in paths) { if (itemJObj.TryGetValue(path.Substring(1), out JToken propertyValue)) { EncryptionProperties encryptionProperties = new EncryptionProperties( encryptionFormatVersion: 2, CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, pathsToEncrypt[paths], propertyValue.ToObject <byte[]>(), path); JObject propPlainTextJObj = await PropertyEncryptionProcessor.DecryptContentAsync( encryptionProperties, encryptor, diagnosticsContext, cancellationToken); foreach (JProperty property in propPlainTextJObj.Properties()) { itemJObj[property.Name] = property.Value; } } } input.Dispose(); } } JToken token = itemJObj[Constants.EncryptedInfo]; if (token != null) { 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 PropertyEncryptionProcessor.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)); }