private static void ValidateInputForEncrypt( Stream input, Encryptor encryptor, EncryptionOptions encryptionOptions) { 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)); } }
/// <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)); }