diff --git a/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs b/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs
index 0af410cea5..a2e3589613 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs
@@ -3,12 +3,11 @@ using System.Security.Cryptography;
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 HMACSHA256AlgorithmHandle = CreateHMACSHA256AlgorithmHandle();
public static readonly BCryptAlgorithmHandle HMACSHA512AlgorithmHandle = CreateHMACSHA512AlgorithmHandle();
- public static readonly BCryptAlgorithmHandle SP800108AlgorithmHandle = CreateSP800108AlgorithmHandle();
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
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)
{
@@ -32,11 +31,11 @@ namespace Microsoft.AspNet.Security.DataProtection
return algHandle;
}
-
- internal static BCryptAlgorithmHandle CreateGenericHMACHandleFromPrimitiveProvider(string algorithmName)
+ private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle()
{
+ // create the HMACSHA-256 instance
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)
{
throw new CryptographicException(status);
@@ -45,23 +44,11 @@ namespace Microsoft.AspNet.Security.DataProtection
return algHandle;
}
- private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle()
- {
- // create the HMACSHA-256 instance
- return CreateGenericHMACHandleFromPrimitiveProvider(Constants.BCRYPT_SHA256_ALGORITHM);
- }
-
private static BCryptAlgorithmHandle CreateHMACSHA512AlgorithmHandle()
{
// create the HMACSHA-512 instance
- return CreateGenericHMACHandleFromPrimitiveProvider(Constants.BCRYPT_SHA512_ALGORITHM);
- }
-
- 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);
+ 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)
{
throw new CryptographicException(status);
@@ -70,4 +57,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return algHandle;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCRYPT_KEY_DATA_BLOB_HEADER.cs b/src/Microsoft.AspNet.Security.DataProtection/BCRYPT_KEY_DATA_BLOB_HEADER.cs
index 3bc50731df..ffb5f32308 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCRYPT_KEY_DATA_BLOB_HEADER.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCRYPT_KEY_DATA_BLOB_HEADER.cs
@@ -23,4 +23,4 @@ namespace Microsoft.AspNet.Security.DataProtection
pHeader.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmFlags.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmFlags.cs
index 25bbe91cfa..a4c2e5b927 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmFlags.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmFlags.cs
@@ -10,4 +10,4 @@ namespace Microsoft.AspNet.Security.DataProtection
BCRYPT_CAPI_AES_FLAG = 0x00000010,
BCRYPT_HASH_REUSABLE_FLAG = 0x00000020,
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmHandle.cs
index f5a54fa9f8..0824fb3aab 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmHandle.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmHandle.cs
@@ -17,4 +17,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return (UnsafeNativeMethods.BCryptCloseAlgorithmProvider(handle, dwFlags: 0) == 0);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptBuffer.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptBuffer.cs
index 0a73118bbb..20f3305c18 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptBuffer.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptBuffer.cs
@@ -12,4 +12,4 @@ namespace Microsoft.AspNet.Security.DataProtection
public BCryptKeyDerivationBufferType BufferType; // Buffer type
public IntPtr pvBuffer; // Pointer to buffer
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptBufferDesc.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptBufferDesc.cs
index 32eed76657..4ed446cb39 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptBufferDesc.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptBufferDesc.cs
@@ -20,4 +20,4 @@ namespace Microsoft.AspNet.Security.DataProtection
bufferDesc.ulVersion = BCRYPTBUFFER_VERSION;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptEncryptFlags.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptEncryptFlags.cs
index dfc3d86d71..491e2ab9bd 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptEncryptFlags.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptEncryptFlags.cs
@@ -8,4 +8,4 @@ namespace Microsoft.AspNet.Security.DataProtection
{
BCRYPT_BLOCK_PADDING = 0x00000001,
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptGenRandomFlags.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptGenRandomFlags.cs
index 8fdce726b2..6c17410e5d 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptGenRandomFlags.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptGenRandomFlags.cs
@@ -9,4 +9,4 @@ namespace Microsoft.AspNet.Security.DataProtection
BCRYPT_RNG_USE_ENTROPY_IN_BUFFER = 0x00000001,
BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002,
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptHashHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptHashHandle.cs
index ab59352f9d..c20e2e785f 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptHashHandle.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptHashHandle.cs
@@ -17,4 +17,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return (UnsafeNativeMethods.BCryptDestroyHash(handle) == 0);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyDerivationBufferType.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyDerivationBufferType.cs
index 143c811529..11fe12af50 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyDerivationBufferType.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyDerivationBufferType.cs
@@ -23,4 +23,4 @@ namespace Microsoft.AspNet.Security.DataProtection
KDF_SALT = 0xF,
KDF_ITERATION_COUNT = 0x10,
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyHandle.cs
index 16ab238e5a..491b807dd0 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyHandle.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyHandle.cs
@@ -17,4 +17,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return (UnsafeNativeMethods.BCryptDestroyKey(handle) == 0);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptUtil.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptUtil.cs
index 8d1cfc4884..0ec7d478f1 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/BCryptUtil.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptUtil.cs
@@ -1,13 +1,21 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Security.Cryptography;
+using System.Text;
using Microsoft.AspNet.Security.DataProtection.Util;
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
[MethodImpl(MethodImplOptions.NoOptimization)]
public static bool BuffersAreEqualSecure(byte* p1, byte* p2, uint count)
@@ -23,22 +31,22 @@ namespace Microsoft.AspNet.Security.DataProtection
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(int input)
{
- var unused = checked((uint) input);
+ var unused = checked((uint)input);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(uint input)
{
- var unused = checked((int) input);
+ var unused = checked((int)input);
}
- // helper function to wrap BCryptCreateHash
- public static BCryptHashHandle CreateHash(BCryptAlgorithmHandle algorithmHandle, byte* key, int keyLengthInBytes)
+ // helper function to wrap BCryptCreateHash, passing in a key used for HMAC
+ public static BCryptHashHandle CreateHMACHandle(BCryptAlgorithmHandle algorithmHandle, byte* key, int keyLengthInBytes)
{
CheckOverflowUnderflow(keyLengthInBytes);
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)
{
throw new CryptographicException(status);
@@ -61,24 +69,24 @@ namespace Microsoft.AspNet.Security.DataProtection
throw new InvalidOperationException();
}
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;
- 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)
{
throw new CryptographicException(status);
}
- return checked((int) retVal);
+ return checked((int)retVal);
}
// 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 HMAC_KEY_SIZE_IN_BYTES = 256/8;
- const int KDF_SUBKEY_SIZE_IN_BYTES = 512/8;
+ const int ENCRYPTION_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 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
@@ -87,50 +95,27 @@ namespace Microsoft.AspNet.Security.DataProtection
byte* pNewHmacKey = &pNewEncryptionKey[ENCRYPTION_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
- uint numBuffers = 1;
- BCryptBuffer* pBCryptBuffers = stackalloc BCryptBuffer[2];
- 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)));
- }
+ // Since the KDK is pinned, the GC won't move around the array containing the plaintext key before we
+ // have the opportunity to clear its contents.
+ UnprotectMemoryWithinThisProcess(pKdk, (uint)protectedKdk.Length);
- // .. and the header ..
- BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
- BCryptBufferDesc.Initialize(ref bufferDesc);
- bufferDesc.cBuffers = numBuffers;
- bufferDesc.pBuffers = pBCryptBuffers;
+ byte[] purposeBytes = (!String.IsNullOrEmpty(purpose)) ? _secureUtf8Encoding.GetBytes(purpose) : null;
+ SP800_108Helper.DeriveKeys(pKdk, protectedKdk.Length, purposeBytes, pBuffer, TOTAL_NUM_BYTES_TO_DERIVE);
- uint numBytesDerived;
- int status = UnsafeNativeMethods.BCryptKeyDerivation(keyHandle, &bufferDesc, pBuffer, TOTAL_NUM_BYTES_TO_DERIVE, out numBytesDerived, dwFlags: 0);
- if (status != 0 || numBytesDerived != TOTAL_NUM_BYTES_TO_DERIVE)
- {
- throw new CryptographicException(status);
- }
- }
+ // Split into AES, HMAC, and KDF subkeys
+ encryptionKeyHandle = ImportKey(encryptionAlgorithmHandle, pNewEncryptionKey, ENCRYPTION_KEY_SIZE_IN_BYTES);
+ hmacHandle = CreateHMACHandle(hashAlgorithmHandle, pNewHmacKey, HMAC_KEY_SIZE_IN_BYTES);
+ kdfSubkey = BufferUtil.ToProtectedManagedByteArray(pNewKdfSubkey, KDF_SUBKEY_SIZE_IN_BYTES);
+ }
+ 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();
}
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;
- 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)
{
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
@@ -183,7 +168,7 @@ namespace Microsoft.AspNet.Security.DataProtection
BCryptHashHandle hashHandle;
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
@@ -206,7 +191,7 @@ namespace Microsoft.AspNet.Security.DataProtection
{
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)
{
throw new CryptographicException(status);
@@ -219,13 +204,13 @@ namespace Microsoft.AspNet.Security.DataProtection
CheckOverflowUnderflow(inputBytes);
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)
{
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)
{
throw new CryptographicException(status);
@@ -238,7 +223,7 @@ namespace Microsoft.AspNet.Security.DataProtection
CheckOverflowUnderflow(keyBytes);
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)
{
heapAllocatedKeyDataBlob = new byte[numBytesRequiredForKeyDataBlob]; // allocate on heap if we cannot allocate on stack
@@ -248,28 +233,28 @@ namespace Microsoft.AspNet.Security.DataProtection
BCryptKeyHandle retVal;
fixed (byte* pHeapAllocatedKeyDataBlob = heapAllocatedKeyDataBlob)
{
- // The header is first
- BCRYPT_KEY_DATA_BLOB_HEADER* pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*) pHeapAllocatedKeyDataBlob;
+ // 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;
if (pKeyDataBlobHeader == null)
{
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);
- pKeyDataBlobHeader->cbKeyData = (uint) keyBytes;
+ pKeyDataBlobHeader->cbKeyData = (uint)keyBytes;
// the raw material immediately follows the header
- byte* pKeyDataRawMaterial = (byte*) (&pKeyDataBlobHeader[1]);
+ byte* pKeyDataRawMaterial = (byte*)(&pKeyDataBlobHeader[1]);
try
{
- BufferUtil.BlockCopy(from: (IntPtr) key, to: (IntPtr) 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);
+ 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);
}
finally
{
// 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;
}
+
+ 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());
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Constants.cs b/src/Microsoft.AspNet.Security.DataProtection/Constants.cs
index 059590c7e5..1cba9e94bc 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/Constants.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/Constants.cs
@@ -82,4 +82,4 @@ namespace Microsoft.AspNet.Security.DataProtection
internal const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";
internal const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/CryptographicException.cs b/src/Microsoft.AspNet.Security.DataProtection/CryptographicException.cs
deleted file mode 100644
index 19e72c0c2f..0000000000
--- a/src/Microsoft.AspNet.Security.DataProtection/CryptographicException.cs
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DATA_BLOB.cs b/src/Microsoft.AspNet.Security.DataProtection/DATA_BLOB.cs
index 55bfc7ea8e..73a4cb5263 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/DATA_BLOB.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/DATA_BLOB.cs
@@ -1,5 +1,4 @@
using System;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection
@@ -11,4 +10,4 @@ namespace Microsoft.AspNet.Security.DataProtection
public uint cbData;
public byte* pbData;
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProvider.cs b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProvider.cs
index fc011d8c0a..2babfa5008 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProvider.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProvider.cs
@@ -1,41 +1,38 @@
using System;
using System.Globalization;
-using System.Reflection;
+using System.Text;
+using Microsoft.AspNet.Security.DataProtection;
using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection
{
- public static unsafe class DataProtectionProvider
+ ///
+ /// Provides methods for creating IDataProtectionProvider instances.
+ ///
+ 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_DPAPI_ENTROPY = GetMasterSubkeyGenerator(isDpapi: true);
+ private static readonly byte[] MASTER_SUBKEY_GENERATOR = Encoding.ASCII.GetBytes("Microsoft.AspNet.Security.DataProtection");
- private static byte[] GetMasterSubkeyGenerator(bool isDpapi = false)
+ ///
+ /// Creates a new IDataProtectionProvider backed by DPAPI, where the protected
+ /// payload can only be decrypted by the current user.
+ ///
+ public static IDataProtectionProvider CreateFromDpapi()
{
- TypeInfo typeInfo = ((isDpapi) ? typeof(DpapiDataProtectionProviderImpl) : typeof(DataProtectionProvider)).GetTypeInfo();
-
- 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;
+ return CreateFromDpapi(protectToLocalMachine: false);
}
///
/// Creates a new IDataProtectionProvider backed by DPAPI.
///
- public static IDataProtectionProvider CreateFromDpapi()
+ /// 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.
+ public static IDataProtectionProvider CreateFromDpapi(bool protectToLocalMachine)
{
- return new DpapiDataProtectionProviderImpl(MASTER_DPAPI_ENTROPY);
+ return new DpapiDataProtectionProviderImpl(MASTER_SUBKEY_GENERATOR, protectToLocalMachine);
}
///
@@ -51,7 +48,7 @@ namespace Microsoft.AspNet.Security.DataProtection
}
finally
{
- BufferUtil.ZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH);
+ BufferUtil.SecureZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH);
}
}
@@ -82,17 +79,20 @@ namespace Microsoft.AspNet.Security.DataProtection
byte* masterSubkey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH];
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);
- return new DataProtectionProviderImpl(kdfSubkeyHandle);
+ byte[] protectedKdk = BufferUtil.ToProtectedManagedByteArray(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
+ return new DataProtectionProviderImpl(protectedKdk);
}
finally
{
- BufferUtil.ZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
+ BufferUtil.SecureZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProviderImpl.cs b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProviderImpl.cs
index 2165b21fed..97f0b7f895 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProviderImpl.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProviderImpl.cs
@@ -2,28 +2,28 @@
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)
{
BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle;
- BCryptKeyHandle newKdfSubkeyHandle;
+ byte[] newProtectedKdfSubkey;
- BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle);
- return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle);
+ BCryptUtil.DeriveKeysSP800108(_protectedKdk, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newProtectedKdfSubkey);
+ return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newProtectedKdfSubkey);
}
public void Dispose()
{
- _kdfSubkeyHandle.Dispose();
+ // no-op: we hold no protected resources
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DataProtectorImpl.cs b/src/Microsoft.AspNet.Security.DataProtection/DataProtectorImpl.cs
index dec7c4c485..111e3118b8 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/DataProtectorImpl.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/DataProtectorImpl.cs
@@ -5,29 +5,37 @@ using Microsoft.AspNet.Security.DataProtection.Util;
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 MAC_LENGTH_IN_BYTES = 256/8;
+ private const int AES_BLOCK_LENGTH_IN_BYTES = 128 / 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 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;
_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
- 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 */);
+ checked
+ {
+ // 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()
@@ -39,17 +47,16 @@ namespace Microsoft.AspNet.Security.DataProtection
{
BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle;
- BCryptKeyHandle newKdfSubkeyHandle;
+ byte[] newProtectedKdfSubkey;
- BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle);
- return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle);
+ BCryptUtil.DeriveKeysSP800108(_protectedKdk, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newProtectedKdfSubkey);
+ return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newProtectedKdfSubkey);
}
public void Dispose()
{
_aesKeyHandle.Dispose();
_hmacHashHandle.Dispose();
- _kdfSubkeyHandle.Dispose();
}
public byte[] Protect(byte[] unprotectedData)
@@ -66,14 +73,14 @@ namespace Microsoft.AspNet.Security.DataProtection
{
// first, generate a random IV for CBC mode encryption
byte* pIV = pProtectedData;
- BCryptUtil.GenRandom(pIV, AES_BLOCK_LENGTH_IN_BYTES);
+ BCryptUtil.GenRandom(pIV, AES_IV_LENGTH_IN_BYTES);
// then, encrypt the plaintext contents
- byte* pCiphertext = &pIV[AES_BLOCK_LENGTH_IN_BYTES];
- int expectedCiphertextLength = protectedData.Length - AES_BLOCK_LENGTH_IN_BYTES - MAC_LENGTH_IN_BYTES;
- fixed (byte* pPlaintext = unprotectedData)
+ byte* pCiphertext = &pIV[AES_IV_LENGTH_IN_BYTES];
+ int expectedCiphertextLength = protectedData.Length - AES_IV_LENGTH_IN_BYTES - MAC_LENGTH_IN_BYTES;
+ 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)
{
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
// 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);
// 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;
}
@@ -134,8 +141,8 @@ namespace Microsoft.AspNet.Security.DataProtection
{
// calculate pointer offsets
byte* pIV = pProtectedData;
- byte* pCiphertext = &pProtectedData[AES_BLOCK_LENGTH_IN_BYTES];
- int ciphertextLength = protectedData.Length - AES_BLOCK_LENGTH_IN_BYTES /* IV */- MAC_LENGTH_IN_BYTES /* MAC */;
+ byte* pCiphertext = &pProtectedData[AES_IV_LENGTH_IN_BYTES];
+ int ciphertextLength = protectedData.Length - AES_IV_LENGTH_IN_BYTES /* IV */ - MAC_LENGTH_IN_BYTES /* MAC */;
byte* pSuppliedMac = &pCiphertext[ciphertextLength];
// first, ensure that the MAC is valid
@@ -143,7 +150,7 @@ namespace Microsoft.AspNet.Security.DataProtection
using (var clonedHashHandle = BCryptUtil.DuplicateHash(_hmacHashHandle))
{
// 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))
{
@@ -168,18 +175,13 @@ namespace Microsoft.AspNet.Security.DataProtection
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);
// truncate the return value to accomodate the plaintext size perfectly
- byte[] retVal = new byte[actualPlaintextLength];
- fixed (byte* pRetVal = retVal)
- {
- BufferUtil.BlockCopy(from: (IntPtr) pPlaintextBuffer, to: (IntPtr) pRetVal, byteCount: actualPlaintextLength);
- }
- return retVal;
+ return BufferUtil.ToManagedByteArray(pPlaintextBuffer, actualPlaintextLength);
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectionProviderImpl.cs b/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectionProviderImpl.cs
index fa37a07bae..679a5b094a 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectionProviderImpl.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectionProviderImpl.cs
@@ -6,16 +6,18 @@ namespace Microsoft.AspNet.Security.DataProtection
internal sealed class DpapiDataProtectionProviderImpl : IDataProtectionProvider
{
private readonly byte[] _entropy;
+ private readonly bool _protectToLocalMachine;
- public DpapiDataProtectionProviderImpl(byte[] entropy)
+ public DpapiDataProtectionProviderImpl(byte[] entropy, bool protectToLocalMachine)
{
Debug.Assert(entropy != null);
_entropy = entropy;
+ _protectToLocalMachine = protectToLocalMachine;
}
public IDataProtector CreateProtector(string purpose)
{
- return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose));
+ return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose), _protectToLocalMachine);
}
public void Dispose()
@@ -23,4 +25,4 @@ namespace Microsoft.AspNet.Security.DataProtection
// no-op; no unmanaged resources to dispose
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectorImpl.cs b/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectorImpl.cs
index 13e5319e1a..66be4205a2 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectorImpl.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/DpapiDataProtectorImpl.cs
@@ -10,15 +10,19 @@ namespace Microsoft.AspNet.Security.DataProtection
internal unsafe sealed class DpapiDataProtectorImpl : IDataProtector
{
// from dpapi.h
+ private const uint CRYPTPROTECT_LOCAL_MACHINE = 0x4;
private const uint CRYPTPROTECT_UI_FORBIDDEN = 0x1;
// Used as the 'purposes' parameter to DPAPI operations
private readonly byte[] _entropy;
- public DpapiDataProtectorImpl(byte[] entropy)
+ private readonly bool _protectToLocalMachine;
+
+ public DpapiDataProtectorImpl(byte[] entropy, bool protectToLocalMachine)
{
Debug.Assert(entropy != null);
_entropy = entropy;
+ _protectToLocalMachine = protectToLocalMachine;
}
private static CryptographicException CreateGenericCryptographicException(bool isErrorDueToProfileNotLoaded = false)
@@ -29,7 +33,7 @@ namespace Microsoft.AspNet.Security.DataProtection
public IDataProtector CreateSubProtector(string purpose)
{
- return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose));
+ return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose), _protectToLocalMachine);
}
public void Dispose()
@@ -37,6 +41,18 @@ namespace Microsoft.AspNet.Security.DataProtection
// 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)
{
if (unprotectedData == null)
@@ -52,14 +68,14 @@ namespace Microsoft.AspNet.Security.DataProtection
try
{
bool success;
- fixed (byte* pUnprotectedData = unprotectedData)
+ fixed (byte* pUnprotectedData = unprotectedData.AsFixed())
{
fixed (byte* pEntropy = _entropy)
{
// no need for checked arithmetic here
DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)unprotectedData.Length, pbData = pUnprotectedData };
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
{
bool success;
- fixed (byte* pProtectedData = protectedData)
+ fixed (byte* pProtectedData = protectedData.AsFixed())
{
fixed (byte* pEntropy = _entropy)
{
// no need for checked arithmetic here
DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)protectedData.Length, pbData = pProtectedData };
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);
}
}
@@ -139,4 +155,4 @@ namespace Microsoft.AspNet.Security.DataProtection
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/IDataProtectionProvider.cs b/src/Microsoft.AspNet.Security.DataProtection/IDataProtectionProvider.cs
index 99bc6e3285..eb529127b6 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/IDataProtectionProvider.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/IDataProtectionProvider.cs
@@ -2,6 +2,9 @@
namespace Microsoft.AspNet.Security.DataProtection
{
+ ///
+ /// A factory that can provide IDataProtector instances.
+ ///
public interface IDataProtectionProvider : IDisposable
{
///
@@ -11,4 +14,4 @@ namespace Microsoft.AspNet.Security.DataProtection
/// An IDataProtector.
IDataProtector CreateProtector(string purpose);
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/IDataProtector.cs b/src/Microsoft.AspNet.Security.DataProtection/IDataProtector.cs
index 5577a3cbdb..f1f41dd81e 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/IDataProtector.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/IDataProtector.cs
@@ -32,4 +32,4 @@ namespace Microsoft.AspNet.Security.DataProtection
/// Throws CryptographicException if the protectedData parameter has been tampered with.
byte[] Unprotect(byte[] protectedData);
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Microsoft.AspNet.Security.DataProtection.kproj b/src/Microsoft.AspNet.Security.DataProtection/Microsoft.AspNet.Security.DataProtection.kproj
index d68788f92f..9b57d1f1b4 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/Microsoft.AspNet.Security.DataProtection.kproj
+++ b/src/Microsoft.AspNet.Security.DataProtection/Microsoft.AspNet.Security.DataProtection.kproj
@@ -23,6 +23,8 @@
+
+
@@ -34,7 +36,6 @@
-
@@ -44,11 +45,13 @@
-
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Security.DataProtection/PBKDF2.cs b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2.cs
deleted file mode 100644
index c8824b37c1..0000000000
--- a/src/Microsoft.AspNet.Security.DataProtection/PBKDF2.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System;
-using System.Security.Cryptography;
-
-namespace Microsoft.AspNet.Security.DataProtection
-{
- ///
- /// Helper class to derive keys from low-entropy passwords using the PBKDF2 algorithm.
- ///
- public static class PBKDF2
- {
- ///
- /// Derives a key from a low-entropy password.
- ///
- /// The name of the PRF to use for key derivation.
- /// The low-entropy password from which to generate a key.
- /// The salt used to randomize the key derivation.
- /// The number of iterations to perform.
- /// The desired byte length of the derived key.
- /// A key derived from the provided password.
- /// For compatibility with the Rfc2898DeriveBytes class, specify "SHA1" for the algorithmName parameter.
- 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);
- }
- }
- }
-}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/SP800_108Helper.cs b/src/Microsoft.AspNet.Security.DataProtection/SP800_108Helper.cs
new file mode 100644
index 0000000000..f08127f864
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/SP800_108Helper.cs
@@ -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
+{
+ ///
+ /// Provides an implementation of the SP800-108-CTR-HMACSHA512 key derivation function.
+ /// This class assumes at least Windows 7 / Server 2008 R2.
+ ///
+ ///
+ /// More info at http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf, Sec. 5.1.
+ ///
+ 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", 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;
+ }
+ }
+
+ ///
+ /// Performs a key derivation using SP800-108-CTR-HMACSHA512.
+ ///
+ /// Pointer to the key derivation key.
+ /// Length (in bytes) of the key derivation key.
+ /// Purpose to attach to the generated subkey. Corresponds to the 'Label' parameter
+ /// in the KDF. May be null.
+ /// Pointer to a buffer which will receive the subkey.
+ /// Length (in bytes) of the output buffer.
+ 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);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/SafeLibraryHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/SafeLibraryHandle.cs
new file mode 100644
index 0000000000..7d1a131676
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/SafeLibraryHandle.cs
@@ -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
+{
+ ///
+ /// Represents a handle to a Windows module (DLL).
+ ///
+ internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Called by P/Invoke when returning SafeHandles
+ private SafeLibraryHandle()
+ : base(ownsHandle: true) { }
+
+ ///
+ /// Gets a delegate pointing to a given export from this library.
+ ///
+ public TDelegate GetProcAddress(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));
+ }
+
+ ///
+ /// Forbids this library from being unloaded. The library will remain loaded until process termination,
+ /// regardless of how many times FreeLibrary is called.
+ ///
+ 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();
+ }
+ }
+
+ ///
+ /// Opens a library. If 'filename' is not a fully-qualified path, the default search path is used.
+ ///
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/SuppressUnmanagedCodeSecurityAttribute - Copy.cs b/src/Microsoft.AspNet.Security.DataProtection/SuppressUnmanagedCodeSecurityAttribute - Copy.cs
new file mode 100644
index 0000000000..18ed3b1c92
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/SuppressUnmanagedCodeSecurityAttribute - Copy.cs
@@ -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
diff --git a/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs b/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs
index 611d1d4084..88e8eb961a 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs
@@ -1,17 +1,11 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
-using System.Text;
-using System.Threading.Tasks;
namespace Microsoft.AspNet.Security.DataProtection
{
-#if NET45
[SuppressUnmanagedCodeSecurity]
-#endif
- internal static unsafe class UnsafeNativeMethods
+ internal unsafe static class UnsafeNativeMethods
{
private const string BCRYPT_LIB = "bcrypt.dll";
private const string CRYPT32_LIB = "crypt32.dll";
@@ -52,19 +46,6 @@ namespace Microsoft.AspNet.Security.DataProtection
[Out] out uint pcbResult,
[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)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375399(v=vs.85).aspx
internal static extern int BCryptDestroyHash(
@@ -177,6 +158,13 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] uint dwFlags,
[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)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882(v=vs.85).aspx
internal static extern bool CryptUnprotectData(
@@ -188,6 +176,13 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] uint dwFlags,
[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
*/
@@ -197,4 +192,4 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] IntPtr Destination,
[In] UIntPtr /* SIZE_T */ Length);
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Util/BufferUtil.cs b/src/Microsoft.AspNet.Security.DataProtection/Util/BufferUtil.cs
index e982d35f9e..443ead3f93 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/Util/BufferUtil.cs
+++ b/src/Microsoft.AspNet.Security.DataProtection/Util/BufferUtil.cs
@@ -1,40 +1,67 @@
using System;
using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Util
{
- internal static unsafe class BufferUtil
+ internal unsafe static class BufferUtil
{
private static readonly byte[] _emptyArray = new byte[0];
[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)]
- 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)]
- 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)
{
*(to++) = *(from++);
}
}
+#endif
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SecureZeroMemory(byte* buffer, int byteCount)
+ {
+ SecureZeroMemory(buffer, checked((uint)byteCount));
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SecureZeroMemory(byte* buffer, uint byteCount)
+ {
+ UnsafeNativeMethods.RtlZeroMemory((IntPtr)buffer, (UIntPtr)byteCount);
+ }
///
/// Creates a new managed byte[] from unmanaged memory.
///
public static byte[] ToManagedByteArray(byte* ptr, int byteCount)
{
- return ToManagedByteArray(ptr, checked((uint) byteCount));
+ return ToManagedByteArray(ptr, checked((uint)byteCount));
}
///
@@ -51,28 +78,33 @@ namespace Microsoft.AspNet.Security.DataProtection.Util
byte[] bytes = new byte[byteCount];
fixed (byte* pBytes = bytes)
{
- BlockCopy(from: (IntPtr) ptr, to: (IntPtr) pBytes, byteCount: byteCount);
+ BlockCopy(from: ptr, to: pBytes, byteCount: byteCount);
}
return bytes;
}
}
///
- /// Clears a memory buffer.
+ /// Creates a new managed byte[] from unmanaged memory. The returned value will be protected
+ /// by CryptProtectMemory.
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ZeroMemory(byte* buffer, int byteCount)
+ public static byte[] ToProtectedManagedByteArray(byte* ptr, int byteCount)
{
- ZeroMemory(buffer, checked((uint) byteCount));
- }
-
- ///
- /// Clears a memory buffer.
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ZeroMemory(byte* buffer, uint byteCount)
- {
- UnsafeNativeMethods.RtlZeroMemory((IntPtr) buffer, (UIntPtr) byteCount); // don't require 'checked': uint -> UIntPtr always guaranteed to succeed
+ byte[] bytes = new byte[byteCount];
+ fixed (byte* pBytes = bytes)
+ {
+ try
+ {
+ BlockCopy(from: ptr, to: pBytes, byteCount: byteCount);
+ BCryptUtil.ProtectMemoryWithinThisProcess(pBytes, (uint)byteCount);
+ }
+ catch
+ {
+ SecureZeroMemory(pBytes, byteCount);
+ throw;
+ }
+ }
+ return bytes;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Util/ByteArrayExtensions.cs b/src/Microsoft.AspNet.Security.DataProtection/Util/ByteArrayExtensions.cs
new file mode 100644
index 0000000000..f3bfe2b5fe
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Util/ByteArrayExtensions.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Diagnostics;
+
+namespace Microsoft.AspNet.Security.DataProtection.Util
+{
+ ///
+ /// Defines helper methods for working with fixed expression blocks.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Util/MemoryUtil.cs b/src/Microsoft.AspNet.Security.DataProtection/Util/MemoryUtil.cs
new file mode 100644
index 0000000000..0d2b727507
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Util/MemoryUtil.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.AspNet.Security.DataProtection.Util
+{
+ internal unsafe static class MemoryUtil
+ {
+ ///
+ /// Writes an Int32 to a potentially unaligned memory address, big-endian.
+ ///
+ [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;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/project.json b/src/Microsoft.AspNet.Security.DataProtection/project.json
index e1784c0b4e..ef495793cb 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/project.json
+++ b/src/Microsoft.AspNet.Security.DataProtection/project.json
@@ -11,7 +11,10 @@
"System.Reflection": "4.0.10.0",
"System.Resources.ResourceManager": "4.0.0.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"
}
}
},