< Summary

Class:Azure.Storage.Cryptography.ClientSideDecryptor
Assembly:Azure.Storage.Blobs
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\ClientsideEncryption\ClientSideDecryptor.cs
Covered lines:0
Uncovered lines:63
Coverable lines:63
Total lines:251
Line coverage:0% (0 of 63)
Covered branches:0
Total branches:26
Branch coverage:0% (0 of 26)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-0%100%
DecryptInternal()-0%0%
DecryptInternalV1_0()-0%0%
GetContentEncryptionKeyAsync()-0%0%
WrapStream(...)-0%0%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\ClientsideEncryption\ClientSideDecryptor.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.IO;
 6using System.Security.Cryptography;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Azure.Core.Cryptography;
 10using Azure.Storage.Cryptography.Models;
 11
 12namespace Azure.Storage.Cryptography
 13{
 14    internal class ClientSideDecryptor
 15    {
 16        /// <summary>
 17        /// Clients that can upload data have a key encryption key stored on them. Checking if
 18        /// a cached key exists and matches a given <see cref="EncryptionData"/> saves a call
 19        /// to the external key resolver implementation when available.
 20        /// </summary>
 21        private readonly IKeyEncryptionKey _potentialCachedIKeyEncryptionKey;
 22
 23        /// <summary>
 24        /// Resolver to fetch the key encryption key.
 25        /// </summary>
 26        private readonly IKeyEncryptionKeyResolver _keyResolver;
 27
 028        public ClientSideDecryptor(ClientSideEncryptionOptions options)
 29        {
 030            _potentialCachedIKeyEncryptionKey = options.KeyEncryptionKey;
 031            _keyResolver = options.KeyResolver;
 032        }
 33
 34        /// <summary>
 35        /// Decrypts the given stream if decryption information is provided.
 36        /// Does not shave off unwanted start/end bytes, but will shave off padding.
 37        /// </summary>
 38        /// <param name="ciphertext">Stream to decrypt.</param>
 39        /// <param name="encryptionData">
 40        /// Encryption metadata and wrapped content encryption key.
 41        /// </param>
 42        /// <param name="ivInStream">
 43        /// Whether to use the first block of the stream for the IV instead of the value in
 44        /// <paramref name="encryptionData"/>. Generally for partial blob downloads where the
 45        /// previous block of the ciphertext is the IV for the next.
 46        /// </param>
 47        /// <param name="noPadding">
 48        /// Whether to ignore padding. Generally for partial blob downloads where the end of
 49        /// the blob (where the padding occurs) was not downloaded.
 50        /// </param>
 51        /// <param name="async">Whether to perform this function asynchronously.</param>
 52        /// <param name="cancellationToken"></param>
 53        /// <returns>
 54        /// Decrypted plaintext.
 55        /// </returns>
 56        /// <exception cref="Exception">
 57        /// Exceptions thrown based on implementations of <see cref="IKeyEncryptionKey"/> and
 58        /// <see cref="IKeyEncryptionKeyResolver"/>.
 59        /// </exception>
 60        public async Task<Stream> DecryptInternal(
 61            Stream ciphertext,
 62            EncryptionData encryptionData,
 63            bool ivInStream,
 64            bool noPadding,
 65            bool async,
 66            CancellationToken cancellationToken)
 67        {
 068            switch (encryptionData.EncryptionAgent.EncryptionVersion)
 69            {
 70                case ClientSideEncryptionVersion.V1_0:
 071                    return await DecryptInternalV1_0(
 072                        ciphertext,
 073                        encryptionData,
 074                        ivInStream,
 075                        noPadding,
 076                        async,
 077                        cancellationToken).ConfigureAwait(false);
 78                default:
 079                    throw Errors.ClientSideEncryption.BadEncryptionAgent(encryptionData.EncryptionAgent.EncryptionVersio
 80            }
 081        }
 82
 83        /// <summary>
 84        /// Decrypts the given stream if decryption information is provided.
 85        /// Does not shave off unwanted start/end bytes, but will shave off padding.
 86        /// </summary>
 87        /// <param name="ciphertext">Stream to decrypt.</param>
 88        /// <param name="encryptionData">
 89        /// Encryption metadata and wrapped content encryption key.
 90        /// </param>
 91        /// <param name="ivInStream">
 92        /// Whether to use the first block of the stream for the IV instead of the value in
 93        /// <paramref name="encryptionData"/>. Generally for partial blob downloads where the
 94        /// previous block of the ciphertext is the IV for the next.
 95        /// </param>
 96        /// <param name="noPadding">
 97        /// Whether to ignore padding. Generally for partial blob downloads where the end of
 98        /// the blob (where the padding occurs) was not downloaded.
 99        /// </param>
 100        /// <param name="async">Whether to perform this function asynchronously.</param>
 101        /// <param name="cancellationToken"></param>
 102        /// <returns>
 103        /// Decrypted plaintext.
 104        /// </returns>
 105        /// <exception cref="Exception">
 106        /// Exceptions thrown based on implementations of <see cref="IKeyEncryptionKey"/> and
 107        /// <see cref="IKeyEncryptionKeyResolver"/>.
 108        /// </exception>
 109        private async Task<Stream> DecryptInternalV1_0(
 110            Stream ciphertext,
 111            EncryptionData encryptionData,
 112            bool ivInStream,
 113            bool noPadding,
 114            bool async,
 115            CancellationToken cancellationToken)
 116        {
 0117            var contentEncryptionKey = await GetContentEncryptionKeyAsync(
 0118                encryptionData,
 0119                async,
 0120                cancellationToken).ConfigureAwait(false);
 121
 122            Stream plaintext;
 123            //int read = 0;
 0124            if (encryptionData != default)
 125            {
 126                byte[] IV;
 0127                if (!ivInStream)
 128                {
 0129                    IV = encryptionData.ContentEncryptionIV;
 130                }
 131                else
 132                {
 0133                    IV = new byte[Constants.ClientSideEncryption.EncryptionBlockSize];
 0134                    if (async)
 135                    {
 0136                        await ciphertext.ReadAsync(IV, 0, IV.Length, cancellationToken).ConfigureAwait(false);
 137                    }
 138                    else
 139                    {
 0140                        ciphertext.Read(IV, 0, IV.Length);
 141                    }
 142                    //read = IV.Length;
 143                }
 144
 0145                plaintext = WrapStream(
 0146                    ciphertext,
 0147                    contentEncryptionKey.ToArray(),
 0148                    encryptionData,
 0149                    IV,
 0150                    noPadding);
 0151            }
 152            else
 153            {
 0154                plaintext = ciphertext;
 155            }
 156
 0157            return plaintext;
 0158        }
 159
 160#pragma warning disable CS1587 // XML comment is not placed on a valid language element
 161        /// <summary>
 162        /// Returns the content encryption key for blob. First tries to get the key encryption key from KeyResolver,
 163        /// then falls back to IKey stored on this EncryptionPolicy. Unwraps the content encryption key with the
 164        /// correct key wrapper.
 165        /// </summary>
 166        /// <param name="encryptionData">The encryption data.</param>
 167        /// <param name="async">Whether to perform asynchronously.</param>
 168        /// <param name="cancellationToken"></param>
 169        /// <returns>
 170        /// Encryption key as a byte array.
 171        /// </returns>
 172        /// <exception cref="Exception">
 173        /// Exceptions thrown based on implementations of <see cref="IKeyEncryptionKey"/> and
 174        /// <see cref="IKeyEncryptionKeyResolver"/>.
 175        /// </exception>
 176        private async Task<Memory<byte>> GetContentEncryptionKeyAsync(
 177#pragma warning restore CS1587 // XML comment is not placed on a valid language element
 178            EncryptionData encryptionData,
 179            bool async,
 180            CancellationToken cancellationToken)
 181        {
 0182            IKeyEncryptionKey key = default;
 183
 184            // If we already have a local key and it is the correct one, use that.
 0185            if (encryptionData.WrappedContentKey.KeyId == _potentialCachedIKeyEncryptionKey?.KeyId)
 186            {
 0187                key = _potentialCachedIKeyEncryptionKey;
 188            }
 189            // Otherwise, use the resolver.
 0190            else if (_keyResolver != null)
 191            {
 0192                key = async
 0193                    ? await _keyResolver.ResolveAsync(encryptionData.WrappedContentKey.KeyId, cancellationToken).Configu
 0194                    : _keyResolver.Resolve(encryptionData.WrappedContentKey.KeyId, cancellationToken);
 195            }
 196
 197            // We throw for every other reason that decryption couldn't happen. Throw a reasonable
 198            // exception here instead of nullref.
 0199            if (key == default)
 200            {
 0201                throw Errors.ClientSideEncryption.KeyNotFound(encryptionData.WrappedContentKey.KeyId);
 202            }
 203
 0204            return async
 0205                ? await key.UnwrapKeyAsync(
 0206                    encryptionData.WrappedContentKey.Algorithm,
 0207                    encryptionData.WrappedContentKey.EncryptedKey,
 0208                    cancellationToken).ConfigureAwait(false)
 0209                : key.UnwrapKey(
 0210                    encryptionData.WrappedContentKey.Algorithm,
 0211                    encryptionData.WrappedContentKey.EncryptedKey,
 0212                    cancellationToken);
 0213        }
 214
 215
 216        /// <summary>
 217        /// Wraps a stream of ciphertext to stream plaintext.
 218        /// </summary>
 219        /// <param name="contentStream"></param>
 220        /// <param name="contentEncryptionKey"></param>
 221        /// <param name="encryptionData"></param>
 222        /// <param name="iv"></param>
 223        /// <param name="noPadding"></param>
 224        /// <returns></returns>
 225        private static Stream WrapStream(
 226            Stream contentStream,
 227            byte[] contentEncryptionKey,
 228            EncryptionData encryptionData,
 229            byte[] iv,
 230            bool noPadding)
 231        {
 0232            if (encryptionData.EncryptionAgent.EncryptionAlgorithm == ClientSideEncryptionAlgorithm.AesCbc256)
 233            {
 0234                using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
 235                {
 0236                    aesProvider.IV = iv ?? encryptionData.ContentEncryptionIV;
 0237                    aesProvider.Key = contentEncryptionKey;
 238
 0239                    if (noPadding)
 240                    {
 0241                        aesProvider.Padding = PaddingMode.None;
 242                    }
 243
 0244                    return new CryptoStream(contentStream, aesProvider.CreateDecryptor(), CryptoStreamMode.Read);
 245                }
 246            }
 247
 0248            throw Errors.ClientSideEncryption.BadEncryptionAlgorithm(encryptionData.EncryptionAgent.EncryptionAlgorithm.
 0249        }
 250    }
 251}