Beispiel #1
0
        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));
            }
        }
Beispiel #2
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));
        }