Merge from internal DataProtection repo.

This commit is contained in:
GrabYourPitchforks 2014-04-15 18:03:41 -07:00
parent 26fedbb999
commit 1959aa9e7f
32 changed files with 647 additions and 312 deletions

View File

@ -3,12 +3,11 @@ using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection
{ {
internal static unsafe class Algorithms internal unsafe static class Algorithms
{ {
public static readonly BCryptAlgorithmHandle AESAlgorithmHandle = CreateAESAlgorithmHandle(); public static readonly BCryptAlgorithmHandle AESAlgorithmHandle = CreateAESAlgorithmHandle();
public static readonly BCryptAlgorithmHandle HMACSHA256AlgorithmHandle = CreateHMACSHA256AlgorithmHandle(); public static readonly BCryptAlgorithmHandle HMACSHA256AlgorithmHandle = CreateHMACSHA256AlgorithmHandle();
public static readonly BCryptAlgorithmHandle HMACSHA512AlgorithmHandle = CreateHMACSHA512AlgorithmHandle(); public static readonly BCryptAlgorithmHandle HMACSHA512AlgorithmHandle = CreateHMACSHA512AlgorithmHandle();
public static readonly BCryptAlgorithmHandle SP800108AlgorithmHandle = CreateSP800108AlgorithmHandle();
private static BCryptAlgorithmHandle CreateAESAlgorithmHandle() private static BCryptAlgorithmHandle CreateAESAlgorithmHandle()
{ {
@ -23,7 +22,7 @@ namespace Microsoft.AspNet.Security.DataProtection
// change it to use CBC chaining; it already uses PKCS7 padding by default // change it to use CBC chaining; it already uses PKCS7 padding by default
fixed (char* pCbcMode = Constants.BCRYPT_CHAIN_MODE_CBC) fixed (char* pCbcMode = Constants.BCRYPT_CHAIN_MODE_CBC)
{ {
status = UnsafeNativeMethods.BCryptSetProperty(algHandle, Constants.BCRYPT_CHAINING_MODE, (IntPtr) pCbcMode, (uint) ((Constants.BCRYPT_CHAIN_MODE_CBC.Length + 1 /* trailing null */)*sizeof (char)), dwFlags: 0); status = UnsafeNativeMethods.BCryptSetProperty(algHandle, Constants.BCRYPT_CHAINING_MODE, (IntPtr)pCbcMode, (uint)((Constants.BCRYPT_CHAIN_MODE_CBC.Length + 1 /* trailing null */) * sizeof(char)), dwFlags: 0);
} }
if (status != 0) if (status != 0)
{ {
@ -32,11 +31,11 @@ namespace Microsoft.AspNet.Security.DataProtection
return algHandle; return algHandle;
} }
private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle()
internal static BCryptAlgorithmHandle CreateGenericHMACHandleFromPrimitiveProvider(string algorithmName)
{ {
// create the HMACSHA-256 instance
BCryptAlgorithmHandle algHandle; BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, algorithmName, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG); int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SHA256_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG);
if (status != 0 || algHandle == null || algHandle.IsInvalid) if (status != 0 || algHandle == null || algHandle.IsInvalid)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);
@ -45,23 +44,11 @@ namespace Microsoft.AspNet.Security.DataProtection
return algHandle; return algHandle;
} }
private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle()
{
// create the HMACSHA-256 instance
return CreateGenericHMACHandleFromPrimitiveProvider(Constants.BCRYPT_SHA256_ALGORITHM);
}
private static BCryptAlgorithmHandle CreateHMACSHA512AlgorithmHandle() private static BCryptAlgorithmHandle CreateHMACSHA512AlgorithmHandle()
{ {
// create the HMACSHA-512 instance // create the HMACSHA-512 instance
return CreateGenericHMACHandleFromPrimitiveProvider(Constants.BCRYPT_SHA512_ALGORITHM);
}
private static BCryptAlgorithmHandle CreateSP800108AlgorithmHandle()
{
// create the SP800-108 instance
BCryptAlgorithmHandle algHandle; BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SP800108_CTR_HMAC_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: 0); int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SHA512_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG);
if (status != 0 || algHandle == null || algHandle.IsInvalid) if (status != 0 || algHandle == null || algHandle.IsInvalid)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);

View File

@ -1,13 +1,21 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNet.Security.DataProtection.Util; using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection
{ {
internal static unsafe class BCryptUtil internal unsafe static class BCryptUtil
{ {
// from dpapi.h
const uint CRYPTPROTECTMEMORY_BLOCK_SIZE = 16;
const uint CRYPTPROTECTMEMORY_SAME_PROCESS = 0x00;
private static readonly UTF8Encoding _secureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
// constant-time buffer comparison // constant-time buffer comparison
[MethodImpl(MethodImplOptions.NoOptimization)] [MethodImpl(MethodImplOptions.NoOptimization)]
public static bool BuffersAreEqualSecure(byte* p1, byte* p2, uint count) public static bool BuffersAreEqualSecure(byte* p1, byte* p2, uint count)
@ -23,22 +31,22 @@ namespace Microsoft.AspNet.Security.DataProtection
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(int input) private static void CheckOverflowUnderflow(int input)
{ {
var unused = checked((uint) input); var unused = checked((uint)input);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(uint input) private static void CheckOverflowUnderflow(uint input)
{ {
var unused = checked((int) input); var unused = checked((int)input);
} }
// helper function to wrap BCryptCreateHash // helper function to wrap BCryptCreateHash, passing in a key used for HMAC
public static BCryptHashHandle CreateHash(BCryptAlgorithmHandle algorithmHandle, byte* key, int keyLengthInBytes) public static BCryptHashHandle CreateHMACHandle(BCryptAlgorithmHandle algorithmHandle, byte* key, int keyLengthInBytes)
{ {
CheckOverflowUnderflow(keyLengthInBytes); CheckOverflowUnderflow(keyLengthInBytes);
BCryptHashHandle retVal; BCryptHashHandle retVal;
int status = UnsafeNativeMethods.BCryptCreateHash(algorithmHandle, out retVal, IntPtr.Zero, 0, key, (uint) keyLengthInBytes, dwFlags: 0); int status = UnsafeNativeMethods.BCryptCreateHash(algorithmHandle, out retVal, IntPtr.Zero, 0, key, (uint)keyLengthInBytes, dwFlags: 0);
if (status != 0 || retVal == null || retVal.IsInvalid) if (status != 0 || retVal == null || retVal.IsInvalid)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);
@ -61,24 +69,24 @@ namespace Microsoft.AspNet.Security.DataProtection
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
byte* pDuplicatedIV = stackalloc byte[ivLength]; byte* pDuplicatedIV = stackalloc byte[ivLength];
BufferUtil.BlockCopy(from: (IntPtr) iv, to: (IntPtr) pDuplicatedIV, byteCount: ivLength); BufferUtil.BlockCopy(from: iv, to: pDuplicatedIV, byteCount: ivLength);
uint retVal; uint retVal;
int status = UnsafeNativeMethods.BCryptDecrypt(keyHandle, input, (uint) inputLength, IntPtr.Zero, pDuplicatedIV, (uint) ivLength, output, (uint) outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING); int status = UnsafeNativeMethods.BCryptDecrypt(keyHandle, input, (uint)inputLength, IntPtr.Zero, pDuplicatedIV, (uint)ivLength, output, (uint)outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
if (status != 0) if (status != 0)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);
} }
return checked((int) retVal); return checked((int)retVal);
} }
// helper function to wrap BCryptKeyDerivation using SP800-108-CTR-HMAC-SHA512 // helper function to wrap BCryptKeyDerivation using SP800-108-CTR-HMAC-SHA512
public static void DeriveKeysSP800108(BCryptAlgorithmHandle kdfAlgorithmHandle, BCryptKeyHandle keyHandle, string purpose, BCryptAlgorithmHandle encryptionAlgorithmHandle, out BCryptKeyHandle encryptionKeyHandle, BCryptAlgorithmHandle hashAlgorithmHandle, out BCryptHashHandle hmacHandle, out BCryptKeyHandle kdfKeyHandle) public static void DeriveKeysSP800108(byte[] protectedKdk, string purpose, BCryptAlgorithmHandle encryptionAlgorithmHandle, out BCryptKeyHandle encryptionKeyHandle, BCryptAlgorithmHandle hashAlgorithmHandle, out BCryptHashHandle hmacHandle, out byte[] kdfSubkey)
{ {
const int ENCRYPTION_KEY_SIZE_IN_BYTES = 256/8; const int ENCRYPTION_KEY_SIZE_IN_BYTES = 256 / 8;
const int HMAC_KEY_SIZE_IN_BYTES = 256/8; const int HMAC_KEY_SIZE_IN_BYTES = 256 / 8;
const int KDF_SUBKEY_SIZE_IN_BYTES = 512/8; const int KDF_SUBKEY_SIZE_IN_BYTES = 512 / 8;
const int TOTAL_NUM_BYTES_TO_DERIVE = ENCRYPTION_KEY_SIZE_IN_BYTES + HMAC_KEY_SIZE_IN_BYTES + KDF_SUBKEY_SIZE_IN_BYTES; const int TOTAL_NUM_BYTES_TO_DERIVE = ENCRYPTION_KEY_SIZE_IN_BYTES + HMAC_KEY_SIZE_IN_BYTES + KDF_SUBKEY_SIZE_IN_BYTES;
// keep our buffers on the stack while we're generating key material // keep our buffers on the stack while we're generating key material
@ -87,50 +95,27 @@ namespace Microsoft.AspNet.Security.DataProtection
byte* pNewHmacKey = &pNewEncryptionKey[ENCRYPTION_KEY_SIZE_IN_BYTES]; byte* pNewHmacKey = &pNewEncryptionKey[ENCRYPTION_KEY_SIZE_IN_BYTES];
byte* pNewKdfSubkey = &pNewHmacKey[HMAC_KEY_SIZE_IN_BYTES]; byte* pNewKdfSubkey = &pNewHmacKey[HMAC_KEY_SIZE_IN_BYTES];
try protectedKdk = (byte[])protectedKdk.Clone(); // CryptUnprotectMemory mutates its input, so we preserve the original
fixed (byte* pKdk = protectedKdk)
{ {
fixed (char* pszPrfAlgorithmName = Constants.BCRYPT_SHA512_ALGORITHM) try
{ {
// Create a buffer to hold the hash algorithm name, currently hardcoded to HMACSHA512 // Since the KDK is pinned, the GC won't move around the array containing the plaintext key before we
uint numBuffers = 1; // have the opportunity to clear its contents.
BCryptBuffer* pBCryptBuffers = stackalloc BCryptBuffer[2]; UnprotectMemoryWithinThisProcess(pKdk, (uint)protectedKdk.Length);
pBCryptBuffers[0].BufferType = BCryptKeyDerivationBufferType.KDF_HASH_ALGORITHM;
pBCryptBuffers[0].pvBuffer = (IntPtr) pszPrfAlgorithmName;
pBCryptBuffers[0].cbBuffer = (uint) ((Constants.BCRYPT_SHA512_ALGORITHM.Length + 1)*sizeof (char)); // per http://msdn.microsoft.com/en-us/library/windows/desktop/aa375368(v=vs.85).aspx, need to include terminating null
fixed (char* pszPurpose = (String.IsNullOrEmpty(purpose) ? (string) null : purpose))
{
// Create a buffer to hold the purpose string if it is specified (we'll treat it as UTF-16LE)
if (pszPurpose != null)
{
numBuffers = 2;
pBCryptBuffers[1].BufferType = BCryptKeyDerivationBufferType.KDF_LABEL;
pBCryptBuffers[1].pvBuffer = (IntPtr) pszPurpose;
pBCryptBuffers[1].cbBuffer = checked((uint) (purpose.Length*sizeof (char)));
}
// .. and the header .. byte[] purposeBytes = (!String.IsNullOrEmpty(purpose)) ? _secureUtf8Encoding.GetBytes(purpose) : null;
BCryptBufferDesc bufferDesc = default(BCryptBufferDesc); SP800_108Helper.DeriveKeys(pKdk, protectedKdk.Length, purposeBytes, pBuffer, TOTAL_NUM_BYTES_TO_DERIVE);
BCryptBufferDesc.Initialize(ref bufferDesc);
bufferDesc.cBuffers = numBuffers;
bufferDesc.pBuffers = pBCryptBuffers;
uint numBytesDerived; // Split into AES, HMAC, and KDF subkeys
int status = UnsafeNativeMethods.BCryptKeyDerivation(keyHandle, &bufferDesc, pBuffer, TOTAL_NUM_BYTES_TO_DERIVE, out numBytesDerived, dwFlags: 0); encryptionKeyHandle = ImportKey(encryptionAlgorithmHandle, pNewEncryptionKey, ENCRYPTION_KEY_SIZE_IN_BYTES);
if (status != 0 || numBytesDerived != TOTAL_NUM_BYTES_TO_DERIVE) hmacHandle = CreateHMACHandle(hashAlgorithmHandle, pNewHmacKey, HMAC_KEY_SIZE_IN_BYTES);
{ kdfSubkey = BufferUtil.ToProtectedManagedByteArray(pNewKdfSubkey, KDF_SUBKEY_SIZE_IN_BYTES);
throw new CryptographicException(status); }
} finally
} {
BufferUtil.SecureZeroMemory(pKdk, protectedKdk.Length);
} }
// At this point, we have all the bytes we need.
encryptionKeyHandle = ImportKey(encryptionAlgorithmHandle, pNewEncryptionKey, ENCRYPTION_KEY_SIZE_IN_BYTES);
hmacHandle = CreateHash(hashAlgorithmHandle, pNewHmacKey, HMAC_KEY_SIZE_IN_BYTES);
kdfKeyHandle = ImportKey(kdfAlgorithmHandle, pNewKdfSubkey, KDF_SUBKEY_SIZE_IN_BYTES);
}
finally
{
BufferUtil.ZeroMemory(pBuffer, TOTAL_NUM_BYTES_TO_DERIVE);
} }
} }
@ -161,16 +146,16 @@ namespace Microsoft.AspNet.Security.DataProtection
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
byte* pDuplicatedIV = stackalloc byte[ivLength]; byte* pDuplicatedIV = stackalloc byte[ivLength];
BufferUtil.BlockCopy(from: (IntPtr) iv, to: (IntPtr) pDuplicatedIV, byteCount: ivLength); BufferUtil.BlockCopy(from: iv, to: pDuplicatedIV, byteCount: ivLength);
uint retVal; uint retVal;
int status = UnsafeNativeMethods.BCryptEncrypt(keyHandle, input, (uint) inputLength, IntPtr.Zero, pDuplicatedIV, (uint) ivLength, output, (uint) outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING); int status = UnsafeNativeMethods.BCryptEncrypt(keyHandle, input, (uint)inputLength, IntPtr.Zero, pDuplicatedIV, (uint)ivLength, output, (uint)outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
if (status != 0) if (status != 0)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);
} }
return checked((int) retVal); return checked((int)retVal);
} }
// helper function to take a key, apply a purpose, and generate a new subkey ("entropy") for DPAPI-specific scenarios // helper function to take a key, apply a purpose, and generate a new subkey ("entropy") for DPAPI-specific scenarios
@ -183,7 +168,7 @@ namespace Microsoft.AspNet.Security.DataProtection
BCryptHashHandle hashHandle; BCryptHashHandle hashHandle;
fixed (byte* pPreviousKey = previousKey) fixed (byte* pPreviousKey = previousKey)
{ {
hashHandle = CreateHash(Algorithms.HMACSHA256AlgorithmHandle, pPreviousKey, previousKey.Length); hashHandle = CreateHMACHandle(Algorithms.HMACSHA256AlgorithmHandle, pPreviousKey, previousKey.Length);
} }
// hash the purpose string, treating it as UTF-16LE // hash the purpose string, treating it as UTF-16LE
@ -206,7 +191,7 @@ namespace Microsoft.AspNet.Security.DataProtection
{ {
CheckOverflowUnderflow(bufferBytes); CheckOverflowUnderflow(bufferBytes);
int status = UnsafeNativeMethods.BCryptGenRandom(IntPtr.Zero, buffer, (uint) bufferBytes, BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG); int status = UnsafeNativeMethods.BCryptGenRandom(IntPtr.Zero, buffer, (uint)bufferBytes, BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (status != 0) if (status != 0)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);
@ -219,13 +204,13 @@ namespace Microsoft.AspNet.Security.DataProtection
CheckOverflowUnderflow(inputBytes); CheckOverflowUnderflow(inputBytes);
CheckOverflowUnderflow(outputBytes); CheckOverflowUnderflow(outputBytes);
int status = UnsafeNativeMethods.BCryptHashData(hashHandle, input, (uint) inputBytes, dwFlags: 0); int status = UnsafeNativeMethods.BCryptHashData(hashHandle, input, (uint)inputBytes, dwFlags: 0);
if (status != 0) if (status != 0)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);
} }
status = UnsafeNativeMethods.BCryptFinishHash(hashHandle, output, (uint) outputBytes, dwFlags: 0); status = UnsafeNativeMethods.BCryptFinishHash(hashHandle, output, (uint)outputBytes, dwFlags: 0);
if (status != 0) if (status != 0)
{ {
throw new CryptographicException(status); throw new CryptographicException(status);
@ -238,7 +223,7 @@ namespace Microsoft.AspNet.Security.DataProtection
CheckOverflowUnderflow(keyBytes); CheckOverflowUnderflow(keyBytes);
byte[] heapAllocatedKeyDataBlob = null; byte[] heapAllocatedKeyDataBlob = null;
int numBytesRequiredForKeyDataBlob = checked(keyBytes + sizeof (BCRYPT_KEY_DATA_BLOB_HEADER)); int numBytesRequiredForKeyDataBlob = checked(keyBytes + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER));
if (numBytesRequiredForKeyDataBlob > Constants.MAX_STACKALLOC_BYTES) if (numBytesRequiredForKeyDataBlob > Constants.MAX_STACKALLOC_BYTES)
{ {
heapAllocatedKeyDataBlob = new byte[numBytesRequiredForKeyDataBlob]; // allocate on heap if we cannot allocate on stack heapAllocatedKeyDataBlob = new byte[numBytesRequiredForKeyDataBlob]; // allocate on heap if we cannot allocate on stack
@ -248,28 +233,28 @@ namespace Microsoft.AspNet.Security.DataProtection
BCryptKeyHandle retVal; BCryptKeyHandle retVal;
fixed (byte* pHeapAllocatedKeyDataBlob = heapAllocatedKeyDataBlob) fixed (byte* pHeapAllocatedKeyDataBlob = heapAllocatedKeyDataBlob)
{ {
// The header is first // The header is first; if it wasn't heap-allocated we can stack-allocate now
BCRYPT_KEY_DATA_BLOB_HEADER* pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*) pHeapAllocatedKeyDataBlob; BCRYPT_KEY_DATA_BLOB_HEADER* pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*)pHeapAllocatedKeyDataBlob;
if (pKeyDataBlobHeader == null) if (pKeyDataBlobHeader == null)
{ {
byte* temp = stackalloc byte[numBytesRequiredForKeyDataBlob]; // won't be released until frame pops byte* temp = stackalloc byte[numBytesRequiredForKeyDataBlob]; // won't be released until frame pops
pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*) temp; pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*)temp;
} }
BCRYPT_KEY_DATA_BLOB_HEADER.Initialize(ref *pKeyDataBlobHeader); BCRYPT_KEY_DATA_BLOB_HEADER.Initialize(ref *pKeyDataBlobHeader);
pKeyDataBlobHeader->cbKeyData = (uint) keyBytes; pKeyDataBlobHeader->cbKeyData = (uint)keyBytes;
// the raw material immediately follows the header // the raw material immediately follows the header
byte* pKeyDataRawMaterial = (byte*) (&pKeyDataBlobHeader[1]); byte* pKeyDataRawMaterial = (byte*)(&pKeyDataBlobHeader[1]);
try try
{ {
BufferUtil.BlockCopy(from: (IntPtr) key, to: (IntPtr) pKeyDataRawMaterial, byteCount: keyBytes); BufferUtil.BlockCopy(from: key, to: pKeyDataRawMaterial, byteCount: keyBytes);
status = UnsafeNativeMethods.BCryptImportKey(algHandle, IntPtr.Zero, Constants.BCRYPT_KEY_DATA_BLOB, out retVal, IntPtr.Zero, 0, (byte*) pKeyDataBlobHeader, (uint) numBytesRequiredForKeyDataBlob, dwFlags: 0); status = UnsafeNativeMethods.BCryptImportKey(algHandle, IntPtr.Zero, Constants.BCRYPT_KEY_DATA_BLOB, out retVal, IntPtr.Zero, 0, (byte*)pKeyDataBlobHeader, (uint)numBytesRequiredForKeyDataBlob, dwFlags: 0);
} }
finally finally
{ {
// zero out the key we just copied // zero out the key we just copied
BufferUtil.ZeroMemory(pKeyDataRawMaterial, keyBytes); BufferUtil.SecureZeroMemory(pKeyDataRawMaterial, keyBytes);
} }
} }
@ -279,5 +264,29 @@ namespace Microsoft.AspNet.Security.DataProtection
} }
return retVal; return retVal;
} }
internal static void ProtectMemoryWithinThisProcess(byte* pBuffer, uint bufferLength)
{
Debug.Assert(pBuffer != null);
Debug.Assert(bufferLength % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0, "Input buffer size must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE.");
bool success = UnsafeNativeMethods.CryptProtectMemory(pBuffer, bufferLength, CRYPTPROTECTMEMORY_SAME_PROCESS);
if (!success)
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}
internal static void UnprotectMemoryWithinThisProcess(byte* pBuffer, uint bufferLength)
{
Debug.Assert(pBuffer != null);
Debug.Assert(bufferLength % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0, "Input buffer size must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE.");
bool success = UnsafeNativeMethods.CryptUnprotectMemory(pBuffer, bufferLength, CRYPTPROTECTMEMORY_SAME_PROCESS);
if (!success)
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}
} }
} }

View File

@ -1,15 +0,0 @@
using System;
#if !NET45
namespace System.Security.Cryptography {
internal sealed class CryptographicException : Exception {
internal CryptographicException(string message)
: base(message) {
}
internal CryptographicException(int unused) {
}
}
}
#endif

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection

View File

@ -1,41 +1,38 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Text;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.AspNet.Security.DataProtection.Util; using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection
{ {
public static unsafe class DataProtectionProvider /// <summary>
/// Provides methods for creating IDataProtectionProvider instances.
/// </summary>
public unsafe static class DataProtectionProvider
{ {
private const int MASTER_KEY_REQUIRED_LENGTH = 512/8; const int MASTER_KEY_REQUIRED_LENGTH = 512 / 8;
private static readonly byte[] MASTER_SUBKEY_GENERATOR = GetMasterSubkeyGenerator(); private static readonly byte[] MASTER_SUBKEY_GENERATOR = Encoding.ASCII.GetBytes("Microsoft.AspNet.Security.DataProtection");
private static readonly byte[] MASTER_DPAPI_ENTROPY = GetMasterSubkeyGenerator(isDpapi: true);
private static byte[] GetMasterSubkeyGenerator(bool isDpapi = false) /// <summary>
/// Creates a new IDataProtectionProvider backed by DPAPI, where the protected
/// payload can only be decrypted by the current user.
/// </summary>
public static IDataProtectionProvider CreateFromDpapi()
{ {
TypeInfo typeInfo = ((isDpapi) ? typeof(DpapiDataProtectionProviderImpl) : typeof(DataProtectionProvider)).GetTypeInfo(); return CreateFromDpapi(protectToLocalMachine: false);
byte[] retVal = new byte[sizeof (Guid)*2];
fixed (byte* pRetVal = retVal)
{
Guid* guids = (Guid*) pRetVal;
guids[0] = typeInfo.GUID;
#if NET45
guids[1] = typeInfo.Module.ModuleVersionId;
#else
guids[1] = default(Guid);
#endif
}
return retVal;
} }
/// <summary> /// <summary>
/// Creates a new IDataProtectionProvider backed by DPAPI. /// Creates a new IDataProtectionProvider backed by DPAPI.
/// </summary> /// </summary>
public static IDataProtectionProvider CreateFromDpapi() /// <param name="protectToLocalMachine">True if protected payloads can be decrypted by any user
/// on the local machine, false if protected payloads should only be able to decrypted by the
/// current user account.</param>
public static IDataProtectionProvider CreateFromDpapi(bool protectToLocalMachine)
{ {
return new DpapiDataProtectionProviderImpl(MASTER_DPAPI_ENTROPY); return new DpapiDataProtectionProviderImpl(MASTER_SUBKEY_GENERATOR, protectToLocalMachine);
} }
/// <summary> /// <summary>
@ -51,7 +48,7 @@ namespace Microsoft.AspNet.Security.DataProtection
} }
finally finally
{ {
BufferUtil.ZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH); BufferUtil.SecureZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH);
} }
} }
@ -82,16 +79,19 @@ namespace Microsoft.AspNet.Security.DataProtection
byte* masterSubkey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH]; byte* masterSubkey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH];
try try
{ {
using (var hashHandle = BCryptUtil.CreateHash(Algorithms.HMACSHA512AlgorithmHandle, masterKey, masterKeyLengthInBytes)) using (var hashHandle = BCryptUtil.CreateHMACHandle(Algorithms.HMACSHA512AlgorithmHandle, masterKey, masterKeyLengthInBytes))
{ {
BCryptUtil.HashData(hashHandle, masterKey, masterKeyLengthInBytes, masterSubkey, MASTER_KEY_REQUIRED_LENGTH); fixed (byte* pMasterSubkeyGenerator = MASTER_SUBKEY_GENERATOR)
{
BCryptUtil.HashData(hashHandle, pMasterSubkeyGenerator, MASTER_SUBKEY_GENERATOR.Length, masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
}
} }
BCryptKeyHandle kdfSubkeyHandle = BCryptUtil.ImportKey(Algorithms.SP800108AlgorithmHandle, masterSubkey, MASTER_KEY_REQUIRED_LENGTH); byte[] protectedKdk = BufferUtil.ToProtectedManagedByteArray(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
return new DataProtectionProviderImpl(kdfSubkeyHandle); return new DataProtectionProviderImpl(protectedKdk);
} }
finally finally
{ {
BufferUtil.ZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH); BufferUtil.SecureZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
} }
} }
} }

View File

@ -2,28 +2,28 @@
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection
{ {
internal sealed unsafe class DataProtectionProviderImpl : IDataProtectionProvider internal unsafe sealed class DataProtectionProviderImpl : IDataProtectionProvider
{ {
private readonly BCryptKeyHandle _kdfSubkeyHandle; private readonly byte[] _protectedKdk;
public DataProtectionProviderImpl(BCryptKeyHandle kdfSubkeyHandle) public DataProtectionProviderImpl(byte[] protectedKdk)
{ {
_kdfSubkeyHandle = kdfSubkeyHandle; _protectedKdk = protectedKdk;
} }
public IDataProtector CreateProtector(string purpose) public IDataProtector CreateProtector(string purpose)
{ {
BCryptKeyHandle newAesKeyHandle; BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle; BCryptHashHandle newHmacHashHandle;
BCryptKeyHandle newKdfSubkeyHandle; byte[] newProtectedKdfSubkey;
BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle); BCryptUtil.DeriveKeysSP800108(_protectedKdk, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newProtectedKdfSubkey);
return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle); return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newProtectedKdfSubkey);
} }
public void Dispose() public void Dispose()
{ {
_kdfSubkeyHandle.Dispose(); // no-op: we hold no protected resources
} }
} }
} }

View File

@ -5,29 +5,37 @@ using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection
{ {
internal sealed unsafe class DataProtectorImpl : IDataProtector internal unsafe sealed class DataProtectorImpl : IDataProtector
{ {
private const int AES_BLOCK_LENGTH_IN_BYTES = 128/8; private const int AES_BLOCK_LENGTH_IN_BYTES = 128 / 8;
private const int MAC_LENGTH_IN_BYTES = 256/8; private const int AES_IV_LENGTH_IN_BYTES = AES_BLOCK_LENGTH_IN_BYTES;
private const int MAC_LENGTH_IN_BYTES = 256 / 8;
private readonly BCryptKeyHandle _aesKeyHandle; private readonly BCryptKeyHandle _aesKeyHandle;
private readonly BCryptHashHandle _hmacHashHandle; private readonly BCryptHashHandle _hmacHashHandle;
private readonly BCryptKeyHandle _kdfSubkeyHandle; private readonly byte[] _protectedKdk;
public DataProtectorImpl(BCryptKeyHandle aesKeyHandle, BCryptHashHandle hmacHashHandle, BCryptKeyHandle kdfSubkeyHandle) public DataProtectorImpl(BCryptKeyHandle aesKeyHandle, BCryptHashHandle hmacHashHandle, byte[] protectedKdk)
{ {
_aesKeyHandle = aesKeyHandle; _aesKeyHandle = aesKeyHandle;
_hmacHashHandle = hmacHashHandle; _hmacHashHandle = hmacHashHandle;
_kdfSubkeyHandle = kdfSubkeyHandle; _protectedKdk = protectedKdk;
} }
private static int CalculateTotalProtectedDataSize(int unprotectedDataSize) private static int CalculateTotalProtectedDataSize(int unprotectedDataSizeInBytes)
{ {
Debug.Assert(unprotectedDataSize >= 0); Debug.Assert(unprotectedDataSizeInBytes >= 0);
// Calculates checked
int numFullBlocks = unprotectedDataSize/AES_BLOCK_LENGTH_IN_BYTES; {
return checked(AES_BLOCK_LENGTH_IN_BYTES /* IV */+ (numFullBlocks + 1)*AES_BLOCK_LENGTH_IN_BYTES /* ciphertext w/ padding */+ MAC_LENGTH_IN_BYTES /* HMAC */); // Padding always rounds the block count up, never down.
// If the input size is already a multiple of the block length, a block is added.
int numBlocks = 1 + unprotectedDataSizeInBytes / AES_BLOCK_LENGTH_IN_BYTES;
return
AES_IV_LENGTH_IN_BYTES /* IV */
+ numBlocks * AES_BLOCK_LENGTH_IN_BYTES /* ciphertext with padding */
+ MAC_LENGTH_IN_BYTES /* MAC */;
}
} }
private static CryptographicException CreateGenericCryptographicException() private static CryptographicException CreateGenericCryptographicException()
@ -39,17 +47,16 @@ namespace Microsoft.AspNet.Security.DataProtection
{ {
BCryptKeyHandle newAesKeyHandle; BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle; BCryptHashHandle newHmacHashHandle;
BCryptKeyHandle newKdfSubkeyHandle; byte[] newProtectedKdfSubkey;
BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle); BCryptUtil.DeriveKeysSP800108(_protectedKdk, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newProtectedKdfSubkey);
return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle); return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newProtectedKdfSubkey);
} }
public void Dispose() public void Dispose()
{ {
_aesKeyHandle.Dispose(); _aesKeyHandle.Dispose();
_hmacHashHandle.Dispose(); _hmacHashHandle.Dispose();
_kdfSubkeyHandle.Dispose();
} }
public byte[] Protect(byte[] unprotectedData) public byte[] Protect(byte[] unprotectedData)
@ -66,14 +73,14 @@ namespace Microsoft.AspNet.Security.DataProtection
{ {
// first, generate a random IV for CBC mode encryption // first, generate a random IV for CBC mode encryption
byte* pIV = pProtectedData; byte* pIV = pProtectedData;
BCryptUtil.GenRandom(pIV, AES_BLOCK_LENGTH_IN_BYTES); BCryptUtil.GenRandom(pIV, AES_IV_LENGTH_IN_BYTES);
// then, encrypt the plaintext contents // then, encrypt the plaintext contents
byte* pCiphertext = &pIV[AES_BLOCK_LENGTH_IN_BYTES]; byte* pCiphertext = &pIV[AES_IV_LENGTH_IN_BYTES];
int expectedCiphertextLength = protectedData.Length - AES_BLOCK_LENGTH_IN_BYTES - MAC_LENGTH_IN_BYTES; int expectedCiphertextLength = protectedData.Length - AES_IV_LENGTH_IN_BYTES - MAC_LENGTH_IN_BYTES;
fixed (byte* pPlaintext = unprotectedData) fixed (byte* pPlaintext = unprotectedData.AsFixed())
{ {
int actualCiphertextLength = BCryptUtil.EncryptWithPadding(_aesKeyHandle, pPlaintext, unprotectedData.Length, pIV, AES_BLOCK_LENGTH_IN_BYTES, pCiphertext, expectedCiphertextLength); int actualCiphertextLength = BCryptUtil.EncryptWithPadding(_aesKeyHandle, pPlaintext, unprotectedData.Length, pIV, AES_IV_LENGTH_IN_BYTES, pCiphertext, expectedCiphertextLength);
if (actualCiphertextLength != expectedCiphertextLength) if (actualCiphertextLength != expectedCiphertextLength)
{ {
throw new InvalidOperationException("Unexpected error while encrypting data."); throw new InvalidOperationException("Unexpected error while encrypting data.");
@ -86,7 +93,7 @@ namespace Microsoft.AspNet.Security.DataProtection
{ {
// Use a cloned hash handle since IDataProtector instances could be singletons, but BCryptHashHandle instances contain // Use a cloned hash handle since IDataProtector instances could be singletons, but BCryptHashHandle instances contain
// state hence aren't thread-safe. Our own perf testing shows that duplicating existing hash handles is very fast. // state hence aren't thread-safe. Our own perf testing shows that duplicating existing hash handles is very fast.
BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_BLOCK_LENGTH_IN_BYTES + expectedCiphertextLength, pMac, MAC_LENGTH_IN_BYTES); BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_IV_LENGTH_IN_BYTES + expectedCiphertextLength, pMac, MAC_LENGTH_IN_BYTES);
} }
} }
@ -125,7 +132,7 @@ namespace Microsoft.AspNet.Security.DataProtection
Debug.Assert(protectedData != null); Debug.Assert(protectedData != null);
// is the protected data even long enough to be valid? // is the protected data even long enough to be valid?
if (protectedData.Length < AES_BLOCK_LENGTH_IN_BYTES /* IV */+ AES_BLOCK_LENGTH_IN_BYTES /* min ciphertext size = 1 block */+ MAC_LENGTH_IN_BYTES) if (protectedData.Length < AES_IV_LENGTH_IN_BYTES /* IV */ + AES_BLOCK_LENGTH_IN_BYTES /* min ciphertext size = 1 block */ + MAC_LENGTH_IN_BYTES)
{ {
return null; return null;
} }
@ -134,8 +141,8 @@ namespace Microsoft.AspNet.Security.DataProtection
{ {
// calculate pointer offsets // calculate pointer offsets
byte* pIV = pProtectedData; byte* pIV = pProtectedData;
byte* pCiphertext = &pProtectedData[AES_BLOCK_LENGTH_IN_BYTES]; byte* pCiphertext = &pProtectedData[AES_IV_LENGTH_IN_BYTES];
int ciphertextLength = protectedData.Length - AES_BLOCK_LENGTH_IN_BYTES /* IV */- MAC_LENGTH_IN_BYTES /* MAC */; int ciphertextLength = protectedData.Length - AES_IV_LENGTH_IN_BYTES /* IV */ - MAC_LENGTH_IN_BYTES /* MAC */;
byte* pSuppliedMac = &pCiphertext[ciphertextLength]; byte* pSuppliedMac = &pCiphertext[ciphertextLength];
// first, ensure that the MAC is valid // first, ensure that the MAC is valid
@ -143,7 +150,7 @@ namespace Microsoft.AspNet.Security.DataProtection
using (var clonedHashHandle = BCryptUtil.DuplicateHash(_hmacHashHandle)) using (var clonedHashHandle = BCryptUtil.DuplicateHash(_hmacHashHandle))
{ {
// see comments in Protect(byte[]) for why we duplicate the hash // see comments in Protect(byte[]) for why we duplicate the hash
BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_BLOCK_LENGTH_IN_BYTES + ciphertextLength, pCalculatedMac, MAC_LENGTH_IN_BYTES); BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_IV_LENGTH_IN_BYTES + ciphertextLength, pCalculatedMac, MAC_LENGTH_IN_BYTES);
} }
if (!BCryptUtil.BuffersAreEqualSecure(pSuppliedMac, pCalculatedMac, MAC_LENGTH_IN_BYTES)) if (!BCryptUtil.BuffersAreEqualSecure(pSuppliedMac, pCalculatedMac, MAC_LENGTH_IN_BYTES))
{ {
@ -168,16 +175,11 @@ namespace Microsoft.AspNet.Security.DataProtection
pPlaintextBuffer = temp; pPlaintextBuffer = temp;
} }
int actualPlaintextLength = BCryptUtil.DecryptWithPadding(_aesKeyHandle, pCiphertext, ciphertextLength, pIV, AES_BLOCK_LENGTH_IN_BYTES, pPlaintextBuffer, plaintextBufferLength); int actualPlaintextLength = BCryptUtil.DecryptWithPadding(_aesKeyHandle, pCiphertext, ciphertextLength, pIV, AES_IV_LENGTH_IN_BYTES, pPlaintextBuffer, plaintextBufferLength);
Debug.Assert(actualPlaintextLength >= 0 && actualPlaintextLength < ciphertextLength); Debug.Assert(actualPlaintextLength >= 0 && actualPlaintextLength < ciphertextLength);
// truncate the return value to accomodate the plaintext size perfectly // truncate the return value to accomodate the plaintext size perfectly
byte[] retVal = new byte[actualPlaintextLength]; return BufferUtil.ToManagedByteArray(pPlaintextBuffer, actualPlaintextLength);
fixed (byte* pRetVal = retVal)
{
BufferUtil.BlockCopy(from: (IntPtr) pPlaintextBuffer, to: (IntPtr) pRetVal, byteCount: actualPlaintextLength);
}
return retVal;
} }
} }
} }

View File

@ -6,16 +6,18 @@ namespace Microsoft.AspNet.Security.DataProtection
internal sealed class DpapiDataProtectionProviderImpl : IDataProtectionProvider internal sealed class DpapiDataProtectionProviderImpl : IDataProtectionProvider
{ {
private readonly byte[] _entropy; private readonly byte[] _entropy;
private readonly bool _protectToLocalMachine;
public DpapiDataProtectionProviderImpl(byte[] entropy) public DpapiDataProtectionProviderImpl(byte[] entropy, bool protectToLocalMachine)
{ {
Debug.Assert(entropy != null); Debug.Assert(entropy != null);
_entropy = entropy; _entropy = entropy;
_protectToLocalMachine = protectToLocalMachine;
} }
public IDataProtector CreateProtector(string purpose) public IDataProtector CreateProtector(string purpose)
{ {
return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose)); return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose), _protectToLocalMachine);
} }
public void Dispose() public void Dispose()

View File

@ -10,15 +10,19 @@ namespace Microsoft.AspNet.Security.DataProtection
internal unsafe sealed class DpapiDataProtectorImpl : IDataProtector internal unsafe sealed class DpapiDataProtectorImpl : IDataProtector
{ {
// from dpapi.h // from dpapi.h
private const uint CRYPTPROTECT_LOCAL_MACHINE = 0x4;
private const uint CRYPTPROTECT_UI_FORBIDDEN = 0x1; private const uint CRYPTPROTECT_UI_FORBIDDEN = 0x1;
// Used as the 'purposes' parameter to DPAPI operations // Used as the 'purposes' parameter to DPAPI operations
private readonly byte[] _entropy; private readonly byte[] _entropy;
public DpapiDataProtectorImpl(byte[] entropy) private readonly bool _protectToLocalMachine;
public DpapiDataProtectorImpl(byte[] entropy, bool protectToLocalMachine)
{ {
Debug.Assert(entropy != null); Debug.Assert(entropy != null);
_entropy = entropy; _entropy = entropy;
_protectToLocalMachine = protectToLocalMachine;
} }
private static CryptographicException CreateGenericCryptographicException(bool isErrorDueToProfileNotLoaded = false) private static CryptographicException CreateGenericCryptographicException(bool isErrorDueToProfileNotLoaded = false)
@ -29,7 +33,7 @@ namespace Microsoft.AspNet.Security.DataProtection
public IDataProtector CreateSubProtector(string purpose) public IDataProtector CreateSubProtector(string purpose)
{ {
return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose)); return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose), _protectToLocalMachine);
} }
public void Dispose() public void Dispose()
@ -37,6 +41,18 @@ namespace Microsoft.AspNet.Security.DataProtection
// no-op; no unmanaged resources to dispose // no-op; no unmanaged resources to dispose
} }
private uint GetCryptProtectUnprotectFlags()
{
if (_protectToLocalMachine)
{
return CRYPTPROTECT_LOCAL_MACHINE | CRYPTPROTECT_UI_FORBIDDEN;
}
else
{
return CRYPTPROTECT_UI_FORBIDDEN;
}
}
public byte[] Protect(byte[] unprotectedData) public byte[] Protect(byte[] unprotectedData)
{ {
if (unprotectedData == null) if (unprotectedData == null)
@ -52,14 +68,14 @@ namespace Microsoft.AspNet.Security.DataProtection
try try
{ {
bool success; bool success;
fixed (byte* pUnprotectedData = unprotectedData) fixed (byte* pUnprotectedData = unprotectedData.AsFixed())
{ {
fixed (byte* pEntropy = _entropy) fixed (byte* pEntropy = _entropy)
{ {
// no need for checked arithmetic here // no need for checked arithmetic here
DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)unprotectedData.Length, pbData = pUnprotectedData }; DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)unprotectedData.Length, pbData = pUnprotectedData };
DATA_BLOB optionalEntropy = new DATA_BLOB() { cbData = (uint)_entropy.Length, pbData = pEntropy }; DATA_BLOB optionalEntropy = new DATA_BLOB() { cbData = (uint)_entropy.Length, pbData = pEntropy };
success = UnsafeNativeMethods.CryptProtectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, CRYPTPROTECT_UI_FORBIDDEN, out dataOut); success = UnsafeNativeMethods.CryptProtectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, GetCryptProtectUnprotectFlags(), out dataOut);
} }
} }
@ -104,14 +120,14 @@ namespace Microsoft.AspNet.Security.DataProtection
try try
{ {
bool success; bool success;
fixed (byte* pProtectedData = protectedData) fixed (byte* pProtectedData = protectedData.AsFixed())
{ {
fixed (byte* pEntropy = _entropy) fixed (byte* pEntropy = _entropy)
{ {
// no need for checked arithmetic here // no need for checked arithmetic here
DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)protectedData.Length, pbData = pProtectedData }; DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)protectedData.Length, pbData = pProtectedData };
DATA_BLOB optionalEntropy = new DATA_BLOB() { cbData = (uint)_entropy.Length, pbData = pEntropy }; DATA_BLOB optionalEntropy = new DATA_BLOB() { cbData = (uint)_entropy.Length, pbData = pEntropy };
success = UnsafeNativeMethods.CryptUnprotectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, CRYPTPROTECT_UI_FORBIDDEN, out dataOut); success = UnsafeNativeMethods.CryptUnprotectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, GetCryptProtectUnprotectFlags(), out dataOut);
} }
} }

View File

@ -2,6 +2,9 @@
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection
{ {
/// <summary>
/// A factory that can provide IDataProtector instances.
/// </summary>
public interface IDataProtectionProvider : IDisposable public interface IDataProtectionProvider : IDisposable
{ {
/// <summary> /// <summary>

View File

@ -23,6 +23,8 @@
<ItemGroup> <ItemGroup>
<Compile Include="Algorithms.cs" /> <Compile Include="Algorithms.cs" />
<Compile Include="BCryptAlgorithmFlags.cs" /> <Compile Include="BCryptAlgorithmFlags.cs" />
<Compile Include="SuppressUnmanagedCodeSecurityAttribute - Copy.cs" />
<Compile Include="SafeLibraryHandle.cs" />
<Compile Include="BCryptAlgorithmHandle.cs" /> <Compile Include="BCryptAlgorithmHandle.cs" />
<Compile Include="BCryptBuffer.cs" /> <Compile Include="BCryptBuffer.cs" />
<Compile Include="BCryptBufferDesc.cs" /> <Compile Include="BCryptBufferDesc.cs" />
@ -34,7 +36,6 @@
<Compile Include="BCryptUtil.cs" /> <Compile Include="BCryptUtil.cs" />
<Compile Include="BCRYPT_KEY_DATA_BLOB_HEADER.cs" /> <Compile Include="BCRYPT_KEY_DATA_BLOB_HEADER.cs" />
<Compile Include="Constants.cs" /> <Compile Include="Constants.cs" />
<Compile Include="CryptographicException.cs" />
<Compile Include="CryptRand.cs" /> <Compile Include="CryptRand.cs" />
<Compile Include="DataProtectionProvider.cs" /> <Compile Include="DataProtectionProvider.cs" />
<Compile Include="DataProtectionProviderImpl.cs" /> <Compile Include="DataProtectionProviderImpl.cs" />
@ -44,11 +45,13 @@
<Compile Include="DpapiDataProtectorImpl.cs" /> <Compile Include="DpapiDataProtectorImpl.cs" />
<Compile Include="IDataProtectionProvider.cs" /> <Compile Include="IDataProtectionProvider.cs" />
<Compile Include="IDataProtector.cs" /> <Compile Include="IDataProtector.cs" />
<Compile Include="PBKDF2.cs" />
<Compile Include="Properties\Res.Designer.cs" /> <Compile Include="Properties\Res.Designer.cs" />
<Compile Include="SafeHandleZeroOrMinusOneIsInvalid.cs" /> <Compile Include="SafeHandleZeroOrMinusOneIsInvalid.cs" />
<Compile Include="SP800_108Helper.cs" />
<Compile Include="UnsafeNativeMethods.cs" /> <Compile Include="UnsafeNativeMethods.cs" />
<Compile Include="Util\BufferUtil.cs" /> <Compile Include="Util\BufferUtil.cs" />
<Compile Include="Util\ByteArrayExtensions.cs" />
<Compile Include="Util\MemoryUtil.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(VSToolsPath)\ProjectK\Microsoft.Web.ProjectK.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\ProjectK\Microsoft.Web.ProjectK.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -1,65 +0,0 @@
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Helper class to derive keys from low-entropy passwords using the PBKDF2 algorithm.
/// </summary>
public static class PBKDF2
{
/// <summary>
/// Derives a key from a low-entropy password.
/// </summary>
/// <param name="algorithmName">The name of the PRF to use for key derivation.</param>
/// <param name="password">The low-entropy password from which to generate a key.</param>
/// <param name="salt">The salt used to randomize the key derivation.</param>
/// <param name="iterationCount">The number of iterations to perform.</param>
/// <param name="numBytesToDerive">The desired byte length of the derived key.</param>
/// <returns>A key derived from the provided password.</returns>
/// <remarks>For compatibility with the Rfc2898DeriveBytes class, specify "SHA1" for the <em>algorithmName</em> parameter.</remarks>
public unsafe static byte[] DeriveKey(string algorithmName, byte[] password, byte[] salt, ulong iterationCount, uint numBytesToDerive)
{
if (String.IsNullOrEmpty(algorithmName))
{
throw new ArgumentException(Res.Common_NullOrEmpty, "algorithmName");
}
if (password == null || password.Length == 0)
{
throw new ArgumentException(Res.Common_NullOrEmpty, "password");
}
if (salt == null || salt.Length == 0)
{
throw new ArgumentException(Res.Common_NullOrEmpty, "salt");
}
if (iterationCount <= 0)
{
throw new ArgumentOutOfRangeException("iterationCount");
}
byte[] derivedKey = new byte[numBytesToDerive];
int status;
using (BCryptAlgorithmHandle algHandle = Algorithms.CreateGenericHMACHandleFromPrimitiveProvider(algorithmName))
{
fixed (byte* pPassword = password)
fixed (byte* pSalt = salt)
fixed (byte* pDerivedKey = derivedKey)
{
status = UnsafeNativeMethods.BCryptDeriveKeyPBKDF2(
algHandle, pPassword, (uint)password.Length, pSalt, (uint)salt.Length, iterationCount,
pDerivedKey, numBytesToDerive, dwFlags: 0);
}
}
if (status == 0 /* STATUS_SUCCESS */)
{
return derivedKey;
}
else
{
throw new CryptographicException(status);
}
}
}
}

View File

@ -0,0 +1,190 @@
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);
}
}
}
}
}
}

View File

@ -0,0 +1,121 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
#if NET45
using System.Runtime.ConstrainedExecution;
#endif
namespace Microsoft.Win32.SafeHandles
{
/// <summary>
/// Represents a handle to a Windows module (DLL).
/// </summary>
internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// Called by P/Invoke when returning SafeHandles
private SafeLibraryHandle()
: base(ownsHandle: true) { }
/// <summary>
/// Gets a delegate pointing to a given export from this library.
/// </summary>
public TDelegate GetProcAddress<TDelegate>(string lpProcName, bool throwIfNotFound = true) where TDelegate : class
{
Debug.Assert(typeof(TDelegate).GetTypeInfo().IsSubclassOf(typeof(Delegate)), "TDelegate must be a delegate type!");
IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName);
if (pfnProc == IntPtr.Zero)
{
if (throwIfNotFound)
{
UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
}
else
{
return null;
}
}
return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(pfnProc, typeof(TDelegate));
}
/// <summary>
/// Forbids this library from being unloaded. The library will remain loaded until process termination,
/// regardless of how many times FreeLibrary is called.
/// </summary>
public void ForbidUnload()
{
// from winbase.h
const uint GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004U;
const uint GET_MODULE_HANDLE_EX_FLAG_PIN = 0x00000001U;
IntPtr unused;
bool retVal = UnsafeNativeMethods.GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, this, out unused);
if (!retVal)
{
UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
}
}
/// <summary>
/// Opens a library. If 'filename' is not a fully-qualified path, the default search path is used.
/// </summary>
public static SafeLibraryHandle Open(string filename)
{
SafeLibraryHandle handle = UnsafeNativeMethods.LoadLibrary(filename);
if (handle == null || handle.IsInvalid)
{
UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
}
return handle;
}
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
return UnsafeNativeMethods.FreeLibrary(handle);
}
[SuppressUnmanagedCodeSecurity]
private static class UnsafeNativeMethods
{
private const string KERNEL32_LIB = "kernel32.dll";
// http://msdn.microsoft.com/en-us/library/ms683152(v=vs.85).aspx
[return: MarshalAs(UnmanagedType.Bool)]
#if NET45
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
#endif
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
internal static extern bool FreeLibrary(IntPtr hModule);
// http://msdn.microsoft.com/en-us/library/ms683200(v=vs.85).aspx
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
internal static extern bool GetModuleHandleEx(
[In] uint dwFlags,
[In] SafeLibraryHandle lpModuleName, // can point to a location within the module if GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS is set
[Out] out IntPtr phModule);
// http://msdn.microsoft.com/en-us/library/ms683212(v=vs.85).aspx
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern IntPtr GetProcAddress(
[In] SafeLibraryHandle hModule,
[In, MarshalAs(UnmanagedType.LPStr)] string lpProcName);
// http://msdn.microsoft.com/en-us/library/ms684175(v=vs.85).aspx
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern SafeLibraryHandle LoadLibrary(
[In, MarshalAs(UnmanagedType.LPWStr)]string lpFileName);
internal static void ThrowExceptionForLastWin32Error()
{
int hr = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(hr);
}
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Runtime.InteropServices;
#if !NET45
namespace System.Security
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class SuppressUnmanagedCodeSecurityAttribute : Attribute { }
}
#endif

View File

@ -1,17 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security; using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Security.DataProtection namespace Microsoft.AspNet.Security.DataProtection
{ {
#if NET45
[SuppressUnmanagedCodeSecurity] [SuppressUnmanagedCodeSecurity]
#endif internal unsafe static class UnsafeNativeMethods
internal static unsafe class UnsafeNativeMethods
{ {
private const string BCRYPT_LIB = "bcrypt.dll"; private const string BCRYPT_LIB = "bcrypt.dll";
private const string CRYPT32_LIB = "crypt32.dll"; private const string CRYPT32_LIB = "crypt32.dll";
@ -52,19 +46,6 @@ namespace Microsoft.AspNet.Security.DataProtection
[Out] out uint pcbResult, [Out] out uint pcbResult,
[In] BCryptEncryptFlags dwFlags); [In] BCryptEncryptFlags dwFlags);
[DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd433795(v=vs.85).aspx
internal static extern int BCryptDeriveKeyPBKDF2(
[In] BCryptAlgorithmHandle hPrf,
[In] byte* pbPassword,
[In] uint cbPassword,
[In] byte* pbSalt,
[In] uint cbSalt,
[In] ulong cIterations,
[In] byte* pbDerivedKey,
[In] uint cbDerivedKey,
[In] uint dwFlags);
[DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)] [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375399(v=vs.85).aspx // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375399(v=vs.85).aspx
internal static extern int BCryptDestroyHash( internal static extern int BCryptDestroyHash(
@ -177,6 +158,13 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] uint dwFlags, [In] uint dwFlags,
[Out] out DATA_BLOB pDataOut); [Out] out DATA_BLOB pDataOut);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380262(v=vs.85).aspx
internal static extern bool CryptProtectMemory(
[In] byte* pData,
[In] uint cbData,
[In] uint dwFlags);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882(v=vs.85).aspx // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882(v=vs.85).aspx
internal static extern bool CryptUnprotectData( internal static extern bool CryptUnprotectData(
@ -188,6 +176,13 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] uint dwFlags, [In] uint dwFlags,
[Out] out DATA_BLOB pDataOut); [Out] out DATA_BLOB pDataOut);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380890(v=vs.85).aspx
internal static extern bool CryptUnprotectMemory(
[In] byte* pData,
[In] uint cbData,
[In] uint dwFlags);
/* /*
* KERNEL32.DLL * KERNEL32.DLL
*/ */

View File

@ -1,40 +1,67 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Util namespace Microsoft.AspNet.Security.DataProtection.Util
{ {
internal static unsafe class BufferUtil internal unsafe static class BufferUtil
{ {
private static readonly byte[] _emptyArray = new byte[0]; private static readonly byte[] _emptyArray = new byte[0];
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BlockCopy(IntPtr from, IntPtr to, int byteCount) public static void BlockCopy(void* from, void* to, int byteCount)
{ {
BlockCopy(from, to, checked((uint) byteCount)); // will be checked before invoking the delegate BlockCopy(from, to, checked((uint)byteCount)); // will be checked before invoking the delegate
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BlockCopy(IntPtr from, IntPtr to, uint byteCount) public static void BlockCopy(void* from, void* to, uint byteCount)
{ {
BlockCopySlow((byte*) from, (byte*) to, byteCount); if (byteCount != 0)
{
#if NET45
BlockCopySlow((byte*)from, (byte*)to, byteCount);
#else
Buffer.MemoryCopy(source: from, destination: to, destinationSizeInBytes: byteCount, sourceBytesToCopy: byteCount);
#endif
}
} }
#if NET45
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void BlockCopySlow(byte* from, byte* to, uint byteCount) public static void BlockCopySlow(byte* from, byte* to, uint byteCount)
{ {
// slow, but works
while (byteCount-- != 0) while (byteCount-- != 0)
{ {
*(to++) = *(from++); *(to++) = *(from++);
} }
} }
#endif
/// <summary>
/// Securely clears a memory buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SecureZeroMemory(byte* buffer, int byteCount)
{
SecureZeroMemory(buffer, checked((uint)byteCount));
}
/// <summary>
/// Securely clears a memory buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SecureZeroMemory(byte* buffer, uint byteCount)
{
UnsafeNativeMethods.RtlZeroMemory((IntPtr)buffer, (UIntPtr)byteCount);
}
/// <summary> /// <summary>
/// Creates a new managed byte[] from unmanaged memory. /// Creates a new managed byte[] from unmanaged memory.
/// </summary> /// </summary>
public static byte[] ToManagedByteArray(byte* ptr, int byteCount) public static byte[] ToManagedByteArray(byte* ptr, int byteCount)
{ {
return ToManagedByteArray(ptr, checked((uint) byteCount)); return ToManagedByteArray(ptr, checked((uint)byteCount));
} }
/// <summary> /// <summary>
@ -51,28 +78,33 @@ namespace Microsoft.AspNet.Security.DataProtection.Util
byte[] bytes = new byte[byteCount]; byte[] bytes = new byte[byteCount];
fixed (byte* pBytes = bytes) fixed (byte* pBytes = bytes)
{ {
BlockCopy(from: (IntPtr) ptr, to: (IntPtr) pBytes, byteCount: byteCount); BlockCopy(from: ptr, to: pBytes, byteCount: byteCount);
} }
return bytes; return bytes;
} }
} }
/// <summary> /// <summary>
/// Clears a memory buffer. /// Creates a new managed byte[] from unmanaged memory. The returned value will be protected
/// by CryptProtectMemory.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] ToProtectedManagedByteArray(byte* ptr, int byteCount)
public static void ZeroMemory(byte* buffer, int byteCount)
{ {
ZeroMemory(buffer, checked((uint) byteCount)); byte[] bytes = new byte[byteCount];
} fixed (byte* pBytes = bytes)
{
/// <summary> try
/// Clears a memory buffer. {
/// </summary> BlockCopy(from: ptr, to: pBytes, byteCount: byteCount);
[MethodImpl(MethodImplOptions.AggressiveInlining)] BCryptUtil.ProtectMemoryWithinThisProcess(pBytes, (uint)byteCount);
public static void ZeroMemory(byte* buffer, uint byteCount) }
{ catch
UnsafeNativeMethods.RtlZeroMemory((IntPtr) buffer, (UIntPtr) byteCount); // don't require 'checked': uint -> UIntPtr always guaranteed to succeed {
SecureZeroMemory(pBytes, byteCount);
throw;
}
}
return bytes;
} }
} }
} }

View File

@ -0,0 +1,23 @@
using System;
using System.Diagnostics;
namespace Microsoft.AspNet.Security.DataProtection.Util
{
/// <summary>
/// Defines helper methods for working with fixed expression blocks.
/// </summary>
internal static class ByteArrayExtensions
{
private static readonly byte[] _dummyBuffer = new byte[1];
// Since the 'fixed' keyword turns a zero-length array into a pointer, we need
// to make sure we're always providing a buffer of length >= 1 so that the
// p/invoke methods we pass the pointers to don't see a null pointer. Callers
// are still responsible for passing a proper length to the p/invoke routines.
public static byte[] AsFixed(this byte[] buffer)
{
Debug.Assert(buffer != null);
return (buffer.Length != 0) ? buffer : _dummyBuffer;
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNet.Security.DataProtection.Util
{
internal unsafe static class MemoryUtil
{
/// <summary>
/// Writes an Int32 to a potentially unaligned memory address, big-endian.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnalignedWriteBigEndian(byte* address, uint value)
{
*(address++) = (byte)(value >> 24);
*(address++) = (byte)(value >> 16);
*(address++) = (byte)(value >> 8);
*(address) = (byte)value;
}
}
}

View File

@ -11,7 +11,10 @@
"System.Reflection": "4.0.10.0", "System.Reflection": "4.0.10.0",
"System.Resources.ResourceManager": "4.0.0.0", "System.Resources.ResourceManager": "4.0.0.0",
"System.Runtime": "4.0.20.0", "System.Runtime": "4.0.20.0",
"System.Runtime.InteropServices": "4.0.20.0" "System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.InteropServices": "4.0.20.0",
"System.Security.Cryptography": "4.0.0.0",
"System.Text.Encoding.Extensions": "4.0.10.0"
} }
} }
}, },