File: System\Security\Cryptography\CapiSymmetricAlgorithm.cs
Project: ndp\fx\src\Core\System.Core.csproj (System.Core)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
 
using System;
#if FEATURE_CORESYSTEM
using System.Core;
#endif
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;
using Microsoft.Win32.SafeHandles;
 
namespace System.Security.Cryptography {
    /// <summary>
    ///     Flag to indicate if we're doing encryption or decryption
    /// </summary>
    internal enum EncryptionMode {
        Encrypt,
        Decrypt
    }
 
    /// <summary>
    ///     Implementation of a generic CAPI symmetric encryption algorithm. Concrete SymmetricAlgorithm classes
    ///     which wrap CAPI implementations can use this class to perform the actual encryption work.
    /// </summary>
    internal sealed class CapiSymmetricAlgorithm : ICryptoTransform {
        private int m_blockSize;
        private byte[] m_depadBuffer;
        private EncryptionMode m_encryptionMode;
        [SecurityCritical]
        private SafeCapiKeyHandle m_key;
        private PaddingMode m_paddingMode;
        [SecurityCritical]
        private SafeCspHandle m_provider;
 
        [System.Security.SecurityCritical]
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed")]
        public CapiSymmetricAlgorithm(int blockSize,
                                      int feedbackSize,
                                      SafeCspHandle provider,
                                      SafeCapiKeyHandle key,
                                      byte[] iv,
                                      CipherMode cipherMode,
                                      PaddingMode paddingMode,
                                      EncryptionMode encryptionMode) {
            Contract.Requires(0 < blockSize && blockSize % 8 == 0);
            Contract.Requires(0 <= feedbackSize);
            Contract.Requires(provider != null && !provider.IsInvalid && !provider.IsClosed);
            Contract.Requires(key != null && !key.IsInvalid && !key.IsClosed);
            Contract.Ensures(m_provider != null && !m_provider.IsInvalid && !m_provider.IsClosed);
            
            m_blockSize = blockSize;
            m_encryptionMode = encryptionMode;
            m_paddingMode = paddingMode;
            m_provider = provider.Duplicate();
            m_key = SetupKey(key, ProcessIV(iv, blockSize, cipherMode), cipherMode, feedbackSize);
        }
 
        public bool CanReuseTransform {
            get { return true; }
        }
 
        public bool CanTransformMultipleBlocks {
            get { return true; }
        }
 
        //
        // Note: both input and output block size are in bytes rather than bits
        //
 
        public int InputBlockSize {
            [Pure]
            get { return m_blockSize / 8; }
        }
 
        public int OutputBlockSize {
            get { return m_blockSize / 8; }
        }
 
        [SecuritySafeCritical]
        public void Dispose() {
            Contract.Ensures(m_key == null || m_key.IsClosed);
            Contract.Ensures(m_provider == null || m_provider.IsClosed);
            Contract.Ensures(m_depadBuffer == null);
 
            if (m_key != null) {
                m_key.Dispose();
            }
 
            if (m_provider != null) {
                m_provider.Dispose();
            }
 
            if (m_depadBuffer != null) {
                Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
            }
 
            return;
        }
 
        [SecuritySafeCritical]
        private int DecryptBlocks(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
            Contract.Requires(m_key != null);
            Contract.Requires(inputBuffer != null && inputCount <= inputBuffer.Length - inputOffset);
            Contract.Requires(inputOffset >= 0);
            Contract.Requires(inputCount > 0 && inputCount % InputBlockSize == 0);
            Contract.Requires(outputBuffer != null && inputCount <= outputBuffer.Length - outputOffset);
            Contract.Requires(inputOffset >= 0);
            Contract.Requires(m_depadBuffer == null || (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros));
            Contract.Ensures(Contract.Result<int>() >= 0);
 
            //
            // If we're decrypting, it's possible to be called with the last blocks of the data, and then
            // have TransformFinalBlock called with an empty array. Since we don't know if this is the case,
            // we won't decrypt the last block of the input until either TransformBlock or
            // TransformFinalBlock is next called.
            //
            // We don't need to do this for PaddingMode.None because there is no padding to strip, and
            // we also don't do this for PaddingMode.Zeros since there is no way for us to tell if the
            // zeros at the end of a block are part of the plaintext or the padding.
            //
 
            int decryptedBytes = 0;
            if (m_paddingMode != PaddingMode.None && m_paddingMode != PaddingMode.Zeros) {
                // If we have data saved from a previous call, decrypt that into the output first
                if (m_depadBuffer != null) {
                    int depadDecryptLength = RawDecryptBlocks(m_depadBuffer, 0, m_depadBuffer.Length);
                    Buffer.BlockCopy(m_depadBuffer, 0, outputBuffer, outputOffset, depadDecryptLength);
                    Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
                    outputOffset += depadDecryptLength;
                    decryptedBytes += depadDecryptLength;
                }
                else {
                    m_depadBuffer = new byte[InputBlockSize];
                }
 
                // Copy the last block of the input buffer into the depad buffer
                Debug.Assert(inputCount >= m_depadBuffer.Length, "inputCount >= m_depadBuffer.Length");
                Buffer.BlockCopy(inputBuffer,
                                 inputOffset + inputCount - m_depadBuffer.Length,
                                 m_depadBuffer,
                                 0,
                                 m_depadBuffer.Length);
                inputCount -= m_depadBuffer.Length;
                Debug.Assert(inputCount % InputBlockSize == 0, "Did not remove whole blocks for depadding");
            }
 
            // CryptDecrypt operates in place, so if after reserving the depad buffer there's still data to decrypt,
            // make a copy of that in the output buffer to work on.
            if (inputCount > 0) {
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
                decryptedBytes += RawDecryptBlocks(outputBuffer, outputOffset, inputCount);
            }
 
            return decryptedBytes;
        }
 
        /// <summary>
        ///     Remove the padding from the last blocks being decrypted
        /// </summary>
        private byte[] DepadBlock(byte[] block, int offset, int count) {
            Contract.Requires(block != null && count >= block.Length - offset);
            Contract.Requires(0 <= offset);
            Contract.Requires(0 <= count);
            Contract.Ensures(Contract.Result<byte[]>() != null && Contract.Result<byte[]>().Length <= block.Length);
 
            int padBytes = 0;
 
            // See code:System.Security.Cryptography.CapiSymmetricAlgorithm.PadBlock for a description of the
            // padding modes.
            switch (m_paddingMode) {
                case PaddingMode.ANSIX923:
                    padBytes = block[offset + count - 1];
 
                    // Verify the amount of padding is reasonable
                    if (padBytes <= 0 || padBytes > InputBlockSize) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                    }
 
                    // Verify that all the padding bytes are 0s
                    for (int i = offset + count - padBytes; i < offset + count - 1; i++) {
                        if (block[i] != 0) {
                            throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                        }
                    }
 
                    break;
 
                case PaddingMode.ISO10126:
                    padBytes = block[offset + count - 1];
 
                    // Verify the amount of padding is reasonable
                    if (padBytes <= 0 || padBytes > InputBlockSize) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                    }
 
                    // Since the padding consists of random bytes, we cannot verify the actual pad bytes themselves
                    break;
 
                case PaddingMode.PKCS7:
                    padBytes = block[offset + count - 1];
 
                    // Verify the amount of padding is reasonable
                    if (padBytes <= 0 || padBytes > InputBlockSize) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                    }
 
                    // Verify all the padding bytes match the amount of padding
                    for (int i = offset + count - padBytes; i < offset + count; i++) {
                        if (block[i] != padBytes) {
                            throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidPadding));
                        }
                    }
 
                    break;
 
                    // We cannot remove Zeros padding because we don't know if the zeros at the end of the block
                    // belong to the padding or the plaintext itself.
                case PaddingMode.Zeros:
                case PaddingMode.None:
                    padBytes = 0;
                    break;
 
                default:
                    throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode));
            }
 
            // Copy everything but the padding to the output
            byte[] depadded = new byte[count - padBytes];
            Buffer.BlockCopy(block, offset, depadded, 0, depadded.Length);
            return depadded;
        }
 
        /// <summary>
        ///     Encrypt blocks of plaintext
        /// </summary>
        [SecurityCritical]
        private int EncryptBlocks(byte[] buffer, int offset, int count) {
            Contract.Requires(m_key != null);
            Contract.Requires(buffer != null && count <= buffer.Length - offset);
            Contract.Requires(offset >= 0);
            Contract.Requires(count > 0 && count % InputBlockSize == 0);
            Contract.Ensures(Contract.Result<int>() >= 0);
 
            //
            // Do the encryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR
            // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working
            // with the final block.
            //
 
            int dataLength = count;
            unsafe {
                fixed (byte* pData = &buffer[offset]) {
                    if (!CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key,
                                                                     SafeCapiHashHandle.InvalidHandle,
                                                                     false,
                                                                     0,
                                                                     new IntPtr(pData),
                                                                     ref dataLength,
                                                                     buffer.Length - offset)) {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                }
            }
 
            return dataLength;
        }
 
        /// <summary>
        ///     Calculate the padding for a block of data
        /// </summary>
        [SecuritySafeCritical]
        private byte[] PadBlock(byte[] block, int offset, int count) {
            Contract.Requires(m_provider != null);
            Contract.Requires(block != null && count <= block.Length - offset);
            Contract.Requires(0 <= offset);
            Contract.Requires(0 <= count);
            Contract.Ensures(Contract.Result<byte[]>() != null && Contract.Result<byte[]>().Length % InputBlockSize == 0);
 
            byte[] result = null;
            int padBytes = InputBlockSize - (count % InputBlockSize);
            
            switch (m_paddingMode) {
                    // ANSI padding fills the blocks with zeros and adds the total number of padding bytes as
                    // the last pad byte, adding an extra block if the last block is complete.
                    //
                    // x 00 00 00 00 00 00 07
                case PaddingMode.ANSIX923:
                    result = new byte[count + padBytes];
                    Buffer.BlockCopy(block, 0, result, 0, count);
                    result[result.Length - 1] = (byte)padBytes;
                    break;
 
                    // ISO padding fills the blocks up with random bytes and adds the total number of padding
                    // bytes as the last pad byte, adding an extra block if the last block is complete.
                    //
                    // xx rr rr rr rr rr rr 07
                case PaddingMode.ISO10126:
                    result = new byte[count + padBytes];
                    
                    CapiNative.UnsafeNativeMethods.CryptGenRandom(m_provider, result.Length - 1, result);
                    Buffer.BlockCopy(block, 0, result, 0, count);
                    result[result.Length - 1] = (byte)padBytes;
                    break;
 
                    // No padding requires that the input already be a multiple of the block size
                case PaddingMode.None:
                    if (count % InputBlockSize != 0) {
                        throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock));
                    }
 
                    result = new byte[count];
                    Buffer.BlockCopy(block, offset, result, 0, result.Length);
                    break;
 
                    // PKCS padding fills the blocks up with bytes containing the total number of padding bytes
                    // used, adding an extra block if the last block is complete.
                    //
                    // xx xx 06 06 06 06 06 06
                case PaddingMode.PKCS7:
                    result = new byte[count + padBytes];
                    Buffer.BlockCopy(block, offset, result, 0, count);
 
                    for (int i = count; i < result.Length; i++) {
                        result[i] = (byte)padBytes;
                    }
                    break;
 
                    // Zeros padding fills the last partial block with zeros, and does not add a new block to
                    // the end if the last block is already complete.
                    //
                    //  xx 00 00 00 00 00 00 00
                case PaddingMode.Zeros:
                    if (padBytes == InputBlockSize) {
                        padBytes = 0;
                    }
 
                    result = new byte[count + padBytes];
                    Buffer.BlockCopy(block, offset, result, 0, count);
                    break;
 
                default:
                    throw new CryptographicException(SR.GetString(SR.Cryptography_UnknownPaddingMode));
            }
 
            return result;
        }
 
        /// <summary>
        ///     Validate and transform the user's IV into one that we will pass on to CAPI
        ///
        ///     If we have an IV, make a copy of it so that it doesn't get modified while we're using it. If
        ///     not, and we're not in ECB mode then throw an error, since we cannot decrypt without the IV, and
        ///     generating a random IV to encrypt with would lead to data which is not decryptable.
        ///
        ///     For compatibility with v1.x, we accept IVs which are longer than the block size, and truncate
        ///     them back.  We will reject an IV which is smaller than the block size however.
        /// </summary>
        private static byte[] ProcessIV(byte[] iv, int blockSize, CipherMode cipherMode) {
            Contract.Requires(blockSize % 8 == 0);
            Contract.Ensures(cipherMode == CipherMode.ECB ||
                             (Contract.Result<byte[]>() != null && Contract.Result<byte[]>().Length == blockSize / 8));
 
            byte[] realIV = null;
 
            if (iv != null) {
                if (blockSize / 8 <= iv.Length) {
                    realIV = new byte[blockSize / 8];
                    Buffer.BlockCopy(iv, 0, realIV, 0, realIV.Length);
                }
                else {
                    throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidIVSize));
                }
            }
            else if (cipherMode != CipherMode.ECB) {
                throw new CryptographicException(SR.GetString(SR.Cryptography_MissingIV));
            }
 
            return realIV;
        }
 
        /// <summary>
        ///     Do a direct decryption of the ciphertext blocks. This method should not be called from anywhere
        ///     but DecryptBlocks or TransformFinalBlock since it does not account for the depadding buffer and
        ///     direct use could lead to incorrect decryption values.
        /// </summary>
        [SecurityCritical]
        private int RawDecryptBlocks(byte[] buffer, int offset, int count) {
            Contract.Requires(m_key != null);
            Contract.Requires(buffer != null && count <= buffer.Length - offset);
            Contract.Requires(offset >= 0);
            Contract.Requires(count > 0 && count % InputBlockSize == 0);
            Contract.Ensures(Contract.Result<int>() >= 0);
 
            //
            // Do the decryption. Note that CapiSymmetricAlgorithm will do all padding itself since the CLR
            // supports padding modes that CAPI does not, so we will always tell CAPI that we are not working
            // with the final block.
            //
 
            int dataLength = count;
            unsafe {
                fixed (byte* pData = &buffer[offset]) {
                    if (!CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key,
                                                                     SafeCapiHashHandle.InvalidHandle,
                                                                     false,
                                                                     0,
                                                                     new IntPtr(pData),
                                                                     ref dataLength)) {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                }
            }
 
            return dataLength;
        }
 
        /// <summary>
        ///     Reset the state of the algorithm so that it can begin processing a new message
        /// </summary>
        [SecuritySafeCritical]
        private void Reset() {
            Contract.Requires(m_key != null);
            Contract.Ensures(m_depadBuffer == null);
 
            //
            // CryptEncrypt / CryptDecrypt must be called with the Final parameter set to true so that
            // their internal state is reset. Since we do all padding by hand, this isn't done by
            // TransformFinalBlock so is done on an empty buffer here.
            //
 
            byte[] buffer = new byte[OutputBlockSize];
            int resetSize = 0;
            unsafe {
                fixed (byte* pBuffer = buffer) {
                    if (m_encryptionMode == EncryptionMode.Encrypt) {
                        CapiNative.UnsafeNativeMethods.CryptEncrypt(m_key,
                                                                    SafeCapiHashHandle.InvalidHandle,
                                                                    true,
                                                                    0,
                                                                    new IntPtr(pBuffer),
                                                                    ref resetSize,
                                                                    buffer.Length);
                    }
                    else {
                        if (!LocalAppContextSwitches.AesCryptoServiceProviderDontCorrectlyResetDecryptor) {
                            resetSize = buffer.Length;
                        }
                        CapiNative.UnsafeNativeMethods.CryptDecrypt(m_key,
                                                                    SafeCapiHashHandle.InvalidHandle,
                                                                    true,
                                                                    0,
                                                                    new IntPtr(pBuffer),
                                                                    ref resetSize);
                    }
                }
            }
 
            // Also erase the depadding buffer so we don't cross data from the previous message into this one
            if (m_depadBuffer != null) {
                Array.Clear(m_depadBuffer, 0, m_depadBuffer.Length);
                m_depadBuffer = null;
            }
        }
 
        /// <summary>
        ///     Encrypt or decrypt a single block of data
        /// </summary>
        [SecuritySafeCritical]
        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) {
            Contract.Ensures(Contract.Result<int>() >= 0);
 
            if (inputBuffer == null) {
                throw new ArgumentNullException("inputBuffer");
            }
            if (inputOffset < 0) {
                throw new ArgumentOutOfRangeException("inputOffset");
            }
            if (inputCount <= 0) {
                throw new ArgumentOutOfRangeException("inputCount");
            }
            if (inputCount % InputBlockSize != 0) {
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_MustTransformWholeBlock));
            }
            if (inputCount > inputBuffer.Length - inputOffset) {
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
            }
            if (outputBuffer == null) {
                throw new ArgumentNullException("outputBuffer");
            }
            if (inputCount > outputBuffer.Length - outputOffset) {
                throw new ArgumentOutOfRangeException("outputOffset", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
            }
 
            if (m_encryptionMode == EncryptionMode.Encrypt) {
                // CryptEncrypt operates in place, so make a copy of the original data in the output buffer for
                // it to work on.
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
                return EncryptBlocks(outputBuffer, outputOffset, inputCount);
            }
            else {
                return DecryptBlocks(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
            }
        }
 
        /// <summary>
        ///     Encrypt or decrypt the last block of data in the current message
        /// </summary>
        [SecuritySafeCritical]
        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) {
            Contract.Ensures(Contract.Result<byte[]>() != null);
 
            if (inputBuffer == null) {
                throw new ArgumentNullException("inputBuffer");
            }
            if (inputOffset < 0) {
                throw new ArgumentOutOfRangeException("inputOffset");
            }
            if (inputCount < 0) {
                throw new ArgumentOutOfRangeException("inputCount");
            }
            if (inputCount > inputBuffer.Length - inputOffset) {
                throw new ArgumentOutOfRangeException("inputCount", SR.GetString(SR.Cryptography_TransformBeyondEndOfBuffer));
            }
 
            byte[] outputData = null;
 
            if (m_encryptionMode == EncryptionMode.Encrypt) {
                // If we're encrypting, we need to pad the last block before encrypting it
                outputData = PadBlock(inputBuffer, inputOffset, inputCount);
                if (outputData.Length > 0) {
                    EncryptBlocks(outputData, 0, outputData.Length);
                }
            }
            else {
                // We can't complete decryption on a partial block
                if (inputCount % InputBlockSize != 0) {
                    throw new CryptographicException(SR.GetString(SR.Cryptography_PartialBlock));
                }
 
                //
                // If we have a depad buffer, copy that into the decryption buffer followed by the input data.
                // Otherwise the decryption buffer is just the input data.
                //
 
                byte[] ciphertext = null;
 
                if (m_depadBuffer == null) {
                    ciphertext = new byte[inputCount];
                    Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, 0, inputCount);
                }
                else {
                    ciphertext = new byte[m_depadBuffer.Length + inputCount];
                    Buffer.BlockCopy(m_depadBuffer, 0, ciphertext, 0, m_depadBuffer.Length);
                    Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, m_depadBuffer.Length, inputCount);
                }
 
                // Decrypt the data, then strip the padding to get the final decrypted data.
                if (ciphertext.Length > 0) {
                    int decryptedBytes = RawDecryptBlocks(ciphertext, 0, ciphertext.Length);
                    outputData = DepadBlock(ciphertext, 0, decryptedBytes);
                }
                else {
                    outputData = new byte[0];
                }
            }
 
            Reset();            
            return outputData;
        }
 
        /// <summary>
        ///     Prepare the cryptographic key for use in the encryption / decryption operation
        /// </summary>
        [System.Security.SecurityCritical]
        private static SafeCapiKeyHandle SetupKey(SafeCapiKeyHandle key, byte[] iv, CipherMode cipherMode, int feedbackSize) {
            Contract.Requires(key != null);
            Contract.Requires(cipherMode == CipherMode.ECB || iv != null);
            Contract.Requires(0 <= feedbackSize);
            Contract.Ensures(Contract.Result<SafeCapiKeyHandle>() != null &&
                             !Contract.Result<SafeCapiKeyHandle>().IsInvalid &&
                             !Contract.Result<SafeCapiKeyHandle>().IsClosed);
 
            // Make a copy of the key so that we don't modify the properties of the caller's copy
            SafeCapiKeyHandle encryptionKey = key.Duplicate();
 
            // Setup the cipher mode first
            CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.Mode, (int)cipherMode);
 
            // If we're not in ECB mode then setup the IV
            if (cipherMode != CipherMode.ECB) {
                CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.IV, iv);
            }
 
            // OFB and CFB require a feedback loop size
            if (cipherMode == CipherMode.CFB || cipherMode == CipherMode.OFB) {
                CapiNative.SetKeyParameter(encryptionKey, CapiNative.KeyParameter.ModeBits, feedbackSize);
            }
 
            return encryptionKey;
        }
    }
}