< Summary

Class:Azure.Storage.Blobs.BlobClientSideDecryptor
Assembly:Azure.Storage.Blobs
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Blobs\src\BlobClientSideDecryptor.cs
Covered lines:0
Uncovered lines:65
Coverable lines:65
Total lines:192
Line coverage:0% (0 of 65)
Covered branches:0
Total branches:50
Branch coverage:0% (0 of 50)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-0%100%
DecryptInternal()-0%0%
TrimStreamInternal()-0%0%
GetAndValidateEncryptionDataOrDefault(...)-0%0%
CanIgnorePadding(...)-0%0%
GetEncryptedBlobRange(...)-0%0%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Blobs\src\BlobClientSideDecryptor.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.IO;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Azure.Storage.Blobs.Models;
 8using Azure.Storage.Cryptography;
 9using Azure.Storage.Cryptography.Models;
 10using Azure.Storage.Shared;
 11using Metadata = System.Collections.Generic.IDictionary<string, string>;
 12
 13namespace Azure.Storage.Blobs
 14{
 15    internal class BlobClientSideDecryptor
 16    {
 17        private readonly ClientSideDecryptor _decryptor;
 18
 019        public BlobClientSideDecryptor(ClientSideDecryptor decryptor)
 20        {
 021            _decryptor = decryptor;
 022        }
 23
 24        public async Task<Stream> DecryptInternal(
 25            Stream content,
 26            Metadata metadata,
 27            HttpRange originalRange,
 28            string receivedContentRange,
 29            bool async,
 30            CancellationToken cancellationToken)
 31        {
 032            ContentRange? contentRange = string.IsNullOrWhiteSpace(receivedContentRange)
 033                ? default
 034                : ContentRange.Parse(receivedContentRange);
 35
 036            EncryptionData encryptionData = GetAndValidateEncryptionDataOrDefault(metadata);
 037            if (encryptionData == default)
 38            {
 039                return await TrimStreamInternal(content, originalRange, contentRange, pulledOutIV: false, async, cancell
 40            }
 41
 042            bool ivInStream = originalRange.Offset >= Constants.ClientSideEncryption.EncryptionBlockSize;
 43
 44            // this method throws when key cannot be resolved. Blobs is intended to throw on this failure.
 045            var plaintext = await _decryptor.DecryptInternal(
 046                content,
 047                encryptionData,
 048                ivInStream,
 049                CanIgnorePadding(contentRange),
 050                async,
 051                cancellationToken).ConfigureAwait(false);
 52
 053            return await TrimStreamInternal(plaintext, originalRange, contentRange, ivInStream, async, cancellationToken
 054        }
 55
 56        private static async Task<Stream> TrimStreamInternal(
 57            Stream stream,
 58            HttpRange originalRange,
 59            ContentRange? receivedRange,
 60            bool pulledOutIV,
 61            bool async,
 62            CancellationToken cancellationToken)
 63        {
 64            // retrim start of stream to original requested location
 65            // keeping in mind whether we already pulled the IV out of the stream as well
 066            int gap = (int)(originalRange.Offset - (receivedRange?.Start ?? 0))
 067                - (pulledOutIV ? Constants.ClientSideEncryption.EncryptionBlockSize : 0);
 68
 069            int read = 0;
 070            while (gap > read)
 71            {
 072                int toRead = gap - read;
 73                // throw away initial bytes we want to trim off; stream cannot seek into future
 074                if (async)
 75                {
 076                    read += await stream.ReadAsync(new byte[toRead], 0, toRead, cancellationToken).ConfigureAwait(false)
 77                }
 78                else
 79                {
 080                    read += stream.Read(new byte[toRead], 0, toRead);
 81                }
 82            }
 83
 084            if (originalRange.Length.HasValue)
 85            {
 086                stream = WindowStream.GetWindow(stream, originalRange.Length.Value);
 87            }
 88
 089            return stream;
 090        }
 91
 92        private static EncryptionData GetAndValidateEncryptionDataOrDefault(Metadata metadata)
 93        {
 094            if (metadata == default)
 95            {
 096                return default;
 97            }
 098            if (!metadata.TryGetValue(Constants.ClientSideEncryption.EncryptionDataKey, out string encryptedDataString))
 99            {
 0100                return default;
 101            }
 102
 0103            EncryptionData encryptionData = EncryptionDataSerializer.Deserialize(encryptedDataString);
 104
 0105            _ = encryptionData.ContentEncryptionIV ?? throw Errors.ClientSideEncryption.MissingEncryptionMetadata(
 0106                nameof(EncryptionData.ContentEncryptionIV));
 0107            _ = encryptionData.WrappedContentKey.EncryptedKey ?? throw Errors.ClientSideEncryption.MissingEncryptionMeta
 0108                nameof(EncryptionData.WrappedContentKey.EncryptedKey));
 109
 0110            return encryptionData;
 111        }
 112
 113        /// <summary>
 114        /// Gets whether to ignore padding options for decryption.
 115        /// </summary>
 116        /// <param name="contentRange">Downloaded content range.</param>
 117        /// <returns>True if we should ignore padding.</returns>
 118        /// <remarks>
 119        /// If the last cipher block of the blob was returned, we need the padding. Otherwise, we can ignore it.
 120        /// </remarks>
 121        private static bool CanIgnorePadding(ContentRange? contentRange)
 122        {
 123            // if Content-Range not present, we requested the whole blob
 0124            if (contentRange == null)
 125            {
 0126                return false;
 127            }
 128
 129            // if range is wildcard, we requested the whole blob
 0130            if (!contentRange.Value.End.HasValue)
 131            {
 0132                return false;
 133            }
 134
 135            // blob storage will always return ContentRange.Size
 136            // we don't have to worry about the impossible decision of what to do if it doesn't
 137
 138            // did we request the last block?
 139            // end is inclusive/0-index, so end = n and size = n+1 means we requested the last block
 0140            if (contentRange.Value.Size - contentRange.Value.End == 1)
 141            {
 0142                return false;
 143            }
 144
 0145            return true;
 146        }
 147
 148        internal static HttpRange GetEncryptedBlobRange(HttpRange originalRange)
 149        {
 0150            int offsetAdjustment = 0;
 0151            long? adjustedDownloadCount = originalRange.Length;
 152
 153            // Calculate offsetAdjustment.
 0154            if (originalRange.Offset != 0)
 155            {
 156                // Align with encryption block boundary.
 157                int diff;
 0158                if ((diff = (int)(originalRange.Offset % Constants.ClientSideEncryption.EncryptionBlockSize)) != 0)
 159                {
 0160                    offsetAdjustment += diff;
 0161                    if (adjustedDownloadCount != default)
 162                    {
 0163                        adjustedDownloadCount += diff;
 164                    }
 165                }
 166
 167                // Account for IV.
 0168                if (originalRange.Offset >= Constants.ClientSideEncryption.EncryptionBlockSize)
 169                {
 0170                    offsetAdjustment += Constants.ClientSideEncryption.EncryptionBlockSize;
 171                    // Increment adjustedDownloadCount if necessary.
 0172                    if (adjustedDownloadCount != default)
 173                    {
 0174                        adjustedDownloadCount += Constants.ClientSideEncryption.EncryptionBlockSize;
 175                    }
 176                }
 177            }
 178
 179            // Align adjustedDownloadCount with encryption block boundary at the end of the range. Note that it is impos
 180            // to adjust past the end of the blob as an encrypted blob was padded to align to an encryption block bounda
 0181            if (adjustedDownloadCount != null)
 182            {
 0183                adjustedDownloadCount += (
 0184                    Constants.ClientSideEncryption.EncryptionBlockSize - (int)(adjustedDownloadCount
 0185                    % Constants.ClientSideEncryption.EncryptionBlockSize)
 0186                ) % Constants.ClientSideEncryption.EncryptionBlockSize;
 187            }
 188
 0189            return new HttpRange(originalRange.Offset - offsetAdjustment, adjustedDownloadCount);
 190        }
 191    }
 192}