private static async Task <byte[]> MdeEncAlgoDecryptPropertyAsync( EncryptionProperties encryptionProperties, byte[] cipherText, Encryptor encryptor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (encryptionProperties.EncryptionFormatVersion != 3) { throw new NotSupportedException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); } byte[] plainText = await encryptor.DecryptAsync( cipherText, encryptionProperties.DataEncryptionKeyId, encryptionProperties.EncryptionAlgorithm, cancellationToken); if (plainText == null) { throw new InvalidOperationException($"{nameof(Encryptor)} returned null plainText from {nameof(DecryptAsync)}."); } return(plainText); }
private static async Task <DecryptionContext> MdeEncAlgoDecryptObjectAsync( JObject document, Encryptor encryptor, EncryptionProperties encryptionProperties, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { JObject plainTextJObj = new JObject(); foreach (string path in encryptionProperties.EncryptedPaths) { string propertyName = path.Substring(1); if (!document.TryGetValue(propertyName, out JToken propertyValue)) { throw new InvalidOperationException($"{nameof(encryptionProperties.EncryptedPaths)} includes a path: '{path}' which was not found."); } byte[] cipherTextWithTypeMarker = propertyValue.ToObject <byte[]>(); if (cipherTextWithTypeMarker == null) { continue; } byte[] cipherText = new byte[cipherTextWithTypeMarker.Length - 1]; Buffer.BlockCopy(cipherTextWithTypeMarker, 1, cipherText, 0, cipherTextWithTypeMarker.Length - 1); byte[] plainText = await EncryptionProcessor.MdeEncAlgoDecryptPropertyAsync( encryptionProperties, cipherText, encryptor, diagnosticsContext, cancellationToken); EncryptionProcessor.DeserializeAndAddProperty( (TypeMarker)cipherTextWithTypeMarker[0], plainText, plainTextJObj, propertyName); } List <string> pathsDecrypted = new List <string>(); foreach (JProperty property in plainTextJObj.Properties()) { document[property.Name] = property.Value; pathsDecrypted.Add("/" + property.Name); } DecryptionContext decryptionContext = EncryptionProcessor.CreateDecryptionContext( pathsDecrypted, encryptionProperties.DataEncryptionKeyId); document.Remove(Constants.EncryptedInfo); return(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, 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 = EncryptionProcessor.RetrieveItem(input); JObject encryptionPropertiesJObj = EncryptionProcessor.RetrieveEncryptionProperties(itemJObj); if (encryptionPropertiesJObj == null) { input.Position = 0; return(input, null); } EncryptionProperties encryptionProperties = encryptionPropertiesJObj.ToObject <EncryptionProperties>(); DecryptionContext decryptionContext; switch (encryptionProperties.EncryptionAlgorithm) { case CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized: decryptionContext = await EncryptionProcessor.MdeEncAlgoDecryptObjectAsync( itemJObj, encryptor, encryptionProperties, diagnosticsContext, cancellationToken); break; case CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized: decryptionContext = await EncryptionProcessor.LegacyEncAlgoDecryptContentAsync( itemJObj, encryptionProperties, encryptor, diagnosticsContext, cancellationToken); break; default: throw new NotSupportedException($"Encryption Algorithm : {encryptionProperties.EncryptionAlgorithm} is not supported."); } input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj), decryptionContext); }
public static async Task <(JObject, DecryptionContext)> DecryptAsync( JObject document, Encryptor encryptor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(document != null); Debug.Assert(encryptor != null); JObject encryptionPropertiesJObj = EncryptionProcessor.RetrieveEncryptionProperties(document); if (encryptionPropertiesJObj == null) { return(document, null); } EncryptionProperties encryptionProperties = encryptionPropertiesJObj.ToObject <EncryptionProperties>(); DecryptionContext decryptionContext; switch (encryptionProperties.EncryptionAlgorithm) { case CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized: decryptionContext = await EncryptionProcessor.MdeEncAlgoDecryptObjectAsync( document, encryptor, encryptionProperties, diagnosticsContext, cancellationToken); break; case CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized: decryptionContext = await EncryptionProcessor.LegacyEncAlgoDecryptContentAsync( document, encryptionProperties, encryptor, diagnosticsContext, cancellationToken); break; default: throw new NotSupportedException($"Encryption Algorithm : {encryptionProperties.EncryptionAlgorithm} is not supported."); } return(document, decryptionContext); }
private static async Task <DecryptionContext> LegacyEncAlgoDecryptContentAsync( JObject document, EncryptionProperties encryptionProperties, Encryptor encryptor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { if (encryptionProperties.EncryptionFormatVersion != 2) { throw new NotSupportedException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); } byte[] plainText = await encryptor.DecryptAsync( encryptionProperties.EncryptedData, encryptionProperties.DataEncryptionKeyId, encryptionProperties.EncryptionAlgorithm, cancellationToken); if (plainText == null) { throw new InvalidOperationException($"{nameof(Encryptor)} returned null plainText from {nameof(DecryptAsync)}."); } JObject plainTextJObj; using (MemoryStream memoryStream = new MemoryStream(plainText)) using (StreamReader streamReader = new StreamReader(memoryStream)) using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { plainTextJObj = JObject.Load(jsonTextReader); } List <string> pathsDecrypted = new List <string>(); foreach (JProperty property in plainTextJObj.Properties()) { document.Add(property.Name, property.Value); pathsDecrypted.Add("/" + property.Name); } DecryptionContext decryptionContext = EncryptionProcessor.CreateDecryptionContext( pathsDecrypted, encryptionProperties.DataEncryptionKeyId); document.Remove(Constants.EncryptedInfo); return(decryptionContext); }
/// <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) { EncryptionProcessor.ValidateInputForEncrypt( input, encryptor, encryptionOptions); if (!encryptionOptions.PathsToEncrypt.Any()) { return(input); } if (!encryptionOptions.PathsToEncrypt.Distinct().SequenceEqual(encryptionOptions.PathsToEncrypt)) { throw new InvalidOperationException("Duplicate paths in PathsToEncrypt passed via EncryptionOptions."); } foreach (string path in encryptionOptions.PathsToEncrypt) { if (string.IsNullOrWhiteSpace(path) || path[0] != '/' || path.LastIndexOf('/') != 0) { throw new InvalidOperationException($"Invalid path {path ?? string.Empty}, {nameof(encryptionOptions.PathsToEncrypt)}"); } if (string.Equals(path.Substring(1), "id")) { throw new InvalidOperationException($"{nameof(encryptionOptions.PathsToEncrypt)} includes a invalid path: '{path}'."); } } JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream <JObject>(input); List <string> pathsEncrypted = new List <string>(); EncryptionProperties encryptionProperties = null; byte[] plainText = null; byte[] cipherText = null; TypeMarker typeMarker; switch (encryptionOptions.EncryptionAlgorithm) { case CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized: 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."); } if (propertyValue.Type == JTokenType.Null) { continue; } (typeMarker, plainText) = EncryptionProcessor.Serialize(propertyValue); cipherText = await encryptor.EncryptAsync( plainText, encryptionOptions.DataEncryptionKeyId, encryptionOptions.EncryptionAlgorithm); if (cipherText == null) { throw new InvalidOperationException($"{nameof(Encryptor)} returned null cipherText from {nameof(EncryptAsync)}."); } byte[] cipherTextWithTypeMarker = new byte[cipherText.Length + 1]; cipherTextWithTypeMarker[0] = (byte)typeMarker; Buffer.BlockCopy(cipherText, 0, cipherTextWithTypeMarker, 1, cipherText.Length); itemJObj[propertyName] = cipherTextWithTypeMarker; pathsEncrypted.Add(pathToEncrypt); } encryptionProperties = new EncryptionProperties( encryptionFormatVersion: 3, encryptionOptions.EncryptionAlgorithm, encryptionOptions.DataEncryptionKeyId, encryptedData: null, pathsEncrypted); break; case CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized: 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 _)); plainText = memoryStream.ToArray(); 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 = new EncryptionProperties( encryptionFormatVersion: 2, encryptionOptions.EncryptionAlgorithm, encryptionOptions.DataEncryptionKeyId, encryptedData: cipherText, encryptionOptions.PathsToEncrypt); break; default: throw new NotSupportedException($"Encryption Algorithm : {encryptionOptions.EncryptionAlgorithm} is not supported."); } itemJObj.Add(Constants.EncryptedInfo, JObject.FromObject(encryptionProperties)); input.Dispose(); return(EncryptionProcessor.BaseSerializer.ToStream(itemJObj)); }