File: System\Security\Cryptography\BCryptHashAlgorithm.cs
Project: ndp\fx\src\Core\System.Core.csproj (System.Core)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;
using Microsoft.Win32.SafeHandles;
 
namespace System.Security.Cryptography {
 
    /// <summar>
    /// Implementation of SafeBCryptAlgorithmHandle Cache.
    /// </summary>
    internal sealed class BCryptAlgorithmHandleCache {
        [SecurityCritical]
        private Dictionary<string, WeakReference> m_algorithmHandles;
 
        [SecurityCritical]
        public BCryptAlgorithmHandleCache()
        {
            m_algorithmHandles = new Dictionary<string, WeakReference>();
        }
 
        [SecuritySafeCritical]
        public SafeBCryptAlgorithmHandle GetCachedAlgorithmHandle(string algorithm, string implementation) {
            string handleKey = algorithm + implementation;
            SafeBCryptAlgorithmHandle algorithmHandle = null;
 
            if (m_algorithmHandles.ContainsKey(handleKey))
            {
                algorithmHandle = m_algorithmHandles[handleKey].Target as SafeBCryptAlgorithmHandle;
                if (algorithmHandle != null)
                {
                    return algorithmHandle;
                }
            }
            
            algorithmHandle = BCryptNative.OpenAlgorithm(algorithm, implementation);
            m_algorithmHandles[handleKey] = new WeakReference(algorithmHandle);
            return algorithmHandle;
        }
    }
 
    /// <summary>
    ///     Implementation of a generic BCrypt hashing algorithm, concrete HashAlgorithm classes
    ///     implemented by BCrypt can contain an instance of this class and delegate the work to it.
    /// </summary>
    internal sealed class BCryptHashAlgorithm : IDisposable {
        [ThreadStatic]
        [SecurityCritical]
        private static BCryptAlgorithmHandleCache _algorithmCache;
        [SecurityCritical]
        private SafeBCryptAlgorithmHandle m_algorithmHandle;
        [SecurityCritical]
        private SafeBCryptHashHandle m_hashHandle;
 
        // SafeCritical - we're not exposing out anything that we want to prevent untrusted code from getting at
        [SecuritySafeCritical]
        public BCryptHashAlgorithm(CngAlgorithm algorithm, string implementation) {
            Contract.Requires(algorithm != null);
            Contract.Requires(!String.IsNullOrEmpty(implementation));
            Contract.Ensures(m_algorithmHandle != null && !m_algorithmHandle.IsInvalid && !m_algorithmHandle.IsClosed);
            Contract.Ensures(m_hashHandle != null && !m_hashHandle.IsInvalid && !m_hashHandle.IsClosed);
 
            // Make sure CNG is supported on this platform
            if (!BCryptNative.BCryptSupported) {
                throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported));
            }
 
            if (_algorithmCache == null)
            {
                _algorithmCache = new BCryptAlgorithmHandleCache();
            }
 
            m_algorithmHandle = _algorithmCache.GetCachedAlgorithmHandle(algorithm.Algorithm, implementation);
 
            Initialize();
        }
 
        /// <summary>
        ///     Clean up the hash algorithm
        /// </summary>
        [SecuritySafeCritical]
        public void Dispose() {
            Contract.Ensures(m_hashHandle == null || m_hashHandle.IsClosed);
            Contract.Ensures(m_algorithmHandle == null || m_algorithmHandle.IsClosed);
 
            if (m_hashHandle != null) {
                m_hashHandle.Dispose();
            }
 
            if (m_algorithmHandle != null) {
                m_algorithmHandle = null;
            }
        }
 
        /// <summary>
        ///     Reset the hash algorithm to begin hashing a new set of data
        /// </summary>
        // SafeCritical - we're not exposing out anything that we want to prevent untrusted code from getting
        //                at.  We've also made sure not to leak any native resources out to partial trust code
        //                and we control all native inputs.
        [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_algorithmHandle != null);
 
            // Try to create a new hash algorithm to use
            SafeBCryptHashHandle newHashAlgorithm = null;
            IntPtr hashObjectBuffer = IntPtr.Zero;
 
            // Creating a BCRYPT_HASH_HANDLE requires providing a buffer to hold the hash object in, which
            // is tied to the lifetime of the hash handle. Wrap this in a CER so we can tie the lifetimes together
            // safely.
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                
                int hashObjectSize = BCryptNative.GetInt32Property(m_algorithmHandle,
                                                                   BCryptNative.ObjectPropertyName.ObjectLength);
                Debug.Assert(hashObjectSize > 0, "hashObjectSize > 0");
 
                // Allocate in a CER because we could fail between the alloc and the assignment
                RuntimeHelpers.PrepareConstrainedRegions();
                try { }
                finally {
                    hashObjectBuffer = Marshal.AllocCoTaskMem(hashObjectSize);
                }
 
                BCryptNative.ErrorCode error = BCryptNative.UnsafeNativeMethods.BCryptCreateHash(m_algorithmHandle,
                                                                                                 out newHashAlgorithm,
                                                                                                 hashObjectBuffer,
                                                                                                 hashObjectSize,
                                                                                                 IntPtr.Zero,
                                                                                                 0,
                                                                                                 0);
 
                if (error != BCryptNative.ErrorCode.Success) {
                    throw new CryptographicException((int)error);
                }
            }
            finally {
                // Make sure we've successfully transfered ownership of the hash object buffer to the safe handle
                if (hashObjectBuffer != IntPtr.Zero) {
                    // If we created the safe handle, it needs to own the buffer and free it in release
                    if (newHashAlgorithm != null) {
                        newHashAlgorithm.HashObject = hashObjectBuffer;
                    }
                    else {
                        Marshal.FreeCoTaskMem(hashObjectBuffer);
                    }
 
                }
 
            }
 
            // If we could create it, dispose of any old hash handle we had and replace it with the new one
            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;
            }
 
            BCryptNative.ErrorCode error;
 
            unsafe {
                fixed (byte* dataPtr = array) {
                    error = BCryptNative.UnsafeNativeMethods.BCryptHashData(m_hashHandle,
                                                                            dataPtr + ibStart,
                                                                            cbSize,
                                                                            0);
                }
            }
 
            if (error != BCryptNative.ErrorCode.Success) {
                throw new CryptographicException((int)error);
            }
        }
 
        /// <summary>
        ///     Complete the hash, returning its value
        /// </summary>
        [SecuritySafeCritical]
        public byte[] HashFinal() {
            Contract.Ensures(Contract.Result<byte[]>() != null);
            Contract.Assert(m_hashHandle != null);
 
            int hashSize = BCryptNative.GetInt32Property(m_hashHandle, BCryptNative.HashPropertyName.HashLength);
 
            byte[] hashValue = new byte[hashSize];
            BCryptNative.ErrorCode error = BCryptNative.UnsafeNativeMethods.BCryptFinishHash(m_hashHandle,
                                                                                             hashValue,
                                                                                             hashValue.Length,
                                                                                             0);
 
            if (error != BCryptNative.ErrorCode.Success) {
                throw new CryptographicException((int)error);
            }
 
            return hashValue;
        }
 
        [SecuritySafeCritical]
        public void HashStream(Stream stream) {
            Contract.Requires(stream != null);
 
            // Read the data 4KB at a time, providing similar read characteristics to a standard HashAlgorithm
            byte[] buffer = new byte[4096];
            int bytesRead = 0;
            do {
                bytesRead = stream.Read(buffer, 0, buffer.Length);
                if (bytesRead > 0) {
                    HashCore(buffer, 0, bytesRead);
                }
            } while (bytesRead > 0);
        }
    }
}