aspnetcore/src/Microsoft.AspNet.Security.D.../SP800_108Helper.cs

194 lines
9.7 KiB
C#

// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.Util;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Provides an implementation of the SP800-108-CTR-HMACSHA512 key derivation function.
/// This class assumes at least Windows 7 / Server 2008 R2.
/// </summary>
/// <remarks>
/// More info at http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf, Sec. 5.1.
/// </remarks>
internal unsafe static class SP800_108Helper
{
private const string BCRYPT_LIB = "bcrypt.dll";
[SuppressUnmanagedCodeSecurity]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
// http://msdn.microsoft.com/en-us/library/hh448506(v=vs.85).aspx
private delegate int BCryptKeyDerivation(
[In] BCryptKeyHandle hKey,
[In] BCryptBufferDesc* pParameterList,
[In] byte* pbDerivedKey,
[In] uint cbDerivedKey,
[Out] out uint pcbResult,
[In] uint dwFlags);
private static readonly BCryptAlgorithmHandle SP800108AlgorithmHandle;
private delegate void DeriveKeysDelegate(byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength);
private static DeriveKeysDelegate _thunk = CreateThunk(out SP800108AlgorithmHandle);
private static BCryptAlgorithmHandle CreateSP800108AlgorithmHandle()
{
// create the SP800-108 instance
BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SP800108_CTR_HMAC_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: 0);
if (status != 0 || algHandle == null || algHandle.IsInvalid)
{
throw new CryptographicException(status);
}
return algHandle;
}
private static DeriveKeysDelegate CreateThunk(out BCryptAlgorithmHandle sp800108AlgorithmHandle)
{
SafeLibraryHandle bcryptLibHandle = SafeLibraryHandle.Open(BCRYPT_LIB);
var win8Thunk = bcryptLibHandle.GetProcAddress<BCryptKeyDerivation>("BCryptKeyDerivation", throwIfNotFound: false);
if (win8Thunk != null)
{
// Permanently reference bcrypt.dll for the lifetime of the AppDomain.
// When the AD goes away the SafeLibraryHandle will automatically be released.
GCHandle.Alloc(bcryptLibHandle);
sp800108AlgorithmHandle = CreateSP800108AlgorithmHandle();
return win8Thunk.DeriveKeysWin8;
}
else
{
sp800108AlgorithmHandle = null;
return DeriveKeysWin7;
}
}
/// <summary>
/// Performs a key derivation using SP800-108-CTR-HMACSHA512.
/// </summary>
/// <param name="pKdk">Pointer to the key derivation key.</param>
/// <param name="kdkByteLength">Length (in bytes) of the key derivation key.</param>
/// <param name="purpose">Purpose to attach to the generated subkey. Corresponds to the 'Label' parameter
/// in the KDF. May be null.</param>
/// <param name="pOutputBuffer">Pointer to a buffer which will receive the subkey.</param>
/// <param name="outputBufferByteLength">Length (in bytes) of the output buffer.</param>
public static void DeriveKeys(byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength)
{
_thunk(pKdk, kdkByteLength, purpose, pOutputBuffer, outputBufferByteLength);
}
// Wraps our own SP800-108 implementation around bcrypt.dll primitives.
private static void DeriveKeysWin7(byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength)
{
const int TEMP_RESULT_OUTPUT_BYTES = 512 / 8; // hardcoded to HMACSHA512
// NOTE: pOutputBuffer and outputBufferByteLength are modified as data is copied from temporary buffers
// to the final output buffer.
// used to hold the output of the HMACSHA512 routine
byte* pTempResultBuffer = stackalloc byte[TEMP_RESULT_OUTPUT_BYTES];
int purposeLength = (purpose != null) ? purpose.Length : 0;
// this will be zero-inited
byte[] dataToBeHashed = new byte[checked(
sizeof(int) /* [i] */
+ purposeLength /* Label */
+ 1 /* 0x00 */
+ 0 /* Context */
+ sizeof(int) /* [L] */)];
fixed (byte* pDataToBeHashed = dataToBeHashed)
{
// Step 1: copy purpose into Label part of data to be hashed
if (purposeLength > 0)
{
fixed (byte* pPurpose = purpose)
{
BufferUtil.BlockCopy(from: pPurpose, to: &pDataToBeHashed[sizeof(int)], byteCount: purposeLength);
}
}
// Step 2: copy [L] into last part of data to be hashed, big-endian
uint numBitsToGenerate = checked(outputBufferByteLength * 8);
MemoryUtil.UnalignedWriteBigEndian(&pDataToBeHashed[dataToBeHashed.Length - sizeof(int)], numBitsToGenerate);
// Step 3: iterate until all desired bytes have been generated
for (int i = 1; outputBufferByteLength > 0; i++)
{
// Step 3a: Copy [i] into the first part of data to be hashed, big-endian
MemoryUtil.UnalignedWriteBigEndian(pDataToBeHashed, (uint)i);
// Step 3b: Hash. Win7 doesn't allow reusing hash algorithm objects after the final hash
// has been computed, so we need to create a new instance of the hash object for each
// iteration. We don't bother with this optimization on Win8 since we call BCryptKeyDerivation
// instead when on that OS.
using (var hashHandle = BCryptUtil.CreateHMACHandle(Algorithms.HMACSHA512AlgorithmHandle, pKdk, kdkByteLength))
{
BCryptUtil.HashData(hashHandle, pDataToBeHashed, dataToBeHashed.Length, pTempResultBuffer, TEMP_RESULT_OUTPUT_BYTES);
}
// Step 3c: Copy bytes from the temporary buffer to the output buffer.
uint numBytesToCopy = Math.Min(outputBufferByteLength, (uint)TEMP_RESULT_OUTPUT_BYTES);
BufferUtil.BlockCopy(from: pTempResultBuffer, to: pOutputBuffer, byteCount: numBytesToCopy);
pOutputBuffer += numBytesToCopy;
outputBufferByteLength -= numBytesToCopy;
}
}
}
// Calls into the Win8 implementation (bcrypt.dll) for the SP800-108 KDF
private static void DeriveKeysWin8(this BCryptKeyDerivation fnKeyDerivation, byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength)
{
// Create a buffer to hold the hash algorithm name
fixed (char* pszPrfAlgorithmName = Constants.BCRYPT_SHA512_ALGORITHM)
{
BCryptBuffer* pBCryptBuffers = stackalloc BCryptBuffer[2];
// The first buffer should contain the PRF algorithm name (hardcoded to HMACSHA512).
// Per http://msdn.microsoft.com/en-us/library/aa375368(v=vs.85).aspx, cbBuffer must include the terminating null char.
pBCryptBuffers[0].BufferType = BCryptKeyDerivationBufferType.KDF_HASH_ALGORITHM;
pBCryptBuffers[0].pvBuffer = (IntPtr)pszPrfAlgorithmName;
pBCryptBuffers[0].cbBuffer = (uint)((Constants.BCRYPT_SHA512_ALGORITHM.Length + 1) * sizeof(char));
uint numBuffers = 1;
fixed (byte* pPurpose = ((purpose != null && purpose.Length != 0) ? purpose : null))
{
if (pPurpose != null)
{
// The second buffer will hold the purpose bytes if they're specified.
pBCryptBuffers[1].BufferType = BCryptKeyDerivationBufferType.KDF_LABEL;
pBCryptBuffers[1].pvBuffer = (IntPtr)pPurpose;
pBCryptBuffers[1].cbBuffer = (uint)purpose.Length;
numBuffers = 2;
}
// Add the header
BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
BCryptBufferDesc.Initialize(ref bufferDesc);
bufferDesc.cBuffers = numBuffers;
bufferDesc.pBuffers = pBCryptBuffers;
// Finally, perform the calculation and validate that the actual number of bytes derived matches
// the number that the caller requested.
uint numBytesDerived;
int status;
using (BCryptKeyHandle kdkHandle = BCryptUtil.ImportKey(SP800108AlgorithmHandle, pKdk, kdkByteLength))
{
status = fnKeyDerivation(kdkHandle, &bufferDesc, pOutputBuffer, outputBufferByteLength, out numBytesDerived, dwFlags: 0);
}
if (status != 0 || numBytesDerived != outputBufferByteLength)
{
throw new CryptographicException(status);
}
}
}
}
}
}