Microsoft.Azure.Cosmos/src/Authorization/SecureStringHMACSHA256Helper.cs (184 lines of code) (raw):

//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace Microsoft.Azure.Cosmos { using System; using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; using System.Security; using Microsoft.Azure.Cosmos.Core.Trace; /// <summary> /// Manufactures SHA256 HMACs of byte payloads using a key. The key is a Base64-encoded SecureString. /// In keeping with the goals of SecureString, neither the original Base64 characters nor the decoded /// bytes ever enters the managed heap, and they are kept decrypted in native memory for as short a /// time as possible: just the duration of a single ComputeHash call. /// </summary> internal sealed class SecureStringHMACSHA256Helper : IComputeHash { private const uint SHA256HashOutputSizeInBytes = 32; // SHA256 => 256 bits => 32 bytes private readonly SecureString key; private readonly int keyLength; private IntPtr algorithmHandle; public SecureStringHMACSHA256Helper(SecureString base64EncodedKey) { this.key = base64EncodedKey; // caching the length of SecureString as calling Length method on it everytime causes a performance hit this.keyLength = base64EncodedKey.Length; this.algorithmHandle = IntPtr.Zero; int status = NativeMethods.BCryptOpenAlgorithmProvider(out this.algorithmHandle, NativeMethods.BCRYPT_SHA256_ALGORITHM, IntPtr.Zero, NativeMethods.BCRYPT_ALG_HANDLE_HMAC_FLAG); if (status != 0) { throw new Win32Exception(status, "BCryptOpenAlgorithmProvider"); } } public SecureString Key { get { return this.key; } } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } ~SecureStringHMACSHA256Helper() => this.Dispose(false); private void Dispose(bool disposing) { if (this.algorithmHandle != IntPtr.Zero) { int status = NativeMethods.BCryptCloseAlgorithmProvider(this.algorithmHandle, 0); if (status != 0) { DefaultTrace.TraceError("Failed to close algorithm provider: {0}", status); } this.algorithmHandle = IntPtr.Zero; } } /// <summary> /// Decode the SecureString containing the Base64-encoded key into native memory, compute the /// SHA256 HMAC of the payload, and destroy the native memory containing the decoded key. /// </summary> /// <param name="bytesToHash">payload that is hashed</param> public byte[] ComputeHash(ArraySegment<byte> bytesToHash) { IntPtr hashHandle = IntPtr.Zero; try { this.InitializeBCryptHash(this.key, this.keyLength, out hashHandle); this.AddData(hashHandle, bytesToHash); return this.FinishHash(hashHandle); } finally { if (hashHandle != IntPtr.Zero) { int ignored = NativeMethods.BCryptDestroyHash(hashHandle); hashHandle = IntPtr.Zero; } } } private void AddData(IntPtr hashHandle, ArraySegment<byte> dataStream) { byte[] data = dataStream.Array; GCHandle h = GCHandle.Alloc(data, GCHandleType.Pinned); try { int status = NativeMethods.BCryptHashData(hashHandle, h.AddrOfPinnedObject(), (uint)dataStream.Count, 0); if (status != 0) { throw new Win32Exception(status, "BCryptHashData"); } } finally { h.Free(); } } private byte[] FinishHash(IntPtr hashHandle) { byte[] finishedHash = new byte[SecureStringHMACSHA256Helper.SHA256HashOutputSizeInBytes]; GCHandle h = GCHandle.Alloc(finishedHash, GCHandleType.Pinned); try { int status = NativeMethods.BCryptFinishHash(hashHandle, h.AddrOfPinnedObject(), (uint)finishedHash.Length, 0); if (status != 0) { throw new Win32Exception(status, "BCryptFinishData"); } } finally { h.Free(); } return finishedHash; } private void InitializeBCryptHash(SecureString base64EncodedPassword, int base64EncodedPasswordLength, out IntPtr hashHandle) { IntPtr keyBytes = IntPtr.Zero; uint keyBytesLength = 0; try { Base64Helper.SecureStringToNativeBytes(base64EncodedPassword, base64EncodedPasswordLength, out keyBytes, out keyBytesLength); int status = NativeMethods.BCryptCreateHash(this.algorithmHandle, out hashHandle, IntPtr.Zero, 0, keyBytes, keyBytesLength, 0); if (status != 0) { throw new Win32Exception(status, "BCryptCreateHash"); } } finally { if (keyBytes != IntPtr.Zero) { for (int n = 0; n < (int)keyBytesLength; n++) { Marshal.WriteByte(keyBytes, n, 0); } Marshal.FreeCoTaskMem(keyBytes); keyBytes = IntPtr.Zero; keyBytesLength = 0; } } } private static class NativeMethods { public const string BCRYPT_SHA256_ALGORITHM = "SHA256"; public const uint BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008; [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)] public static extern int BCryptOpenAlgorithmProvider( out IntPtr algorithmHandle, string algorithmId, IntPtr implementation, uint flags); [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)] public static extern int BCryptCloseAlgorithmProvider( IntPtr algorithmHandle, uint flags); [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)] public static extern int BCryptCreateHash( IntPtr algorithmHandle, out IntPtr hashHandle, IntPtr workingSpace, // optional, we just let BCrypt allocate uint workingSpaceSize, IntPtr keyBytes, uint keyBytesLength, uint flags); [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)] public static extern int BCryptDestroyHash( IntPtr hashHandle); [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)] public static extern int BCryptHashData( IntPtr hashHandle, IntPtr bytes, uint byteLength, uint flags); [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)] public static extern int BCryptFinishHash( IntPtr hashHandle, IntPtr byteOutputLocation, uint byteOutputLocationSize, uint flags); } } }