File: System\Security\Cryptography\CapiHashAlgorithm.cs
Project: ndp\fx\src\Core\System.Core.csproj (System.Core)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
 
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;
using Microsoft.Win32.SafeHandles;
 
namespace System.Security.Cryptography {
    /// <summary>
    ///     Implementation of a generic CAPI hashing algorithm, concrete HashAlgorithm classes
    ///     implemented by CAPI can contain an instance of this class and delegate the work to it
    /// </summary>
    internal sealed class CapiHashAlgorithm : IDisposable {
        private CapiNative.AlgorithmId m_algorithmId;
        [SecurityCritical]
        private SafeCspHandle m_cspHandle;
        [SecurityCritical]
        private SafeCapiHashHandle m_hashHandle;
        
        [SecuritySafeCritical]
        // SafeCritical - we're not exposing out anything that we want to prevent untrusted code from getting at
        public CapiHashAlgorithm(string provider,
                                 CapiNative.ProviderType providerType,
                                 CapiNative.AlgorithmId algorithm) {
            Contract.Requires(!String.IsNullOrEmpty(provider));
            Contract.Requires((CapiNative.AlgorithmClass)((uint)algorithm & (uint)CapiNative.AlgorithmClass.Hash) == CapiNative.AlgorithmClass.Hash);
            Contract.Ensures(m_cspHandle != null && !m_cspHandle.IsInvalid && !m_cspHandle.IsClosed);
            Contract.Ensures(m_hashHandle != null && !m_hashHandle.IsInvalid && !m_hashHandle.IsClosed);
 
            m_algorithmId = algorithm;
            m_cspHandle = CapiNative.AcquireCsp(null,
                                                provider,
                                                providerType,
                                                CapiNative.CryptAcquireContextFlags.VerifyContext,
                                                true);
            Initialize();
        }
 
        [SecuritySafeCritical]
        public void Dispose() {
            Contract.Ensures(m_hashHandle == null || m_hashHandle.IsClosed);
            Contract.Ensures(m_cspHandle == null || m_cspHandle.IsClosed);
 
            if (m_hashHandle != null) {
                m_hashHandle.Dispose();
            }
 
            if (m_cspHandle != null) {
                m_cspHandle.Dispose();
            }
        }
 
        /// <summary>
        ///     Reset the hash algorithm to begin hashing a new set of data
        /// </summary>
        [SecuritySafeCritical]
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed")]
        public void Initialize() {
            Contract.Ensures(m_hashHandle != null && !m_hashHandle.IsInvalid && !m_hashHandle.IsClosed);
            Contract.Assert(m_cspHandle != null);
 
            // Try to create a new hash algorithm to use
            SafeCapiHashHandle newHashAlgorithm = null;
 
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                if (!CapiNative.UnsafeNativeMethods.CryptCreateHash(m_cspHandle,
                                                                    m_algorithmId,
                                                                    SafeCapiKeyHandle.InvalidHandle,
                                                                    0,
                                                                    out newHashAlgorithm)) {
                    // BadAlgorithmId means that this CSP does not support the specified algorithm, which means
                    // that we're on a platform that does not support the given hash function.
                    int error = Marshal.GetLastWin32Error();
                    if (error == (int)CapiNative.ErrorCode.BadAlgorithmId) {
                        throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported));
                    }
                    else {
                        throw new CryptographicException(error);
                    }
                }
            }
            finally {
                if (newHashAlgorithm != null && !newHashAlgorithm.IsInvalid) {
                    newHashAlgorithm.SetParentCsp(m_cspHandle);
                }
            }
 
            // If we created a new algorithm, dispose of the old one and use the new one
            Debug.Assert(newHashAlgorithm != null, "newHashAlgorithm != null");
            if (m_hashHandle != null) {
                m_hashHandle.Dispose();
            }
            m_hashHandle = newHashAlgorithm;
        }
 
        /// <summary>
        ///     Hash a block of data
        /// </summary>
        [SecuritySafeCritical]
        public void HashCore(byte[] array, int ibStart, int cbSize) {
            Contract.Assert(m_hashHandle != null);
 
            if (array == null) {
                throw new ArgumentNullException("array");
            }
            if (ibStart < 0 || ibStart > array.Length - cbSize) {
                throw new ArgumentOutOfRangeException("ibStart");
            }
            if (cbSize < 0 || cbSize > array.Length) {
                throw new ArgumentOutOfRangeException("cbSize");
            }
 
            if (cbSize == 0) {
                return;
            }
 
            unsafe {
                fixed (byte* dataPtr = array) {
                    if (!CapiNative.UnsafeNativeMethods.CryptHashData(m_hashHandle, dataPtr + ibStart, cbSize, 0)) {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                }
            }
        }
 
        /// <summary>
        ///     Complete the hash, returning its value
        /// </summary>
        [SecuritySafeCritical]
        public byte[] HashFinal() {
            Contract.Ensures(Contract.Result<byte[]>() != null);
            Contract.Assert(m_hashHandle != null);
 
            return CapiNative.GetHashParameter(m_hashHandle, CapiNative.HashParameter.HashValue);
        }
    }
}