Пример #1
0
        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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        /// <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);
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        /// <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));
        }