Implement new DataProtection pipeline.

This commit is contained in:
Levi B 2014-09-28 21:54:33 -07:00
parent 542d87d9d2
commit 769f21783a
155 changed files with 8583 additions and 1728 deletions

View File

@ -1,12 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22013.1
VisualStudioVersion = 14.0.22115.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.DataProtection", "src\Microsoft.AspNet.Security.DataProtection\Microsoft.AspNet.Security.DataProtection.kproj", "{1E570CD4-6F12-44F4-961E-005EE2002BC2}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.DataProtection.Azure", "src\Microsoft.AspNet.Security.DataProtection.Azure\Microsoft.AspNet.Security.DataProtection.Azure.kproj", "{DF3671D7-A9B1-45F1-A195-0AD596001735}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.DataProtection.Compatibility", "src\Microsoft.AspNet.Security.DataProtection.Compatibility\Microsoft.AspNet.Security.DataProtection.Compatibility.kproj", "{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{60336AB3-948D-4D15-A5FB-F32A2B91E814}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.DataProtection.Test", "test\Microsoft.AspNet.Security.DataProtection.Test\Microsoft.AspNet.Security.DataProtection.Test.kproj", "{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
@ -15,11 +23,20 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1E570CD4-6F12-44F4-961E-005EE2002BC2}.Debug|x86.ActiveCfg = Debug|Any CPU
{1E570CD4-6F12-44F4-961E-005EE2002BC2}.Release|x86.ActiveCfg = Release|Any CPU
{DF3671D7-A9B1-45F1-A195-0AD596001735}.Debug|x86.ActiveCfg = Debug|Any CPU
{DF3671D7-A9B1-45F1-A195-0AD596001735}.Release|x86.ActiveCfg = Release|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Debug|x86.ActiveCfg = Debug|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Release|x86.ActiveCfg = Release|Any CPU
{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|x86.ActiveCfg = Debug|Any CPU
{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1E570CD4-6F12-44F4-961E-005EE2002BC2} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
{DF3671D7-A9B1-45F1-A195-0AD596001735} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
EndGlobalSection
EndGlobal

3
global.json Normal file
View File

@ -0,0 +1,3 @@
{
"sources": [ "src" ]
}

View File

@ -0,0 +1,142 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.ExceptionServices;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.Repositories;
using Microsoft.Framework.OptionsModel;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
namespace Microsoft.AspNet.Security.DataProtection.Azure
{
/// <summary>
/// An XML repository backed by Azure blob storage.
/// </summary>
public class BlobStorageXmlRepository : IXmlRepository
{
private const int MAX_NUM_UPDATE_ATTEMPTS = 10;
internal static readonly XNamespace XmlNamespace = XNamespace.Get("http://www.asp.net/dataProtection/2014/azure");
internal static readonly XName KeyRingElementName = XmlNamespace.GetName("keyRing");
public BlobStorageXmlRepository([NotNull] IOptionsAccessor<BlobStorageXmlRepositoryOptions> optionsAccessor)
{
Directory = optionsAccessor.Options.Directory;
CryptoUtil.Assert(Directory != null, "Directory != null");
}
protected CloudBlobDirectory Directory
{
get;
private set;
}
// IXmlRepository objects are supposed to be thread-safe, but CloudBlockBlob
// instances do not meet this criterion. We'll create them on-demand so that each
// thread can have its own instance that doesn't impact others.
private CloudBlockBlob GetKeyRingBlockBlobReference()
{
return Directory.GetBlockBlobReference("keyring.xml");
}
public virtual IReadOnlyCollection<XElement> GetAllElements()
{
var blobRef = GetKeyRingBlockBlobReference();
XDocument document = ReadDocumentFromStorage(blobRef);
return document?.Root.Elements().ToArray() ?? new XElement[0];
}
private XDocument ReadDocumentFromStorage(CloudBlockBlob blobRef)
{
// Try downloading from Azure storage
using (var memoryStream = new MemoryStream())
{
try
{
blobRef.DownloadToStream(memoryStream);
}
catch (StorageException ex) if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.NotFound)
{
// 404s are not a fatal error - empty keyring
return null;
}
// Rewind the memory stream and read it into an XDocument
memoryStream.Position = 0;
XDocument document = XDocument.Load(memoryStream);
// Format checks
CryptoUtil.Assert(document.Root.Name == KeyRingElementName, "TODO: Unknown element.");
CryptoUtil.Assert((int)document.Root.Attribute("version") == 1, "TODO: Unknown version.");
return document;
}
}
public virtual void StoreElement([NotNull] XElement element, string friendlyName)
{
ExceptionDispatchInfo lastException = null;
// To perform a transactional update of keyring.xml, we first need to get
// the original contents of the blob.
var blobRef = GetKeyRingBlockBlobReference();
for (int i = 0; i < MAX_NUM_UPDATE_ATTEMPTS; i++)
{
AccessCondition updateAccessCondition;
XDocument document = ReadDocumentFromStorage(blobRef);
// Inject the new element into the existing <keyRing> root.
if (document != null)
{
document.Root.Add(element);
// only update if the contents haven't changed (prevents overwrite)
updateAccessCondition = AccessCondition.GenerateIfMatchCondition(blobRef.Properties.ETag);
}
else
{
document = new XDocument(
new XElement(KeyRingElementName,
new XAttribute("version", 1),
element));
// only update if the file doesn't exist (prevents overwrite)
updateAccessCondition = AccessCondition.GenerateIfNoneMatchCondition("*");
}
// Write the updated document back out
MemoryStream memoryStream = new MemoryStream();
document.Save(memoryStream);
try
{
blobRef.UploadFromByteArray(memoryStream.GetBuffer(), 0, checked((int)memoryStream.Length), accessCondition: updateAccessCondition);
return; // success!
}
catch (StorageException ex)
{
switch ((HttpStatusCode)ex.RequestInformation.HttpStatusCode)
{
// If we couldn't update the blob due to a conflict on the server, try again.
case HttpStatusCode.Conflict:
case HttpStatusCode.PreconditionFailed:
lastException = ExceptionDispatchInfo.Capture(ex);
continue;
default:
throw;
}
}
}
// If we got this far, too many conflicts occurred while trying to update the blob.
// Just bail.
lastException.Throw();
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.WindowsAzure.Storage.Blob;
namespace Microsoft.AspNet.Security.DataProtection.Azure
{
/// <summary>
/// Specifies options for configuring an Azure blob storage-based repository.
/// </summary>
public class BlobStorageXmlRepositoryOptions
{
/// <summary>
/// The blob storage directory where the key ring will be stored.
/// </summary>
public CloudBlobDirectory Directory { get; set; }
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection
{
internal static class CryptoUtil
{
// This isn't a typical Debug.Assert; the check is always performed, even in retail builds.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Assert(bool condition, string message)
{
if (!condition)
{
Fail(message);
}
}
// This isn't a typical Debug.Fail; an error always occurs, even in retail builds.
// This method doesn't return, but since the CLR doesn't allow specifying a 'never'
// return type, we mimic it by specifying our return type as Exception. That way
// callers can write 'throw Fail(...);' to make the C# compiler happy, as the
// throw keyword is implicitly of type O.
[MethodImpl(MethodImplOptions.NoInlining)]
public static Exception Fail(string message)
{
Debug.Fail(message);
throw new CryptographicException("Assertion failed: " + message);
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>DF3671D7-A9B1-45F1-A195-0AD596001735</ProjectGuid>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Azure
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
internal sealed class NotNullAttribute : Attribute
{
}
}

View File

@ -0,0 +1,21 @@
{
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
"Microsoft.Framework.OptionsModel": "1.0.0-*",
"WindowsAzure.Storage": "4.3.0"
},
"frameworkDependencies": {
"System.Xml.Linq": "4.0.0.0"
},
"frameworks": {
"net451": {
},
"aspnet50": {
}
},
"compilationOptions": {
"warningsAsErrors": true,
"languageVersion": "experimental"
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Threading;
namespace Microsoft.AspNet.Security.DataProtection.Compatibility
{
internal sealed class DataProtectionProviderHelper
{
private IDataProtectionProvider _dataProtectionProvider;
private DataProtectionProviderHelper() { } // can only be instantaited by self
public static IDataProtectionProvider GetDataProtectionProvider(ref DataProtectionProviderHelper helperRef, IFactorySupportFunctions supportFunctions)
{
// First, make sure that only one thread ever initializes the helper instance.
var helper = Volatile.Read(ref helperRef);
if (helper == null)
{
var newHelper = new DataProtectionProviderHelper();
helper = Interlocked.CompareExchange(ref helperRef, newHelper, null) ?? newHelper;
}
// Has the provider already been created?
var provider = Volatile.Read(ref helper._dataProtectionProvider);
if (provider == null)
{
// Since the helper is accessed by reference, all threads should agree on the one true helper
// instance, so this lock is global given a particular reference. This is an implementation
// of the double-check lock pattern.
lock (helper)
{
provider = Volatile.Read(ref helper._dataProtectionProvider);
if (provider == null)
{
provider = supportFunctions.CreateDataProtectionProvider();
Volatile.Write(ref helper._dataProtectionProvider, provider);
}
}
}
// And we're done!
Debug.Assert(provider != null);
return provider;
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Compatibility
{
public sealed class DataProtector<T> : DataProtector, IFactorySupportFunctions
where T : class, IDataProtectionProviderFactory, new()
{
private static DataProtectionProviderHelper _staticHelper;
private DataProtectorHelper _helper;
public DataProtector(string applicationName, string primaryPurpose, string[] specificPurposes)
: base(applicationName, primaryPurpose, specificPurposes)
{
}
protected override bool PrependHashedPurposeToPlaintext
{
get
{
return false;
}
}
private IDataProtector GetCachedDataProtector()
{
var dataProtectionProvider = DataProtectionProviderHelper.GetDataProtectionProvider(ref _staticHelper, this);
return DataProtectorHelper.GetDataProtector(ref _helper, dataProtectionProvider, this);
}
public override bool IsReprotectRequired(byte[] encryptedData)
{
return false;
}
protected override byte[] ProviderProtect(byte[] userData)
{
return GetCachedDataProtector().Protect(userData);
}
protected override byte[] ProviderUnprotect(byte[] encryptedData)
{
return GetCachedDataProtector().Unprotect(encryptedData);
}
IDataProtectionProvider IFactorySupportFunctions.CreateDataProtectionProvider()
{
IDataProtectionProviderFactory factory = Activator.CreateInstance<T>();
IDataProtectionProvider dataProtectionProvider = factory.CreateDataProtectionProvider();
Debug.Assert(dataProtectionProvider != null);
return dataProtectionProvider;
}
IDataProtector IFactorySupportFunctions.CreateDataProtector(IDataProtectionProvider dataProtectionProvider)
{
Debug.Assert(dataProtectionProvider != null);
IDataProtector dataProtector = dataProtectionProvider.CreateProtector(ApplicationName).CreateProtector(PrimaryPurpose);
foreach (string specificPurpose in SpecificPurposes)
{
dataProtector = dataProtector.CreateProtector(specificPurpose);
}
Debug.Assert(dataProtector != null);
return dataProtector;
}
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Threading;
namespace Microsoft.AspNet.Security.DataProtection.Compatibility
{
internal sealed class DataProtectorHelper
{
private IDataProtector _dataProtector;
private DataProtectorHelper() { } // can only be instantaited by self
public static IDataProtector GetDataProtector(ref DataProtectorHelper helperRef, IDataProtectionProvider protectionProvider, IFactorySupportFunctions supportFunctions)
{
// First, make sure that only one thread ever initializes the helper instance.
var helper = Volatile.Read(ref helperRef);
if (helper == null)
{
var newHelper = new DataProtectorHelper();
helper = Interlocked.CompareExchange(ref helperRef, newHelper, null) ?? newHelper;
}
// Has the protector already been created?
var protector = Volatile.Read(ref helper._dataProtector);
if (protector == null)
{
// Since the helper is accessed by reference, all threads should agree on the one true helper
// instance, so this lock is global given a particular reference. This is an implementation
// of the double-check lock pattern.
lock (helper)
{
protector = Volatile.Read(ref helper._dataProtector);
if (protector == null)
{
protector = supportFunctions.CreateDataProtector(protectionProvider);
Volatile.Write(ref helper._dataProtector, protector);
}
}
}
// And we're done!
Debug.Assert(protector != null);
return protector;
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Compatibility
{
public interface IDataProtectionProviderFactory
{
IDataProtectionProvider CreateDataProtectionProvider();
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Compatibility
{
internal interface IFactorySupportFunctions
{
IDataProtectionProvider CreateDataProtectionProvider();
IDataProtector CreateDataProtector(IDataProtectionProvider dataProtectionProvider);
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>C2FD9D02-AA0E-45FA-8561-EE357A94B73D</ProjectGuid>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,17 @@
{
"version": "1.0.0-*",
"frameworks": {
"net451": {
"dependencies": {
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*"
},
"frameworkAssemblies": {
"System.Security": "4.0.0.0"
}
}
},
"compilationOptions": {
"warningsAsErrors": true,
"languageVersion": "experimental"
}
}

View File

@ -1,62 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection
{
internal unsafe static class Algorithms
{
public static readonly BCryptAlgorithmHandle AESAlgorithmHandle = CreateAESAlgorithmHandle();
public static readonly BCryptAlgorithmHandle HMACSHA256AlgorithmHandle = CreateHMACSHA256AlgorithmHandle();
public static readonly BCryptAlgorithmHandle HMACSHA512AlgorithmHandle = CreateHMACSHA512AlgorithmHandle();
private static BCryptAlgorithmHandle CreateAESAlgorithmHandle()
{
// create the AES instance
BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_AES_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: 0);
if (status != 0 || algHandle == null || algHandle.IsInvalid)
{
throw new CryptographicException(status);
}
// 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);
}
if (status != 0)
{
throw new CryptographicException(status);
}
return algHandle;
}
internal static BCryptAlgorithmHandle CreateGenericHMACHandleFromPrimitiveProvider(string algorithmName)
{
BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, algorithmName, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG);
if (status != 0 || algHandle == null || algHandle.IsInvalid)
{
throw new CryptographicException(status);
}
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);
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
internal static class ArraySegmentExtensions
{
public static byte[] AsStandaloneArray(this ArraySegment<byte> arraySegment)
{
// Fast-track: Don't need to duplicate the array.
if (arraySegment.Offset == 0 && arraySegment.Count == arraySegment.Array.Length)
{
return arraySegment.Array;
}
byte[] retVal = new byte[arraySegment.Count];
Buffer.BlockCopy(arraySegment.Array, arraySegment.Offset, retVal, 0, retVal.Length);
return retVal;
}
public static void Validate<T>(this ArraySegment<T> arraySegment)
{
// Since ArraySegment<T> is a struct, it can be improperly initialized or torn.
// We call the ctor again to make sure the instance data is valid.
var unused = new ArraySegment<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
}
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.Cng;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal static class AuthenticatedEncryptorExtensions
{
public static byte[] Encrypt(this IAuthenticatedEncryptor encryptor, ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
{
// Can we call the optimized version?
IAuthenticatedEncryptor2 optimizedEncryptor = encryptor as IAuthenticatedEncryptor2;
if (optimizedEncryptor != null)
{
return optimizedEncryptor.Encrypt(plaintext, additionalAuthenticatedData, preBufferSize, postBufferSize);
}
// Fall back to the unoptimized version
if (preBufferSize == 0 && postBufferSize == 0)
{
// optimization: call through to inner encryptor with no modifications
return encryptor.Encrypt(plaintext, additionalAuthenticatedData);
}
else
{
byte[] temp = encryptor.Encrypt(plaintext, additionalAuthenticatedData);
byte[] retVal = new byte[checked(preBufferSize + temp.Length + postBufferSize)];
Buffer.BlockCopy(temp, 0, retVal, checked((int)preBufferSize), temp.Length);
return retVal;
}
}
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal sealed class CngCbcAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration
{
internal static readonly XNamespace XmlNamespace = XNamespace.Get("http://www.asp.net/2014/dataProtection/cng");
internal static readonly XName CbcEncryptorElementName = XmlNamespace.GetName("cbcEncryptor");
internal static readonly XName EncryptionElementName = XmlNamespace.GetName("encryption");
internal static readonly XName SecretElementName = XmlNamespace.GetName("secret");
internal static readonly XName ValidationElementName = XmlNamespace.GetName("validation");
private readonly CngCbcAuthenticatedEncryptorConfigurationOptions _options;
private readonly ISecret _secret;
public CngCbcAuthenticatedEncryptorConfiguration(CngCbcAuthenticatedEncryptorConfigurationOptions options, ISecret secret)
{
_options = options;
_secret = secret;
}
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return _options.CreateAuthenticatedEncryptor(_secret);
}
private XElement EncryptSecret(IXmlEncryptor encryptor)
{
// First, create the inner <secret> element.
XElement secretElement;
byte[] plaintextSecret = new byte[_secret.Length];
try
{
_secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
secretElement = new XElement(SecretElementName, Convert.ToBase64String(plaintextSecret));
}
finally
{
Array.Clear(plaintextSecret, 0, plaintextSecret.Length);
}
// Then encrypt it and wrap it in another <secret> element.
var encryptedSecretElement = encryptor.Encrypt(secretElement);
CryptoUtil.Assert(!String.IsNullOrEmpty((string)encryptedSecretElement.Attribute("decryptor")),
@"TODO: <secret> encryption was invalid.");
return new XElement(SecretElementName, encryptedSecretElement);
}
public XElement ToXml([NotNull] IXmlEncryptor xmlEncryptor)
{
// <cbcEncryptor reader="{TYPE}">
// <encryption algorithm="{STRING}" provider="{STRING}" keyLength="{INT}" />
// <validation algorithm="{STRING}" provider="{STRING}" />
// <secret>...</secret>
// </cbcEncryptor>
return new XElement(CbcEncryptorElementName,
new XAttribute("reader", typeof(CngCbcAuthenticatedEncryptorConfigurationXmlReader).AssemblyQualifiedName),
new XElement(EncryptionElementName,
new XAttribute("algorithm", _options.EncryptionAlgorithm),
new XAttribute("provider", _options.EncryptionAlgorithmProvider ?? String.Empty),
new XAttribute("keyLength", _options.EncryptionAlgorithmKeySize)),
new XElement(ValidationElementName,
new XAttribute("algorithm", _options.HashAlgorithm),
new XAttribute("provider", _options.HashAlgorithmProvider ?? String.Empty)),
EncryptSecret(xmlEncryptor));
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// A factory that is able to create a CNG-based IAuthenticatedEncryptor
/// using CBC encryption + HMAC validation.
/// </summary>
public unsafe sealed class CngCbcAuthenticatedEncryptorConfigurationFactory : IAuthenticatedEncryptorConfigurationFactory
{
private readonly CngCbcAuthenticatedEncryptorConfigurationOptions _options;
public CngCbcAuthenticatedEncryptorConfigurationFactory([NotNull] IOptionsAccessor<CngCbcAuthenticatedEncryptorConfigurationOptions> optionsAccessor)
{
_options = optionsAccessor.Options.Clone();
}
public IAuthenticatedEncryptorConfiguration CreateNewConfiguration()
{
// generate a 512-bit secret randomly
const int KDK_SIZE_IN_BYTES = 512 / 8;
var secret = ProtectedMemoryBlob.Random(KDK_SIZE_IN_BYTES);
return new CngCbcAuthenticatedEncryptorConfiguration(_options, secret);
}
}
}

View File

@ -0,0 +1,182 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.Cng;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring an authenticated encryption mechanism which uses
/// Windows CNG algorithms in CBC encryption + HMAC validation modes.
/// </summary>
public sealed class CngCbcAuthenticatedEncryptorConfigurationOptions
{
/// <summary>
/// The name of the algorithm to use for symmetric encryption.
/// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The algorithm must support CBC-style encryption and must have a block size of 64 bits or greater.
/// The default value is 'AES'.
/// </remarks>
public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM;
/// <summary>
/// The name of the provider which contains the implementation of the symmetric encryption algorithm.
/// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider.
/// This property is optional.
/// </summary>
/// <remarks>
/// The default value is null.
/// </remarks>
public string EncryptionAlgorithmProvider { get; set; } = null;
/// <summary>
/// The length (in bits) of the key that will be used for symmetric encryption.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The key length must be 128 bits or greater.
/// The default value is 256.
/// </remarks>
public int EncryptionAlgorithmKeySize { get; set; } = 256;
/// <summary>
/// The name of the algorithm to use for hashing data.
/// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The algorithm must support being opened in HMAC mode and must have a digest length
/// of 128 bits or greater.
/// The default value is 'SHA256'.
/// </remarks>
public string HashAlgorithm { get; set; } = Constants.BCRYPT_SHA256_ALGORITHM;
/// <summary>
/// The name of the provider which contains the implementation of the hash algorithm.
/// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider.
/// This property is optional.
/// </summary>
/// <remarks>
/// The default value is null.
/// </remarks>
public string HashAlgorithmProvider { get; set; } = null;
/// <summary>
/// Makes a duplicate of this object, which allows the original object to remain mutable.
/// </summary>
internal CngCbcAuthenticatedEncryptorConfigurationOptions Clone()
{
return new CngCbcAuthenticatedEncryptorConfigurationOptions()
{
EncryptionAlgorithm = this.EncryptionAlgorithm,
EncryptionAlgorithmKeySize = this.EncryptionAlgorithmKeySize,
EncryptionAlgorithmProvider = this.EncryptionAlgorithmProvider,
HashAlgorithm = this.HashAlgorithm,
HashAlgorithmProvider = this.HashAlgorithmProvider
};
}
internal IAuthenticatedEncryptor CreateAuthenticatedEncryptor([NotNull] ISecret secret)
{
// Create the encryption object
string encryptionAlgorithm = GetPropertyValueNotNullOrEmpty(EncryptionAlgorithm, nameof(EncryptionAlgorithm));
string encryptionAlgorithmProvider = GetPropertyValueNormalizeToNull(EncryptionAlgorithmProvider);
uint encryptionAlgorithmKeySizeInBits = GetKeySizeInBits(EncryptionAlgorithmKeySize);
BCryptAlgorithmHandle encryptionAlgorithmHandle = GetEncryptionAlgorithmHandleAndCheckKeySize(encryptionAlgorithm, encryptionAlgorithmProvider, encryptionAlgorithmKeySizeInBits);
// Create the validation object
string hashAlgorithm = GetPropertyValueNotNullOrEmpty(HashAlgorithm, nameof(HashAlgorithm));
string hashAlgorithmProvider = GetPropertyValueNormalizeToNull(HashAlgorithmProvider);
BCryptAlgorithmHandle hashAlgorithmHandle = GetHashAlgorithmHandle(hashAlgorithm, hashAlgorithmProvider);
// and we're good to go!
return new CbcAuthenticatedEncryptor(
keyDerivationKey: new ProtectedMemoryBlob(secret),
symmetricAlgorithmHandle: encryptionAlgorithmHandle,
symmetricAlgorithmKeySizeInBytes: encryptionAlgorithmKeySizeInBits / 8,
hmacAlgorithmHandle: hashAlgorithmHandle);
}
private static BCryptAlgorithmHandle GetEncryptionAlgorithmHandleAndCheckKeySize(string encryptionAlgorithm, string encryptionAlgorithmProvider, uint keyLengthInBits)
{
BCryptAlgorithmHandle algorithmHandle = null;
// Special-case cached providers
if (encryptionAlgorithmProvider == null)
{
if (encryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_CBC; }
}
// Look up the provider dynamically if we couldn't fetch a cached instance
if (algorithmHandle == null)
{
algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(encryptionAlgorithm, encryptionAlgorithmProvider);
algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_CBC);
}
// make sure we're using a block cipher with an appropriate block size
uint cipherBlockSizeInBytes = algorithmHandle.GetCipherBlockLength();
CryptoUtil.Assert(cipherBlockSizeInBytes >= CbcAuthenticatedEncryptor.SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES,
"cipherBlockSizeInBytes >= CbcAuthenticatedEncryptor.SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES");
// make sure the provided key length is valid
algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength(keyLengthInBits);
// all good!
return algorithmHandle;
}
private static BCryptAlgorithmHandle GetHashAlgorithmHandle(string hashAlgorithm, string hashAlgorithmProvider)
{
BCryptAlgorithmHandle algorithmHandle = null;
// Special-case cached providers
if (hashAlgorithmProvider == null)
{
if (hashAlgorithm == Constants.BCRYPT_SHA1_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA1; }
else if (hashAlgorithm == Constants.BCRYPT_SHA256_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA256; }
else if (hashAlgorithm == Constants.BCRYPT_SHA512_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA512; }
}
// Look up the provider dynamically if we couldn't fetch a cached instance
if (algorithmHandle == null)
{
algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(hashAlgorithm, hashAlgorithmProvider, hmac: true);
}
// Make sure we're using a hash algorithm. We require a minimum 128-bit digest.
uint digestSize = algorithmHandle.GetHashDigestLength();
CryptoUtil.Assert(digestSize >= CbcAuthenticatedEncryptor.HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES,
"digestSize >= CbcAuthenticatedEncryptor.HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES");
// all good!
return algorithmHandle;
}
private static uint GetKeySizeInBits(int value)
{
CryptoUtil.Assert(value >= 0, "value >= 0");
CryptoUtil.Assert(value % 8 == 0, "value % 8 == 0");
return (uint)value;
}
private static string GetPropertyValueNormalizeToNull(string value)
{
return (String.IsNullOrEmpty(value)) ? null : value;
}
private static string GetPropertyValueNotNullOrEmpty(string value, string propertyName)
{
if (String.IsNullOrEmpty(value))
{
throw Error.Common_PropertyCannotBeNullOrEmpty(propertyName);
}
return value;
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal sealed class CngCbcAuthenticatedEncryptorConfigurationXmlReader : IAuthenticatedEncryptorConfigurationXmlReader
{
private readonly IServiceProvider _serviceProvider;
private readonly ITypeActivator _typeActivator;
public CngCbcAuthenticatedEncryptorConfigurationXmlReader(
[NotNull] IServiceProvider serviceProvider,
[NotNull] ITypeActivator typeActivator)
{
_serviceProvider = serviceProvider;
_typeActivator = typeActivator;
}
public IAuthenticatedEncryptorConfiguration FromXml([NotNull] XElement element)
{
// <cbcEncryptor reader="{TYPE}">
// <encryption algorithm="{STRING}" provider="{STRING}" keyLength="{INT}" />
// <validation algorithm="{STRING}" provider="{STRING}" />
// <secret>...</secret>
// </cbcEncryptor>
CryptoUtil.Assert(element.Name == CngCbcAuthenticatedEncryptorConfiguration.CbcEncryptorElementName,
@"TODO: Bad element.");
var options = new CngCbcAuthenticatedEncryptorConfigurationOptions();
// read <encryption> element
var encryptionElement = element.Element(CngCbcAuthenticatedEncryptorConfiguration.EncryptionElementName);
options.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm");
options.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider");
options.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength");
// read <validation> element
var validationElement = element.Element(CngCbcAuthenticatedEncryptorConfiguration.ValidationElementName);
options.HashAlgorithm = (string)validationElement.Attribute("algorithm");
options.HashAlgorithmProvider = (string)validationElement.Attribute("provider");
// read the child of the <secret> element, then decrypt it
var encryptedSecretElement = element.Element(CngCbcAuthenticatedEncryptorConfiguration.SecretElementName).Elements().Single();
var secretElementDecryptorTypeName = (string)encryptedSecretElement.Attribute("decryptor");
var secretElementDecryptorType = Type.GetType(secretElementDecryptorTypeName, throwOnError: true);
var secretElementDecryptor = (IXmlDecryptor)_typeActivator.CreateInstance(_serviceProvider, secretElementDecryptorType);
var decryptedSecretElement = secretElementDecryptor.Decrypt(encryptedSecretElement);
CryptoUtil.Assert(decryptedSecretElement.Name == CngCbcAuthenticatedEncryptorConfiguration.SecretElementName,
@"TODO: Bad element.");
byte[] decryptedSecretBytes = Convert.FromBase64String((string)decryptedSecretElement);
try
{
var protectedMemoryBlob = new ProtectedMemoryBlob(decryptedSecretBytes);
return new CngCbcAuthenticatedEncryptorConfiguration(options, protectedMemoryBlob);
}
finally
{
Array.Clear(decryptedSecretBytes, 0, decryptedSecretBytes.Length);
}
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal sealed class CngGcmAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration
{
internal static readonly XNamespace XmlNamespace = XNamespace.Get("http://www.asp.net/2014/dataProtection/cng");
internal static readonly XName EncryptionElementName = XmlNamespace.GetName("encryption");
internal static readonly XName GcmEncryptorElementName = XmlNamespace.GetName("gcmEncryptor");
internal static readonly XName SecretElementName = XmlNamespace.GetName("secret");
private readonly CngGcmAuthenticatedEncryptorConfigurationOptions _options;
private readonly ISecret _secret;
public CngGcmAuthenticatedEncryptorConfiguration(CngGcmAuthenticatedEncryptorConfigurationOptions options, ISecret secret)
{
_options = options;
_secret = secret;
}
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return _options.CreateAuthenticatedEncryptor(_secret);
}
private XElement EncryptSecret(IXmlEncryptor encryptor)
{
// First, create the inner <secret> element.
XElement secretElement;
byte[] plaintextSecret = new byte[_secret.Length];
try
{
_secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
secretElement = new XElement(SecretElementName, Convert.ToBase64String(plaintextSecret));
}
finally
{
Array.Clear(plaintextSecret, 0, plaintextSecret.Length);
}
// Then encrypt it and wrap it in another <secret> element.
var encryptedSecretElement = encryptor.Encrypt(secretElement);
CryptoUtil.Assert(!String.IsNullOrEmpty((string)encryptedSecretElement.Attribute("decryptor")),
@"TODO: <secret> encryption was invalid.");
return new XElement(SecretElementName, encryptedSecretElement);
}
public XElement ToXml([NotNull] IXmlEncryptor xmlEncryptor)
{
// <cbcEncryptor reader="{TYPE}">
// <encryption algorithm="{STRING}" provider="{STRING}" keyLength="{INT}" />
// <secret>...</secret>
// </cbcEncryptor>
return new XElement(GcmEncryptorElementName,
new XAttribute("reader", typeof(CngGcmAuthenticatedEncryptorConfigurationXmlReader).AssemblyQualifiedName),
new XElement(EncryptionElementName,
new XAttribute("algorithm", _options.EncryptionAlgorithm),
new XAttribute("provider", _options.EncryptionAlgorithmProvider ?? String.Empty),
new XAttribute("keyLength", _options.EncryptionAlgorithmKeySize)),
EncryptSecret(xmlEncryptor));
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// A factory that is able to create a CNG-based IAuthenticatedEncryptor
/// using CBC encryption + HMAC validation.
/// </summary>
public unsafe sealed class CngGcmAuthenticatedEncryptorConfigurationFactory : IAuthenticatedEncryptorConfigurationFactory
{
private readonly CngGcmAuthenticatedEncryptorConfigurationOptions _options;
public CngGcmAuthenticatedEncryptorConfigurationFactory([NotNull] IOptionsAccessor<CngGcmAuthenticatedEncryptorConfigurationOptions> optionsAccessor)
{
_options = optionsAccessor.Options.Clone();
}
public IAuthenticatedEncryptorConfiguration CreateNewConfiguration()
{
// generate a 512-bit secret randomly
const int KDK_SIZE_IN_BYTES = 512 / 8;
var secret = ProtectedMemoryBlob.Random(KDK_SIZE_IN_BYTES);
return new CngGcmAuthenticatedEncryptorConfiguration(_options, secret);
}
}
}

View File

@ -0,0 +1,124 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.Cng;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring an authenticated encryption mechanism which uses
/// Windows CNG encryption algorithms in Galois/Counter Mode.
/// </summary>
public sealed class CngGcmAuthenticatedEncryptorConfigurationOptions
{
/// <summary>
/// The name of the algorithm to use for symmetric encryption.
/// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The algorithm must support GCM-style encryption and must have a block size of exactly 128 bits.
/// The default value is 'AES'.
/// </remarks>
public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM;
/// <summary>
/// The name of the provider which contains the implementation of the symmetric encryption algorithm.
/// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider.
/// This property is optional.
/// </summary>
/// <remarks>
/// The default value is null.
/// </remarks>
public string EncryptionAlgorithmProvider { get; set; } = null;
/// <summary>
/// The length (in bits) of the key that will be used for symmetric encryption.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The key length must be 128 bits or greater.
/// The default value is 256.
/// </remarks>
public int EncryptionAlgorithmKeySize { get; set; } = 256;
/// <summary>
/// Makes a duplicate of this object, which allows the original object to remain mutable.
/// </summary>
internal CngGcmAuthenticatedEncryptorConfigurationOptions Clone()
{
return new CngGcmAuthenticatedEncryptorConfigurationOptions()
{
EncryptionAlgorithm = this.EncryptionAlgorithm,
EncryptionAlgorithmKeySize = this.EncryptionAlgorithmKeySize,
EncryptionAlgorithmProvider = this.EncryptionAlgorithmProvider
};
}
internal IAuthenticatedEncryptor CreateAuthenticatedEncryptor([NotNull] ISecret secret)
{
// Create the encryption object
string encryptionAlgorithm = GetPropertyValueNotNullOrEmpty(EncryptionAlgorithm, nameof(EncryptionAlgorithm));
string encryptionAlgorithmProvider = GetPropertyValueNormalizeToNull(EncryptionAlgorithmProvider);
uint encryptionAlgorithmKeySizeInBits = GetKeySizeInBits(EncryptionAlgorithmKeySize);
BCryptAlgorithmHandle encryptionAlgorithmHandle = GetEncryptionAlgorithmHandleAndCheckKeySize(encryptionAlgorithm, encryptionAlgorithmProvider, encryptionAlgorithmKeySizeInBits);
// and we're good to go!
return new GcmAuthenticatedEncryptor(
keyDerivationKey: new ProtectedMemoryBlob(secret),
symmetricAlgorithmHandle: encryptionAlgorithmHandle,
symmetricAlgorithmKeySizeInBytes: encryptionAlgorithmKeySizeInBits / 8);
}
private static BCryptAlgorithmHandle GetEncryptionAlgorithmHandleAndCheckKeySize(string encryptionAlgorithm, string encryptionAlgorithmProvider, uint keyLengthInBits)
{
BCryptAlgorithmHandle algorithmHandle = null;
// Special-case cached providers
if (encryptionAlgorithmProvider == null)
{
if (encryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_GCM; }
}
// Look up the provider dynamically if we couldn't fetch a cached instance
if (algorithmHandle == null)
{
algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(encryptionAlgorithm, encryptionAlgorithmProvider);
algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_GCM);
}
// make sure we're using a block cipher with an appropriate block size
uint cipherBlockSizeInBytes = algorithmHandle.GetCipherBlockLength();
CryptoUtil.Assert(cipherBlockSizeInBytes == 128 / 8, "cipherBlockSizeInBytes == 128 / 8");
// make sure the provided key length is valid
algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength(keyLengthInBits);
// all good!
return algorithmHandle;
}
private static uint GetKeySizeInBits(int value)
{
CryptoUtil.Assert(value >= 0, "value >= 0");
CryptoUtil.Assert(value % 8 == 0, "value % 8 == 0");
return (uint)value;
}
private static string GetPropertyValueNormalizeToNull(string value)
{
return (String.IsNullOrEmpty(value)) ? null : value;
}
private static string GetPropertyValueNotNullOrEmpty(string value, string propertyName)
{
if (String.IsNullOrEmpty(value))
{
throw Error.Common_PropertyCannotBeNullOrEmpty(propertyName);
}
return value;
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal sealed class CngGcmAuthenticatedEncryptorConfigurationXmlReader : IAuthenticatedEncryptorConfigurationXmlReader
{
private readonly IServiceProvider _serviceProvider;
private readonly ITypeActivator _typeActivator;
public CngGcmAuthenticatedEncryptorConfigurationXmlReader(
[NotNull] IServiceProvider serviceProvider,
[NotNull] ITypeActivator typeActivator)
{
_serviceProvider = serviceProvider;
_typeActivator = typeActivator;
}
public IAuthenticatedEncryptorConfiguration FromXml([NotNull] XElement element)
{
// <cbcEncryptor reader="{TYPE}">
// <encryption algorithm="{STRING}" provider="{STRING}" keyLength="{INT}" />
// <secret>...</secret>
// </cbcEncryptor>
CryptoUtil.Assert(element.Name == CngGcmAuthenticatedEncryptorConfiguration.GcmEncryptorElementName,
@"TODO: Bad element.");
var options = new CngGcmAuthenticatedEncryptorConfigurationOptions();
// read <encryption> element
var encryptionElement = element.Element(CngGcmAuthenticatedEncryptorConfiguration.EncryptionElementName);
options.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm");
options.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider");
options.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength");
// read the child of the <secret> element, then decrypt it
var encryptedSecretElement = element.Element(CngGcmAuthenticatedEncryptorConfiguration.SecretElementName).Elements().Single();
var secretElementDecryptorTypeName = (string)encryptedSecretElement.Attribute("decryptor");
var secretElementDecryptorType = Type.GetType(secretElementDecryptorTypeName, throwOnError: true);
var secretElementDecryptor = (IXmlDecryptor)_typeActivator.CreateInstance(_serviceProvider, secretElementDecryptorType);
var decryptedSecretElement = secretElementDecryptor.Decrypt(encryptedSecretElement);
CryptoUtil.Assert(decryptedSecretElement.Name == CngGcmAuthenticatedEncryptorConfiguration.SecretElementName,
@"TODO: Bad element.");
byte[] decryptedSecretBytes = Convert.FromBase64String((string)decryptedSecretElement);
try
{
var protectedMemoryBlob = new ProtectedMemoryBlob(decryptedSecretBytes);
return new CngGcmAuthenticatedEncryptorConfiguration(options, protectedMemoryBlob);
}
finally
{
Array.Clear(decryptedSecretBytes, 0, decryptedSecretBytes.Length);
}
}
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// The basic interface for providing an authenticated encryption and decryption routine.
/// </summary>
public interface IAuthenticatedEncryptor
{
/// <summary>
/// Validates the authentication tag of and decrypts a blob of encrypted data.
/// </summary>
/// <param name="ciphertext">The ciphertext (including authentication tag) to decrypt.</param>
/// <param name="additionalAuthenticatedData">Any ancillary data which was used during computation
/// of the authentication tag. The same AAD must have been specified in the corresponding
/// call to 'Encrypt'.</param>
/// <returns>The original plaintext data (if the authentication tag was validated and decryption succeeded).</returns>
/// <remarks>All cryptography-related exceptions should be homogenized to CryptographicException.</remarks>
byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData);
/// <summary>
/// Encrypts and tamper-proofs a piece of data.
/// </summary>
/// <param name="plaintext">The plaintext to encrypt. This input may be zero bytes in length.</param>
/// <param name="additionalAuthenticatedData">A piece of data which will not be included in
/// the returned ciphertext but which will still be covered by the authentication tag.
/// This input may be zero bytes in length. The same AAD must be specified in the corresponding
/// call to Decrypt.</param>
/// <returns>The ciphertext blob, including authentication tag.</returns>
/// <remarks>All cryptography-related exceptions should be homogenized to CryptographicException.</remarks>
byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData);
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal interface IAuthenticatedEncryptor2 : IAuthenticatedEncryptor
{
byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize);
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Represents a type that contains configuration information about an IAuthenticatedEncryptor
/// instance, including how to serialize it to XML.
/// </summary>
public interface IAuthenticatedEncryptorConfiguration
{
/// <summary>
/// Creates a new IAuthenticatedEncryptor instance based on the current configuration.
/// </summary>
/// <returns>An IAuthenticatedEncryptor instance.</returns>
IAuthenticatedEncryptor CreateEncryptorInstance();
/// <summary>
/// Exports the current configuration to XML, optionally encrypting secret key material.
/// </summary>
/// <param name="xmlEncryptor">The XML encryptor used to encrypt secret material.</param>
/// <returns>An XElement representing the current configuration object.</returns>
XElement ToXml(IXmlEncryptor xmlEncryptor);
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Represents a type that can create new authenticated encryption configuration objects.
/// </summary>
public interface IAuthenticatedEncryptorConfigurationFactory
{
/// <summary>
/// Creates a new configuration object with fresh secret key material.
/// </summary>
/// <returns>
/// An IAuthenticatedEncryptorConfiguration instance.
/// </returns>
IAuthenticatedEncryptorConfiguration CreateNewConfiguration();
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Represents a type that can deserialize an XML-serialized IAuthenticatedEncryptorConfiguration.
/// </summary>
public interface IAuthenticatedEncryptorConfigurationXmlReader
{
/// <summary>
/// Deserializes an XML-serialized IAuthenticatedEncryptorConfiguration.
/// </summary>
/// <param name="element">The XML element to deserialize.</param>
/// <returns>The deserialized IAuthenticatedEncryptorConfiguration.</returns>
IAuthenticatedEncryptorConfiguration FromXml(XElement element);
}
}

View File

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal sealed class ManagedAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration
{
internal static readonly XNamespace XmlNamespace = XNamespace.Get("http://www.asp.net/2014/dataProtection/managed");
internal static readonly XName ManagedEncryptorElementName = XmlNamespace.GetName("managedEncryptor");
internal static readonly XName EncryptionElementName = XmlNamespace.GetName("encryption");
internal static readonly XName SecretElementName = XmlNamespace.GetName("secret");
internal static readonly XName ValidationElementName = XmlNamespace.GetName("validation");
private readonly ManagedAuthenticatedEncryptorConfigurationOptions _options;
private readonly ISecret _secret;
public ManagedAuthenticatedEncryptorConfiguration(ManagedAuthenticatedEncryptorConfigurationOptions options, ISecret secret)
{
_options = options;
_secret = secret;
}
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return _options.CreateAuthenticatedEncryptor(_secret);
}
private XElement EncryptSecret(IXmlEncryptor encryptor)
{
// First, create the inner <secret> element.
XElement secretElement;
byte[] plaintextSecret = new byte[_secret.Length];
try
{
_secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
secretElement = new XElement(SecretElementName, Convert.ToBase64String(plaintextSecret));
}
finally
{
Array.Clear(plaintextSecret, 0, plaintextSecret.Length);
}
// Then encrypt it and wrap it in another <secret> element.
var encryptedSecretElement = encryptor.Encrypt(secretElement);
CryptoUtil.Assert(!String.IsNullOrEmpty((string)encryptedSecretElement.Attribute("decryptor")),
@"TODO: <secret> encryption was invalid.");
return new XElement(SecretElementName, encryptedSecretElement);
}
public XElement ToXml([NotNull] IXmlEncryptor xmlEncryptor)
{
// <managedEncryptor reader="{TYPE}">
// <encryption type="{TYPE}" keyLength="{INT}" />
// <validation type="{TYPE}" />
// <secret>...</secret>
// </managedEncryptor>
return new XElement(ManagedEncryptorElementName,
new XAttribute("reader", typeof(ManagedAuthenticatedEncryptorConfigurationXmlReader).AssemblyQualifiedName),
new XElement(EncryptionElementName,
new XAttribute("type", _options.EncryptionAlgorithmType),
new XAttribute("keyLength", _options.EncryptionAlgorithmKeySize)),
new XElement(ValidationElementName,
new XAttribute("type", _options.ValidationAlgorithmType)),
EncryptSecret(xmlEncryptor));
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.Managed;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
public sealed class ManagedAuthenticatedEncryptorConfigurationFactory : IAuthenticatedEncryptorConfigurationFactory
{
private readonly ManagedAuthenticatedEncryptorConfigurationOptions _options;
public ManagedAuthenticatedEncryptorConfigurationFactory([NotNull] IOptionsAccessor<ManagedAuthenticatedEncryptorConfigurationOptions> optionsAccessor)
{
_options = optionsAccessor.Options.Clone();
}
public IAuthenticatedEncryptorConfiguration CreateNewConfiguration()
{
// generate a 512-bit secret randomly
const int KDK_SIZE_IN_BYTES = 512 / 8;
byte[] kdk = ManagedGenRandomImpl.Instance.GenRandom(KDK_SIZE_IN_BYTES);
ProtectedMemoryBlob secret;
try
{
secret = new ProtectedMemoryBlob(kdk);
}
finally
{
Array.Clear(kdk, 0, kdk.Length);
}
return new ManagedAuthenticatedEncryptorConfiguration(_options, secret);
}
}
}

View File

@ -0,0 +1,115 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.Managed;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring an authenticated encryption mechanism which uses
/// managed SymmetricAlgorithm and KeyedHashAlgorithm implementations.
/// </summary>
public sealed class ManagedAuthenticatedEncryptorConfigurationOptions
{
/// <summary>
/// The type of the algorithm to use for symmetric encryption.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The algorithm must support CBC-style encryption and PKCS#7 padding and must have a block size of 64 bits or greater.
/// The default algorithm is AES.
/// </remarks>
public Type EncryptionAlgorithmType { get; set; } = typeof(Aes);
/// <summary>
/// The length (in bits) of the key that will be used for symmetric encryption.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The key length must be 128 bits or greater.
/// The default value is 256.
/// </remarks>
public int EncryptionAlgorithmKeySize { get; set; } = 256;
/// <summary>
/// A factory for the algorithm to use for validation.
/// This property is required to have a value.
/// </summary>
/// <remarks>
/// The algorithm must have a digest length of 128 bits or greater.
/// The default algorithm is HMACSHA256.
/// </remarks>
public Type ValidationAlgorithmType { get; set; } = typeof(HMACSHA256);
/// <summary>
/// Makes a duplicate of this object, which allows the original object to remain mutable.
/// </summary>
internal ManagedAuthenticatedEncryptorConfigurationOptions Clone()
{
return new ManagedAuthenticatedEncryptorConfigurationOptions()
{
EncryptionAlgorithmType = this.EncryptionAlgorithmType,
EncryptionAlgorithmKeySize = this.EncryptionAlgorithmKeySize,
ValidationAlgorithmType = this.ValidationAlgorithmType
};
}
internal IAuthenticatedEncryptor CreateAuthenticatedEncryptor([NotNull] ISecret secret)
{
// Create the encryption and validation object
Func<SymmetricAlgorithm> encryptorFactory = GetEncryptionAlgorithmFactory();
Func<KeyedHashAlgorithm> validatorFactory = GetValidationAlgorithmFactory();
// Check key size here
int keySizeInBits = EncryptionAlgorithmKeySize;
CryptoUtil.Assert(keySizeInBits % 8 == 0, "keySizeInBits % 8 == 0");
int keySizeInBytes = keySizeInBits / 8;
// We're good to go!
return new ManagedAuthenticatedEncryptor(
keyDerivationKey: new ProtectedMemoryBlob(secret),
symmetricAlgorithmFactory: encryptorFactory,
symmetricAlgorithmKeySizeInBytes: keySizeInBytes,
validationAlgorithmFactory: validatorFactory);
}
private Func<SymmetricAlgorithm> GetEncryptionAlgorithmFactory()
{
CryptoUtil.Assert(EncryptionAlgorithmType != null, "EncryptionAlgorithmType != null");
CryptoUtil.Assert(typeof(SymmetricAlgorithm).IsAssignableFrom(EncryptionAlgorithmType), "typeof(SymmetricAlgorithm).IsAssignableFrom(EncryptionAlgorithmType)");
if (EncryptionAlgorithmType == typeof(Aes))
{
// On Core CLR, there's no public concrete implementation of AES, so we'll special-case it here
return Aes.Create;
}
else
{
// Otherwise the algorithm must have a default ctor
return ((IActivator<SymmetricAlgorithm>)Activator.CreateInstance(typeof(AlgorithmActivator<>).MakeGenericType(EncryptionAlgorithmType))).Creator;
}
}
private Func<KeyedHashAlgorithm> GetValidationAlgorithmFactory()
{
CryptoUtil.Assert(ValidationAlgorithmType != null, "ValidationAlgorithmType != null");
CryptoUtil.Assert(typeof(KeyedHashAlgorithm).IsAssignableFrom(ValidationAlgorithmType), "typeof(KeyedHashAlgorithm).IsAssignableFrom(ValidationAlgorithmType)");
// The algorithm must have a default ctor
return ((IActivator<KeyedHashAlgorithm>)Activator.CreateInstance(typeof(AlgorithmActivator<>).MakeGenericType(ValidationAlgorithmType))).Creator;
}
private interface IActivator<out T>
{
Func<T> Creator { get; }
}
private class AlgorithmActivator<T> : IActivator<T> where T : new()
{
public Func<T> Creator { get; } = Activator.CreateInstance<T>;
}
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption
{
internal sealed class ManagedAuthenticatedEncryptorConfigurationXmlReader : IAuthenticatedEncryptorConfigurationXmlReader
{
private readonly IServiceProvider _serviceProvider;
private readonly ITypeActivator _typeActivator;
public ManagedAuthenticatedEncryptorConfigurationXmlReader(
[NotNull] IServiceProvider serviceProvider,
[NotNull] ITypeActivator typeActivator)
{
_serviceProvider = serviceProvider;
_typeActivator = typeActivator;
}
public IAuthenticatedEncryptorConfiguration FromXml([NotNull] XElement element)
{
// <managedEncryptor reader="{TYPE}">
// <encryption type="{STRING}" keyLength="{INT}" />
// <validation type="{STRING}" />
// <secret>...</secret>
// </managedEncryptor>
CryptoUtil.Assert(element.Name == ManagedAuthenticatedEncryptorConfiguration.EncryptionElementName,
@"TODO: Bad element.");
var options = new ManagedAuthenticatedEncryptorConfigurationOptions();
// read <encryption> element
var encryptionElement = element.Element(ManagedAuthenticatedEncryptorConfiguration.EncryptionElementName);
options.EncryptionAlgorithmType = Type.GetType((string)encryptionElement.Attribute("type"), throwOnError: true);
options.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength");
// read <validation> element
var validationElement = element.Element(ManagedAuthenticatedEncryptorConfiguration.ValidationElementName);
options.ValidationAlgorithmType = Type.GetType((string)validationElement.Attribute("type"), throwOnError: true);
// read the child of the <secret> element, then decrypt it
var encryptedSecretElement = element.Element(ManagedAuthenticatedEncryptorConfiguration.SecretElementName).Elements().Single();
var secretElementDecryptorTypeName = (string)encryptedSecretElement.Attribute("decryptor");
var secretElementDecryptorType = Type.GetType(secretElementDecryptorTypeName, throwOnError: true);
var secretElementDecryptor = (IXmlDecryptor)_typeActivator.CreateInstance(_serviceProvider, secretElementDecryptorType);
var decryptedSecretElement = secretElementDecryptor.Decrypt(encryptedSecretElement);
CryptoUtil.Assert(decryptedSecretElement.Name == ManagedAuthenticatedEncryptorConfiguration.SecretElementName,
@"TODO: Bad element.");
byte[] decryptedSecretBytes = Convert.FromBase64String((string)decryptedSecretElement);
try
{
var protectedMemoryBlob = new ProtectedMemoryBlob(decryptedSecretBytes);
return new ManagedAuthenticatedEncryptorConfiguration(options, protectedMemoryBlob);
}
finally
{
Array.Clear(decryptedSecretBytes, 0, decryptedSecretBytes.Length);
}
}
}
}

View File

@ -1,29 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375524(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct BCRYPT_KEY_DATA_BLOB_HEADER
{
// from bcrypt.h
private const uint BCRYPT_KEY_DATA_BLOB_MAGIC = 0x4d42444b; //Key Data Blob Magic (KDBM)
private const uint BCRYPT_KEY_DATA_BLOB_VERSION1 = 0x1;
public uint dwMagic;
public uint dwVersion;
public uint cbKeyData;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Initialize(ref BCRYPT_KEY_DATA_BLOB_HEADER pHeader)
{
pHeader.dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC;
pHeader.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1;
}
}
}

View File

@ -1,16 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
// from bcrypt.h
[Flags]
internal enum BCryptAlgorithmFlags
{
BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008,
BCRYPT_CAPI_AES_FLAG = 0x00000010,
BCRYPT_HASH_REUSABLE_FLAG = 0x00000020,
}
}

View File

@ -1,23 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection
{
internal sealed class BCryptAlgorithmHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// Called by P/Invoke when returning SafeHandles
private BCryptAlgorithmHandle()
: base(ownsHandle: true)
{
}
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
return (UnsafeNativeMethods.BCryptCloseAlgorithmProvider(handle, dwFlags: 0) == 0);
}
}
}

View File

@ -1,23 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection
{
internal sealed class BCryptHashHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// Called by P/Invoke when returning SafeHandles
private BCryptHashHandle()
: base(ownsHandle: true)
{
}
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
return (UnsafeNativeMethods.BCryptDestroyHash(handle) == 0);
}
}
}

View File

@ -1,295 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.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 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)
{
bool retVal = true;
while (count-- > 0)
{
retVal &= (*(p1++) == *(p2++));
}
return retVal;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(int input)
{
var unused = checked((uint)input);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(uint input)
{
var unused = checked((int)input);
}
// 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);
if (status != 0 || retVal == null || retVal.IsInvalid)
{
throw new CryptographicException(status);
}
return retVal;
}
// helper function to wrap BCryptEncrypt; returns number of bytes written to 'output'
// assumes the output buffer is large enough to hold the ciphertext + any necessary padding
public static int DecryptWithPadding(BCryptKeyHandle keyHandle, byte* input, int inputLength, byte* iv, int ivLength, byte* output, int outputLength)
{
CheckOverflowUnderflow(inputLength);
CheckOverflowUnderflow(ivLength);
CheckOverflowUnderflow(outputLength);
// BCryptEncrypt destroys the 'iv' parameter, so we need to pass a duplicate instead of the original
if (ivLength > Constants.MAX_STACKALLOC_BYTES)
{
throw new InvalidOperationException();
}
byte* pDuplicatedIV = stackalloc byte[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);
if (status != 0)
{
throw new CryptographicException(status);
}
return checked((int)retVal);
}
// helper function to wrap BCryptKeyDerivation using SP800-108-CTR-HMAC-SHA512
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 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
byte* pBuffer = stackalloc byte[TOTAL_NUM_BYTES_TO_DERIVE]; // will be freed with frame pops
byte* pNewEncryptionKey = pBuffer;
byte* pNewHmacKey = &pNewEncryptionKey[ENCRYPTION_KEY_SIZE_IN_BYTES];
byte* pNewKdfSubkey = &pNewHmacKey[HMAC_KEY_SIZE_IN_BYTES];
protectedKdk = (byte[])protectedKdk.Clone(); // CryptUnprotectMemory mutates its input, so we preserve the original
fixed (byte* pKdk = protectedKdk)
{
try
{
// 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);
byte[] purposeBytes = (!String.IsNullOrEmpty(purpose)) ? _secureUtf8Encoding.GetBytes(purpose) : null;
SP800_108Helper.DeriveKeys(pKdk, protectedKdk.Length, purposeBytes, pBuffer, TOTAL_NUM_BYTES_TO_DERIVE);
// 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);
}
}
}
// helper function to wrap BCryptDuplicateHash
public static BCryptHashHandle DuplicateHash(BCryptHashHandle hashHandle)
{
BCryptHashHandle retVal;
int status = UnsafeNativeMethods.BCryptDuplicateHash(hashHandle, out retVal, IntPtr.Zero, 0, dwFlags: 0);
if (status != 0 || retVal == null || retVal.IsInvalid)
{
throw new CryptographicException(status);
}
return retVal;
}
// helper function to wrap BCryptEncrypt; returns number of bytes written to 'output'
// assumes the output buffer is large enough to hold the ciphertext + any necessary padding
public static int EncryptWithPadding(BCryptKeyHandle keyHandle, byte* input, int inputLength, byte* iv, int ivLength, byte* output, int outputLength)
{
CheckOverflowUnderflow(inputLength);
CheckOverflowUnderflow(ivLength);
CheckOverflowUnderflow(outputLength);
// BCryptEncrypt destroys the 'iv' parameter, so we need to pass a duplicate instead of the original
if (ivLength > Constants.MAX_STACKALLOC_BYTES)
{
throw new InvalidOperationException();
}
byte* pDuplicatedIV = stackalloc byte[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);
if (status != 0)
{
throw new CryptographicException(status);
}
return checked((int)retVal);
}
// helper function to take a key, apply a purpose, and generate a new subkey ("entropy") for DPAPI-specific scenarios
public static byte[] GenerateDpapiSubkey(byte[] previousKey, string purpose)
{
Debug.Assert(previousKey != null);
purpose = purpose ?? String.Empty; // cannot be null
// create the HMAC object
BCryptHashHandle hashHandle;
fixed (byte* pPreviousKey = previousKey)
{
hashHandle = CreateHMACHandle(Algorithms.HMACSHA256AlgorithmHandle, pPreviousKey, previousKey.Length);
}
// hash the purpose string, treating it as UTF-16LE
using (hashHandle)
{
byte[] retVal = new byte[256 / 8]; // fixed length output since we're hardcoded to HMACSHA256
fixed (byte* pRetVal = retVal)
{
fixed (char* pPurpose = purpose)
{
HashData(hashHandle, (byte*)pPurpose, checked(purpose.Length * sizeof(char)), pRetVal, retVal.Length);
return retVal;
}
}
}
}
// helper function that's similar to RNGCryptoServiceProvider, but works directly with pointers
public static void GenRandom(byte* buffer, int bufferBytes)
{
CheckOverflowUnderflow(bufferBytes);
int status = UnsafeNativeMethods.BCryptGenRandom(IntPtr.Zero, buffer, (uint)bufferBytes, BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (status != 0)
{
throw new CryptographicException(status);
}
}
// helper function that wraps BCryptHashData / BCryptFinishHash
public static void HashData(BCryptHashHandle hashHandle, byte* input, int inputBytes, byte* output, int outputBytes)
{
CheckOverflowUnderflow(inputBytes);
CheckOverflowUnderflow(outputBytes);
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);
if (status != 0)
{
throw new CryptographicException(status);
}
}
// helper function that wraps BCryptImportKey with a key data blob
public static BCryptKeyHandle ImportKey(BCryptAlgorithmHandle algHandle, byte* key, int keyBytes)
{
CheckOverflowUnderflow(keyBytes);
byte[] heapAllocatedKeyDataBlob = null;
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
}
int status;
BCryptKeyHandle retVal;
fixed (byte* pHeapAllocatedKeyDataBlob = heapAllocatedKeyDataBlob)
{
// 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;
}
BCRYPT_KEY_DATA_BLOB_HEADER.Initialize(ref *pKeyDataBlobHeader);
pKeyDataBlobHeader->cbKeyData = (uint)keyBytes;
// the raw material immediately follows the header
byte* pKeyDataRawMaterial = (byte*)(&pKeyDataBlobHeader[1]);
try
{
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.SecureZeroMemory(pKeyDataRawMaterial, keyBytes);
}
}
if (status != 0 || retVal == null || retVal.IsInvalid)
{
throw new CryptographicException(status);
}
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

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNet.Security.DataProtection
{
internal unsafe static class BitHelpers
{
/// <summary>
/// Writes an unsigned 32-bit value to a memory address, big-endian.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteTo(void* ptr, uint value)
{
byte* bytePtr = (byte*)ptr;
bytePtr[0] = (byte)(value >> 24);
bytePtr[1] = (byte)(value >> 16);
bytePtr[2] = (byte)(value >> 8);
bytePtr[3] = (byte)(value);
}
/// <summary>
/// Writes a signed 32-bit value to a memory address, big-endian.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteTo(byte[] buffer, ref int idx, int value)
{
WriteTo(buffer, ref idx, (uint)value);
}
/// <summary>
/// Writes a signed 32-bit value to a memory address, big-endian.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteTo(byte[] buffer, ref int idx, uint value)
{
buffer[idx++] = (byte)(value >> 24);
buffer[idx++] = (byte)(value >> 16);
buffer[idx++] = (byte)(value >> 8);
buffer[idx++] = (byte)(value);
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/cc562981(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
{
public uint cbSize;
public uint dwInfoVersion;
public byte* pbNonce;
public uint cbNonce;
public byte* pbAuthData;
public uint cbAuthData;
public byte* pbTag;
public uint cbTag;
public byte* pbMacContext;
public uint cbMacContext;
public uint cbAAD;
public ulong cbData;
public uint dwFlags;
// corresponds to the BCRYPT_INIT_AUTH_MODE_INFO macro in bcrypt.h
public static void Init(out BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO info)
{
const uint BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION = 1;
info = new BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
{
cbSize = (uint)sizeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO),
dwInfoVersion = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION
};
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375525(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct BCRYPT_KEY_LENGTHS_STRUCT
{
// MSDN says these fields represent the key length in bytes.
// It's wrong: these key lengths are all actually in bits.
private uint dwMinLength;
private uint dwMaxLength;
private uint dwIncrement;
public void EnsureValidKeyLength(uint keyLengthInBits)
{
if (!IsValidKeyLength(keyLengthInBits))
{
string message = String.Format(CultureInfo.CurrentCulture, Resources.BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength, keyLengthInBits, dwMinLength, dwMaxLength, dwIncrement);
throw new ArgumentException(message, "keyLengthInBits");
}
CryptoUtil.Assert(keyLengthInBits % 8 == 0, "keyLengthInBits % 8 == 0");
}
private bool IsValidKeyLength(uint keyLengthInBits)
{
// If the step size is zero, then the key length must be exactly the min or the max. Otherwise,
// key length must be between min and max (inclusive) and a whole number of increments away from min.
if (dwIncrement == 0)
{
return (keyLengthInBits == dwMinLength || keyLengthInBits == dwMaxLength);
}
else
{
return (dwMinLength <= keyLengthInBits)
&& (keyLengthInBits <= dwMaxLength)
&& ((keyLengthInBits - dwMinLength) % dwIncrement == 0);
}
}
}
}

View File

@ -1,11 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375368(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]

View File

@ -1,11 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375370(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]

View File

@ -1,11 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// from bcrypt.h
[Flags]
internal enum BCryptEncryptFlags
{

View File

@ -1,9 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// from bcrypt.h
[Flags]

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
internal unsafe sealed class BCryptGenRandomImpl : IBCryptGenRandom
{
public static readonly BCryptGenRandomImpl Instance = new BCryptGenRandomImpl();
private BCryptGenRandomImpl()
{
}
public void GenRandom(byte* pbBuffer, uint cbBuffer)
{
BCryptUtil.GenRandom(pbBuffer, cbBuffer);
}
}
}

View File

@ -1,9 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// from bcrypt.h
internal enum BCryptKeyDerivationBufferType

View File

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
internal unsafe static class BCryptUtil
{
// helper function that's similar to RNGCryptoServiceProvider, but works directly with pointers
public static void GenRandom(byte* pbBuffer, uint cbBuffer)
{
if (cbBuffer != 0)
{
int ntstatus = UnsafeNativeMethods.BCryptGenRandom(
hAlgorithm: IntPtr.Zero,
pbBuffer: pbBuffer,
cbBuffer: cbBuffer,
dwFlags: BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
}
}
}
}

View File

@ -0,0 +1,152 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
/// <summary>
/// Provides cached CNG algorithm provider instances, as calling BCryptOpenAlgorithmProvider is expensive.
/// Callers should use caution never to dispose of the algorithm provider instances returned by this type.
/// </summary>
internal static class CachedAlgorithmHandles
{
private static CachedAlgorithmInfo _aesCbc = new CachedAlgorithmInfo(() => GetAesAlgorithm(chainingMode: Constants.BCRYPT_CHAIN_MODE_CBC));
private static CachedAlgorithmInfo _aesGcm = new CachedAlgorithmInfo(() => GetAesAlgorithm(chainingMode: Constants.BCRYPT_CHAIN_MODE_GCM));
private static CachedAlgorithmInfo _hmacSha1 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA1_ALGORITHM));
private static CachedAlgorithmInfo _hmacSha256 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA256_ALGORITHM));
private static CachedAlgorithmInfo _hmacSha512 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA512_ALGORITHM));
private static CachedAlgorithmInfo _pbkdf2 = new CachedAlgorithmInfo(GetPbkdf2Algorithm);
private static CachedAlgorithmInfo _sha1 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA1_ALGORITHM));
private static CachedAlgorithmInfo _sha256 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA256_ALGORITHM));
private static CachedAlgorithmInfo _sha512 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA512_ALGORITHM));
private static CachedAlgorithmInfo _sp800_108_ctr_hmac = new CachedAlgorithmInfo(GetSP800_108_CTR_HMACAlgorithm);
public static BCryptAlgorithmHandle AES_CBC
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesCbc);
}
}
public static BCryptAlgorithmHandle AES_GCM
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesGcm);
}
}
public static BCryptAlgorithmHandle HMAC_SHA1
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha1);
}
}
public static BCryptAlgorithmHandle HMAC_SHA256
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha256);
}
}
public static BCryptAlgorithmHandle HMAC_SHA512
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha512);
}
}
// Only available on Win8+.
public static BCryptAlgorithmHandle PBKDF2
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _pbkdf2);
}
}
public static BCryptAlgorithmHandle SHA1
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha1);
}
}
public static BCryptAlgorithmHandle SHA256
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha256);
}
}
public static BCryptAlgorithmHandle SHA512
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha512);
}
}
public static BCryptAlgorithmHandle SP800_108_CTR_HMAC
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sp800_108_ctr_hmac);
}
}
private static BCryptAlgorithmHandle GetAesAlgorithm(string chainingMode)
{
var algHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_AES_ALGORITHM);
algHandle.SetChainingMode(chainingMode);
return algHandle;
}
private static BCryptAlgorithmHandle GetHashAlgorithm(string algorithm)
{
return BCryptAlgorithmHandle.OpenAlgorithmHandle(algorithm, hmac: false);
}
private static BCryptAlgorithmHandle GetHmacAlgorithm(string algorithm)
{
return BCryptAlgorithmHandle.OpenAlgorithmHandle(algorithm, hmac: true);
}
private static BCryptAlgorithmHandle GetPbkdf2Algorithm()
{
return BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_PBKDF2_ALGORITHM, implementation: Constants.MS_PRIMITIVE_PROVIDER);
}
private static BCryptAlgorithmHandle GetSP800_108_CTR_HMACAlgorithm()
{
return BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_SP800108_CTR_HMAC_ALGORITHM, implementation: Constants.MS_PRIMITIVE_PROVIDER);
}
// Warning: mutable struct!
private struct CachedAlgorithmInfo
{
private WeakReference<BCryptAlgorithmHandle> _algorithmHandle;
private readonly Func<BCryptAlgorithmHandle> _factory;
public CachedAlgorithmInfo(Func<BCryptAlgorithmHandle> factory)
{
_algorithmHandle = null;
_factory = factory;
}
public static BCryptAlgorithmHandle GetAlgorithmHandle(ref CachedAlgorithmInfo cachedAlgorithmInfo)
{
return WeakReferenceHelpers.GetSharedInstance(ref cachedAlgorithmInfo._algorithmHandle, cachedAlgorithmInfo._factory);
}
}
}
}

View File

@ -0,0 +1,437 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
using Microsoft.AspNet.Security.DataProtection.SP800_108;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// An encryptor which does Encrypt(CBC) + HMAC using the Windows CNG (BCrypt*) APIs.
// The payloads produced by this encryptor should be compatible with the payloads
// produced by the managed Encrypt(CBC) + HMAC encryptor.
internal unsafe sealed class CbcAuthenticatedEncryptor : CngAuthenticatedEncryptorBase
{
// Even when IVs are chosen randomly, CBC is susceptible to IV collisions within a single
// key. For a 64-bit block cipher (like 3DES), we'd expect a collision after 2^32 block
// encryption operations, which a high-traffic web server might perform in mere hours.
// AES and other 128-bit block ciphers are less susceptible to this due to the larger IV
// space, but unfortunately some organizations require older 64-bit block ciphers. To address
// the collision issue, we'll feed 128 bits of entropy to the KDF when performing subkey
// generation. This creates >= 192 bits total entropy for each operation, so we shouldn't
// expect a collision until >= 2^96 operations. Even 2^80 operations still maintains a <= 2^-32
// probability of collision, and this is acceptable for the expected KDK lifetime.
private const uint KEY_MODIFIER_SIZE_IN_BYTES = 128 / 8;
// Our analysis re: IV collision resistance only holds if we're working with block ciphers
// with a block length of 64 bits or greater.
internal const uint SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES = 64 / 8;
// Min security bar: authentication tag must have at least 128 bits of output.
internal const uint HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES = 128 / 8;
private readonly byte[] _contextHeader;
private readonly IBCryptGenRandom _genRandom;
private readonly BCryptAlgorithmHandle _hmacAlgorithmHandle;
private readonly uint _hmacAlgorithmDigestLengthInBytes;
private readonly uint _hmacAlgorithmSubkeyLengthInBytes;
private readonly ISP800_108_CTR_HMACSHA512Provider _sp800_108_ctr_hmac_provider;
private readonly BCryptAlgorithmHandle _symmetricAlgorithmHandle;
private readonly uint _symmetricAlgorithmBlockSizeInBytes;
private readonly uint _symmetricAlgorithmSubkeyLengthInBytes;
public CbcAuthenticatedEncryptor(ProtectedMemoryBlob keyDerivationKey, BCryptAlgorithmHandle symmetricAlgorithmHandle, uint symmetricAlgorithmKeySizeInBytes, BCryptAlgorithmHandle hmacAlgorithmHandle, IBCryptGenRandom genRandom = null)
{
CryptoUtil.Assert(KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES,
"KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES");
_genRandom = genRandom ?? BCryptGenRandomImpl.Instance;
_sp800_108_ctr_hmac_provider = SP800_108_CTR_HMACSHA512Util.CreateProvider(keyDerivationKey);
_symmetricAlgorithmHandle = symmetricAlgorithmHandle;
_symmetricAlgorithmBlockSizeInBytes = symmetricAlgorithmHandle.GetCipherBlockLength();
_symmetricAlgorithmSubkeyLengthInBytes = symmetricAlgorithmKeySizeInBytes;
_hmacAlgorithmHandle = hmacAlgorithmHandle;
_hmacAlgorithmDigestLengthInBytes = hmacAlgorithmHandle.GetHashDigestLength();
_hmacAlgorithmSubkeyLengthInBytes = _hmacAlgorithmDigestLengthInBytes; // for simplicity we'll generate HMAC subkeys with a length equal to the digest length
CryptoUtil.Assert(SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES <= _symmetricAlgorithmBlockSizeInBytes && _symmetricAlgorithmBlockSizeInBytes <= Constants.MAX_STACKALLOC_BYTES,
"SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES <= _symmetricAlgorithmBlockSizeInBytes && _symmetricAlgorithmBlockSizeInBytes <= Constants.MAX_STACKALLOC_BYTES");
CryptoUtil.Assert(HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES <= _hmacAlgorithmDigestLengthInBytes,
"HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES <= _hmacAlgorithmDigestLengthInBytes");
CryptoUtil.Assert(KEY_MODIFIER_SIZE_IN_BYTES <= _hmacAlgorithmSubkeyLengthInBytes && _hmacAlgorithmSubkeyLengthInBytes <= Constants.MAX_STACKALLOC_BYTES,
"KEY_MODIFIER_SIZE_IN_BYTES <= _hmacAlgorithmSubkeyLengthInBytes && _hmacAlgorithmSubkeyLengthInBytes <= Constants.MAX_STACKALLOC_BYTES");
_contextHeader = CreateContextHeader();
}
private byte[] CreateContextHeader()
{
byte[] retVal = new byte[checked(
1 /* KDF alg */
+ 1 /* chaining mode */
+ sizeof(uint) /* sym alg key size */
+ sizeof(uint) /* sym alg block size */
+ sizeof(uint) /* hmac alg key size */
+ sizeof(uint) /* hmac alg digest size */
+ _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */
+ _hmacAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)];
fixed (byte* pbRetVal = retVal)
{
byte* ptr = pbRetVal;
// First is the two-byte header
*(ptr++) = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
*(ptr++) = 0; // 0x00 = CBC encryption + HMAC authentication
// Next is information about the symmetric algorithm (key size followed by block size)
BitHelpers.WriteTo(ptr, _symmetricAlgorithmSubkeyLengthInBytes);
ptr += sizeof(uint);
BitHelpers.WriteTo(ptr, _symmetricAlgorithmBlockSizeInBytes);
ptr += sizeof(uint);
// Next is information about the HMAC algorithm (key size followed by digest size)
BitHelpers.WriteTo(ptr, _hmacAlgorithmSubkeyLengthInBytes);
ptr += sizeof(uint);
BitHelpers.WriteTo(ptr, _hmacAlgorithmDigestLengthInBytes);
ptr += sizeof(uint);
// See the design document for an explanation of the following code.
byte[] tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes];
fixed (byte* pbTempKeys = tempKeys)
{
byte dummy;
// Derive temporary keys for encryption + HMAC.
using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider())
{
provider.DeriveKey(
pbLabel: &dummy,
cbLabel: 0,
pbContext: &dummy,
cbContext: 0,
pbDerivedKey: pbTempKeys,
cbDerivedKey: (uint)tempKeys.Length);
}
// At this point, tempKeys := { K_E || K_H }.
byte* pbSymmetricEncryptionSubkey = pbTempKeys;
byte* pbHmacSubkey = &pbTempKeys[_symmetricAlgorithmSubkeyLengthInBytes];
// Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer.
using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
{
fixed (byte* pbIV = new byte[_symmetricAlgorithmBlockSizeInBytes] /* will be zero-initialized */)
{
DoCbcEncrypt(
symmetricKeyHandle: symmetricKeyHandle,
pbIV: pbIV,
pbInput: &dummy,
cbInput: 0,
pbOutput: ptr,
cbOutput: _symmetricAlgorithmBlockSizeInBytes);
}
}
ptr += _symmetricAlgorithmBlockSizeInBytes;
// MAC a zero-length input string and copy the digest to the return buffer.
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
{
hashHandle.HashData(
pbInput: &dummy,
cbInput: 0,
pbHashDigest: ptr,
cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
}
ptr += _hmacAlgorithmDigestLengthInBytes;
CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length");
}
}
// retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || hmacAlgKeySize || hmacAlgDigestSize || E("") || MAC("") }.
return retVal;
}
protected override byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
{
// Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
if (cbCiphertext < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes))
{
throw Error.CryptCommon_PayloadInvalid();
}
// Assumption: pbCipherText := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
uint cbEncryptedData = checked(cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes));
// Calculate offsets
byte* pbKeyModifier = pbCiphertext;
byte* pbIV = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
byte* pbEncryptedData = &pbIV[_symmetricAlgorithmBlockSizeInBytes];
byte* pbActualHmac = &pbEncryptedData[cbEncryptedData];
// Use the KDF to recreate the symmetric encryption and HMAC subkeys
// We'll need a temporary buffer to hold them
uint cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
try
{
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
pbLabel: pbAdditionalAuthenticatedData,
cbLabel: cbAdditionalAuthenticatedData,
contextHeader: _contextHeader,
pbContext: pbKeyModifier,
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
pbDerivedKey: pbTempSubkeys,
cbDerivedKey: cbTempSubkeys);
// Calculate offsets
byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
// First, perform an explicit integrity check over (iv | encryptedPayload) to ensure the
// data hasn't been tampered with. The integrity check is also implicitly performed over
// keyModifier since that value was provided to the KDF earlier.
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
{
if (!ValidateHash(hashHandle, pbIV, _symmetricAlgorithmBlockSizeInBytes + cbEncryptedData, pbActualHmac))
{
throw Error.CryptCommon_PayloadInvalid();
}
}
// If the integrity check succeeded, decrypt the payload.
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
{
return DoCbcDecrypt(decryptionSubkeyHandle, pbIV, pbEncryptedData, cbEncryptedData);
}
}
finally
{
// Buffer contains sensitive key material; nuke.
UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
}
}
public override void Dispose()
{
_sp800_108_ctr_hmac_provider.Dispose();
// We don't want to dispose of the underlying algorithm instances because they
// might be reused.
}
// 'pbIV' must be a pointer to a buffer equal in length to the symmetric algorithm block size.
private byte[] DoCbcDecrypt(BCryptKeyHandle symmetricKeyHandle, byte* pbIV, byte* pbInput, uint cbInput)
{
// BCryptDecrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
// First, figure out how large an output buffer we require.
// Ideally we'd be able to transform the last block ourselves and strip
// off the padding before creating the return value array, but we don't
// know the actual padding scheme being used under the covers (we can't
// assume PKCS#7). So unfortunately we're stuck with the temporary buffer.
// (Querying the output size won't mutate the IV.)
uint dwEstimatedDecryptedByteCount;
int ntstatus = UnsafeNativeMethods.BCryptDecrypt(
hKey: symmetricKeyHandle,
pbInput: pbInput,
cbInput: cbInput,
pPaddingInfo: null,
pbIV: pbClonedIV,
cbIV: _symmetricAlgorithmBlockSizeInBytes,
pbOutput: null,
cbOutput: 0,
pcbResult: out dwEstimatedDecryptedByteCount,
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
byte[] decryptedPayload = new byte[dwEstimatedDecryptedByteCount];
uint dwActualDecryptedByteCount;
fixed (byte* pbDecryptedPayload = decryptedPayload)
{
byte dummy;
// Perform the actual decryption.
ntstatus = UnsafeNativeMethods.BCryptDecrypt(
hKey: symmetricKeyHandle,
pbInput: pbInput,
cbInput: cbInput,
pPaddingInfo: null,
pbIV: pbClonedIV,
cbIV: _symmetricAlgorithmBlockSizeInBytes,
pbOutput: (pbDecryptedPayload != null) ? pbDecryptedPayload : &dummy, // CLR won't pin zero-length arrays
cbOutput: dwEstimatedDecryptedByteCount,
pcbResult: out dwActualDecryptedByteCount,
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
}
// Decryption finished!
CryptoUtil.Assert(dwActualDecryptedByteCount <= dwEstimatedDecryptedByteCount, "dwActualDecryptedByteCount <= dwEstimatedDecryptedByteCount");
if (dwActualDecryptedByteCount == dwEstimatedDecryptedByteCount)
{
// payload takes up the entire buffer
return decryptedPayload;
}
else
{
// payload takes up only a partial buffer
byte[] resizedDecryptedPayload = new byte[dwActualDecryptedByteCount];
Buffer.BlockCopy(decryptedPayload, 0, resizedDecryptedPayload, 0, resizedDecryptedPayload.Length);
return resizedDecryptedPayload;
}
}
// 'pbIV' must be a pointer to a buffer equal in length to the symmetric algorithm block size.
private void DoCbcEncrypt(BCryptKeyHandle symmetricKeyHandle, byte* pbIV, byte* pbInput, uint cbInput, byte* pbOutput, uint cbOutput)
{
// BCryptEncrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
uint dwEncryptedBytes;
int ntstatus = UnsafeNativeMethods.BCryptEncrypt(
hKey: symmetricKeyHandle,
pbInput: pbInput,
cbInput: cbInput,
pPaddingInfo: null,
pbIV: pbClonedIV,
cbIV: _symmetricAlgorithmBlockSizeInBytes,
pbOutput: pbOutput,
cbOutput: cbOutput,
pcbResult: out dwEncryptedBytes,
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
// Need to make sure we didn't underrun the buffer - means caller passed a bad value
CryptoUtil.Assert(dwEncryptedBytes == cbOutput, "dwEncryptedBytes == cbOutput");
}
protected override byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer)
{
// This buffer will be used to hold the symmetric encryption and HMAC subkeys
// used in the generation of this payload.
uint cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
try
{
// Randomly generate the key modifier and IV.
uint cbKeyModifierAndIV = checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes);
byte* pbKeyModifierAndIV = stackalloc byte[checked((int)cbKeyModifierAndIV)];
_genRandom.GenRandom(pbKeyModifierAndIV, cbKeyModifierAndIV);
// Calculate offsets
byte* pbKeyModifier = pbKeyModifierAndIV;
byte* pbIV = &pbKeyModifierAndIV[KEY_MODIFIER_SIZE_IN_BYTES];
// Use the KDF to generate a new symmetric encryption and HMAC subkey
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
pbLabel: pbAdditionalAuthenticatedData,
cbLabel: cbAdditionalAuthenticatedData,
contextHeader: _contextHeader,
pbContext: pbKeyModifier,
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
pbDerivedKey: pbTempSubkeys,
cbDerivedKey: cbTempSubkeys);
// Calculate offsets
byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
{
// We can't assume PKCS#7 padding (maybe the underlying provided is using CTS),
// so we need to query the padded output size before we can allocate the return value array.
uint cbOutputCiphertext = GetCbcEncryptedOutputSizeWithPadding(symmetricKeyHandle, pbPlaintext, cbPlaintext);
// Allocate return value array and start copying some data
byte[] retVal = new byte[checked(cbPreBuffer + KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + cbOutputCiphertext + _hmacAlgorithmDigestLengthInBytes + cbPostBuffer)];
fixed (byte* pbRetVal = retVal)
{
// Calculate offsets
byte* pbOutputKeyModifier = &pbRetVal[cbPreBuffer];
byte* pbOutputIV = &pbOutputKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
byte* pbOutputCiphertext = &pbOutputIV[_symmetricAlgorithmBlockSizeInBytes];
byte* pbOutputHmac = &pbOutputCiphertext[cbOutputCiphertext];
UnsafeBufferUtil.BlockCopy(from: pbKeyModifierAndIV, to: pbOutputKeyModifier, byteCount: cbKeyModifierAndIV);
// retVal will eventually contain { preBuffer | keyModifier | iv | encryptedData | HMAC(iv | encryptedData) | postBuffer }
// At this point, retVal := { preBuffer | keyModifier | iv | _____ | _____ | postBuffer }
DoCbcEncrypt(
symmetricKeyHandle: symmetricKeyHandle,
pbIV: pbIV,
pbInput: pbPlaintext,
cbInput: cbPlaintext,
pbOutput: pbOutputCiphertext,
cbOutput: cbOutputCiphertext);
// At this point, retVal := { preBuffer | keyModifier | iv | encryptedData | _____ | postBuffer }
// Compute the HMAC over the IV and the ciphertext (prevents IV tampering).
// The HMAC is already implicitly computed over the key modifier since the key
// modifier is used as input to the KDF.
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
{
hashHandle.HashData(
pbInput: pbOutputIV,
cbInput: checked(_symmetricAlgorithmBlockSizeInBytes + cbOutputCiphertext),
pbHashDigest: pbOutputHmac,
cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
}
// At this point, retVal := { preBuffer | keyModifier | iv | encryptedData | HMAC(iv | encryptedData) | postBuffer }
// And we're done!
return retVal;
}
}
}
finally
{
// Buffer contains sensitive material; nuke it.
UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
}
}
private uint GetCbcEncryptedOutputSizeWithPadding(BCryptKeyHandle symmetricKeyHandle, byte* pbInput, uint cbInput)
{
// ok for this memory to remain uninitialized since nobody depends on it
byte* pbIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
// Calling BCryptEncrypt with a null output pointer will cause it to return the total number
// of bytes required for the output buffer.
uint dwResult;
int ntstatus = UnsafeNativeMethods.BCryptEncrypt(
hKey: symmetricKeyHandle,
pbInput: pbInput,
cbInput: cbInput,
pPaddingInfo: null,
pbIV: pbIV,
cbIV: _symmetricAlgorithmBlockSizeInBytes,
pbOutput: null,
cbOutput: 0,
pcbResult: out dwResult,
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
return dwResult;
}
// 'pbExpectedDigest' must point to a '_hmacAlgorithmDigestLengthInBytes'-length buffer
private bool ValidateHash(BCryptHashHandle hashHandle, byte* pbInput, uint cbInput, byte* pbExpectedDigest)
{
byte* pbActualDigest = stackalloc byte[checked((int)_hmacAlgorithmDigestLengthInBytes)];
hashHandle.HashData(pbInput, cbInput, pbActualDigest, _hmacAlgorithmDigestLengthInBytes);
return CryptoUtil.TimeConstantBuffersAreEqual(pbExpectedDigest, pbActualDigest, _hmacAlgorithmDigestLengthInBytes);
}
}
}

View File

@ -0,0 +1,85 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
internal unsafe abstract class CngAuthenticatedEncryptorBase : IAuthenticatedEncryptor, IDisposable
{
public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
{
// This wrapper simply converts ArraySegment<byte> to byte* and calls the impl method.
// Input validation
ciphertext.Validate();
additionalAuthenticatedData.Validate();
byte dummy; // used only if plaintext or AAD is empty, since otherwise 'fixed' returns null pointer
fixed (byte* pbCiphertextArray = ciphertext.Array)
{
fixed (byte* pbAdditionalAuthenticatedDataArray = additionalAuthenticatedData.Array)
{
try
{
return DecryptImpl(
pbCiphertext: (pbCiphertextArray != null) ? &pbCiphertextArray[ciphertext.Offset] : &dummy,
cbCiphertext: (uint)ciphertext.Count,
pbAdditionalAuthenticatedData: (pbAdditionalAuthenticatedDataArray != null) ? &pbAdditionalAuthenticatedDataArray[additionalAuthenticatedData.Offset] : &dummy,
cbAdditionalAuthenticatedData: (uint)additionalAuthenticatedData.Count);
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// Homogenize to CryptographicException.
throw Error.CryptCommon_GenericError(ex);
}
}
}
}
protected abstract byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData);
public abstract void Dispose();
public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
{
return Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
}
public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
{
// This wrapper simply converts ArraySegment<byte> to byte* and calls the impl method.
// Input validation
plaintext.Validate();
additionalAuthenticatedData.Validate();
byte dummy; // used only if plaintext or AAD is empty, since otherwise 'fixed' returns null pointer
fixed (byte* pbPlaintextArray = plaintext.Array)
{
fixed (byte* pbAdditionalAuthenticatedDataArray = additionalAuthenticatedData.Array)
{
try
{
return EncryptImpl(
pbPlaintext: (pbPlaintextArray != null) ? &pbPlaintextArray[plaintext.Offset] : &dummy,
cbPlaintext: (uint)plaintext.Count,
pbAdditionalAuthenticatedData: (pbAdditionalAuthenticatedDataArray != null) ? &pbAdditionalAuthenticatedDataArray[additionalAuthenticatedData.Offset] : &dummy,
cbAdditionalAuthenticatedData: (uint)additionalAuthenticatedData.Count,
cbPreBuffer: preBufferSize,
cbPostBuffer: postBufferSize);
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// Homogenize to CryptographicException.
throw Error.CryptCommon_GenericError(ex);
}
}
}
}
protected abstract byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer);
}
}

View File

@ -0,0 +1,296 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
internal unsafe static class DpapiSecretSerializerHelper
{
// from ncrypt.h
private const uint NCRYPT_SILENT_FLAG = 0x00000040;
// from dpapi.h
private const uint CRYPTPROTECT_UI_FORBIDDEN = 0x1;
private const uint CRYPTPROTECT_LOCAL_MACHINE = 0x4;
private static readonly byte[] _purpose = Encoding.UTF8.GetBytes("DPAPI-Protected Secret");
public static byte[] ProtectWithDpapi(ISecret secret)
{
Debug.Assert(secret != null);
byte[] plaintextSecret = new byte[secret.Length];
fixed (byte* pbPlaintextSecret = plaintextSecret)
{
try
{
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
fixed (byte* pbPurpose = _purpose)
{
return ProtectWithDpapiImpl(pbPlaintextSecret, (uint)plaintextSecret.Length, pbPurpose, (uint)_purpose.Length);
}
}
finally
{
// To limit exposure to the GC.
Array.Clear(plaintextSecret, 0, plaintextSecret.Length);
}
}
}
internal static byte[] ProtectWithDpapiImpl(byte* pbSecret, uint cbSecret, byte* pbOptionalEntropy, uint cbOptionalEntropy, bool fLocalMachine = false)
{
byte dummy; // provides a valid memory address if the secret or entropy has zero length
DATA_BLOB dataIn = new DATA_BLOB()
{
cbData = cbSecret,
pbData = (pbSecret != null) ? pbSecret : &dummy
};
DATA_BLOB entropy = new DATA_BLOB()
{
cbData = cbOptionalEntropy,
pbData = (pbOptionalEntropy != null) ? pbOptionalEntropy : &dummy
};
DATA_BLOB dataOut = default(DATA_BLOB);
#if !ASPNETCORE50
RuntimeHelpers.PrepareConstrainedRegions();
#endif
try
{
bool success = UnsafeNativeMethods.CryptProtectData(
pDataIn: &dataIn,
szDataDescr: IntPtr.Zero,
pOptionalEntropy: &entropy,
pvReserved: IntPtr.Zero,
pPromptStruct: IntPtr.Zero,
dwFlags: CRYPTPROTECT_UI_FORBIDDEN | ((fLocalMachine) ? CRYPTPROTECT_LOCAL_MACHINE : 0),
pDataOut: out dataOut);
if (!success)
{
int errorCode = Marshal.GetLastWin32Error();
throw new CryptographicException(errorCode);
}
int dataLength = checked((int)dataOut.cbData);
byte[] retVal = new byte[dataLength];
Marshal.Copy((IntPtr)dataOut.pbData, retVal, 0, dataLength);
return retVal;
}
finally
{
// Free memory so that we don't leak.
// FreeHGlobal actually calls LocalFree.
if (dataOut.pbData != null)
{
Marshal.FreeHGlobal((IntPtr)dataOut.pbData);
}
}
}
public static byte[] ProtectWithDpapiNG(ISecret secret, NCryptDescriptorHandle protectionDescriptorHandle)
{
Debug.Assert(secret != null);
Debug.Assert(protectionDescriptorHandle != null);
byte[] plaintextSecret = new byte[secret.Length];
fixed (byte* pbPlaintextSecret = plaintextSecret)
{
try
{
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
byte dummy; // used to provide a valid memory address if secret is zero-length
return ProtectWithDpapiNGImpl(
protectionDescriptorHandle: protectionDescriptorHandle,
pbData: (pbPlaintextSecret != null) ? pbPlaintextSecret : &dummy,
cbData: (uint)plaintextSecret.Length);
}
finally
{
// Limits secret exposure to garbage collector.
Array.Clear(plaintextSecret, 0, plaintextSecret.Length);
}
}
}
private static byte[] ProtectWithDpapiNGImpl(NCryptDescriptorHandle protectionDescriptorHandle, byte* pbData, uint cbData)
{
Debug.Assert(protectionDescriptorHandle != null);
Debug.Assert(pbData != null);
// Perform the encryption operation, putting the protected data into LocalAlloc-allocated memory.
LocalAllocHandle protectedData;
uint cbProtectedData;
int ntstatus = UnsafeNativeMethods.NCryptProtectSecret(
hDescriptor: protectionDescriptorHandle,
dwFlags: NCRYPT_SILENT_FLAG,
pbData: pbData,
cbData: cbData,
pMemPara: IntPtr.Zero,
hWnd: IntPtr.Zero,
ppbProtectedBlob: out protectedData,
pcbProtectedBlob: out cbProtectedData);
UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
CryptoUtil.Assert(protectedData != null && !protectedData.IsInvalid, "protectedData != null && !protectedData.IsInvalid");
// Copy the data from LocalAlloc-allocated memory into a managed memory buffer.
using (protectedData)
{
byte[] retVal = new byte[cbProtectedData];
if (cbProtectedData > 0)
{
fixed (byte* pbRetVal = retVal)
{
bool handleAcquired = false;
#if !ASPNETCORE50
RuntimeHelpers.PrepareConstrainedRegions();
#endif
try
{
protectedData.DangerousAddRef(ref handleAcquired);
UnsafeBufferUtil.BlockCopy(from: (void*)protectedData.DangerousGetHandle(), to: pbRetVal, byteCount: cbProtectedData);
}
finally
{
if (handleAcquired)
{
protectedData.DangerousRelease();
}
}
}
}
return retVal;
}
}
public static ProtectedMemoryBlob UnprotectWithDpapi(byte[] protectedSecret)
{
Debug.Assert(protectedSecret != null);
fixed (byte* pbProtectedSecret = protectedSecret)
{
fixed (byte* pbPurpose = _purpose)
{
return UnprotectWithDpapiImpl(pbProtectedSecret, (uint)protectedSecret.Length, pbPurpose, (uint)_purpose.Length);
}
}
}
internal static ProtectedMemoryBlob UnprotectWithDpapiImpl(byte* pbProtectedData, uint cbProtectedData, byte* pbOptionalEntropy, uint cbOptionalEntropy)
{
byte dummy; // provides a valid memory address if the secret or entropy has zero length
DATA_BLOB dataIn = new DATA_BLOB()
{
cbData = cbProtectedData,
pbData = (pbProtectedData != null) ? pbProtectedData : &dummy
};
DATA_BLOB entropy = new DATA_BLOB()
{
cbData = cbOptionalEntropy,
pbData = (pbOptionalEntropy != null) ? pbOptionalEntropy : &dummy
};
DATA_BLOB dataOut = default(DATA_BLOB);
#if !ASPNETCORE50
RuntimeHelpers.PrepareConstrainedRegions();
#endif
try
{
bool success = UnsafeNativeMethods.CryptUnprotectData(
pDataIn: &dataIn,
ppszDataDescr: IntPtr.Zero,
pOptionalEntropy: &entropy,
pvReserved: IntPtr.Zero,
pPromptStruct: IntPtr.Zero,
dwFlags: CRYPTPROTECT_UI_FORBIDDEN,
pDataOut: out dataOut);
if (!success)
{
int errorCode = Marshal.GetLastWin32Error();
throw new CryptographicException(errorCode);
}
return new ProtectedMemoryBlob(dataOut.pbData, checked((int)dataOut.cbData));
}
finally
{
// Zero and free memory so that we don't leak secrets.
// FreeHGlobal actually calls LocalFree.
if (dataOut.pbData != null)
{
UnsafeBufferUtil.SecureZeroMemory(dataOut.pbData, dataOut.cbData);
Marshal.FreeHGlobal((IntPtr)dataOut.pbData);
}
}
}
public static ProtectedMemoryBlob UnprotectWithDpapiNG(byte[] protectedData)
{
Debug.Assert(protectedData != null);
fixed (byte* pbProtectedData = protectedData)
{
byte dummy; // used to provide a valid memory address if protected data is zero-length
return UnprotectWithDpapiNGImpl(
pbData: (pbProtectedData != null) ? pbProtectedData : &dummy,
cbData: (uint)protectedData.Length);
}
}
private static ProtectedMemoryBlob UnprotectWithDpapiNGImpl(byte* pbData, uint cbData)
{
Debug.Assert(pbData != null);
// First, decrypt the payload into LocalAlloc-allocated memory.
LocalAllocHandle unencryptedPayloadHandle;
uint cbUnencryptedPayload;
int ntstatus = UnsafeNativeMethods.NCryptUnprotectSecret(
phDescriptor: IntPtr.Zero,
dwFlags: NCRYPT_SILENT_FLAG,
pbProtectedBlob: pbData,
cbProtectedBlob: cbData,
pMemPara: IntPtr.Zero,
hWnd: IntPtr.Zero,
ppbData: out unencryptedPayloadHandle,
pcbData: out cbUnencryptedPayload);
UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
CryptoUtil.Assert(unencryptedPayloadHandle != null && !unencryptedPayloadHandle.IsInvalid, "unencryptedPayloadHandle != null && !unencryptedPayloadHandle.IsInvalid");
// Copy the data from LocalAlloc-allocated memory into a CryptProtectMemory-protected buffer.
// There's a small window between NCryptUnprotectSecret returning and the call to PrepareConstrainedRegions
// below where the AppDomain could rudely unload. This won't leak memory (due to the SafeHandle), but it
// will cause the secret not to be zeroed out before the memory is freed. We won't worry about this since
// the window is extremely small and AppDomain unloads should not happen here in practice.
using (unencryptedPayloadHandle)
{
bool handleAcquired = false;
#if !ASPNETCORE50
RuntimeHelpers.PrepareConstrainedRegions();
#endif
try
{
unencryptedPayloadHandle.DangerousAddRef(ref handleAcquired);
return new ProtectedMemoryBlob((byte*)unencryptedPayloadHandle.DangerousGetHandle(), checked((int)cbUnencryptedPayload));
}
finally
{
if (handleAcquired)
{
UnsafeBufferUtil.SecureZeroMemory((byte*)unencryptedPayloadHandle.DangerousGetHandle(), cbUnencryptedPayload);
unencryptedPayloadHandle.DangerousRelease();
}
}
}
}
}
}

View File

@ -0,0 +1,289 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
using Microsoft.AspNet.Security.DataProtection.SP800_108;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
// GCM is defined in NIST SP 800-38D (http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf).
// Heed closely the uniqueness requirements called out in Sec. 8: the probability that the GCM encryption
// routine is ever invoked on two or more distinct sets of input data with the same (key, IV) shall not
// exceed 2^-32. If we fix the key and use a random 96-bit IV for each invocation, this means that after
// 2^32 encryption operations the odds of reusing any (key, IV) pair is 2^-32 (see Sec. 8.3). This won't
// work for our use since a high-traffic web server can go through 2^32 requests in mere days. Instead,
// we'll use 224 bits of entropy for each operation, with 128 bits going to the KDF and 96 bits
// going to the IV. This means that we'll only hit the 2^-32 probability limit after 2^96 encryption
// operations, which will realistically never happen. (At the absurd rate of one encryption operation
// per nanosecond, it would still take 180 times the age of the universe to hit 2^96 operations.)
internal unsafe sealed class GcmAuthenticatedEncryptor : CngAuthenticatedEncryptorBase
{
// Having a key modifier ensures with overwhelming probability that no two encryption operations
// will ever derive the same (encryption subkey, MAC subkey) pair. This limits an attacker's
// ability to mount a key-dependent chosen ciphertext attack. See also the class-level comment
// for how this is used to overcome GCM's IV limitations.
private const uint KEY_MODIFIER_SIZE_IN_BYTES = 128 / 8;
private const uint NONCE_SIZE_IN_BYTES = 96 / 8; // GCM has a fixed 96-bit IV
private const uint TAG_SIZE_IN_BYTES = 128 / 8; // we're hardcoding a 128-bit authentication tag size
private readonly byte[] _contextHeader;
private readonly IBCryptGenRandom _genRandom;
private readonly ISP800_108_CTR_HMACSHA512Provider _sp800_108_ctr_hmac_provider;
private readonly BCryptAlgorithmHandle _symmetricAlgorithmHandle;
private readonly uint _symmetricAlgorithmSubkeyLengthInBytes;
public GcmAuthenticatedEncryptor(ProtectedMemoryBlob keyDerivationKey, BCryptAlgorithmHandle symmetricAlgorithmHandle, uint symmetricAlgorithmKeySizeInBytes, IBCryptGenRandom genRandom = null)
{
CryptoUtil.Assert(KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES,
"KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES");
_genRandom = genRandom ?? BCryptGenRandomImpl.Instance;
_sp800_108_ctr_hmac_provider = SP800_108_CTR_HMACSHA512Util.CreateProvider(keyDerivationKey);
_symmetricAlgorithmHandle = symmetricAlgorithmHandle;
_symmetricAlgorithmSubkeyLengthInBytes = symmetricAlgorithmKeySizeInBytes;
_contextHeader = CreateContextHeader();
}
private byte[] CreateContextHeader()
{
byte[] retVal = new byte[checked(
1 /* KDF alg */
+ 1 /* chaining mode */
+ sizeof(uint) /* sym alg key size */
+ sizeof(uint) /* GCM nonce size */
+ sizeof(uint) /* sym alg block size */
+ sizeof(uint) /* GCM tag size */
+ TAG_SIZE_IN_BYTES /* tag of GCM-encrypted empty string */)];
fixed (byte* pbRetVal = retVal)
{
byte* ptr = pbRetVal;
// First is the two-byte header
*(ptr++) = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
*(ptr++) = 1; // 0x01 = GCM encryption + authentication
// Next is information about the symmetric algorithm (key size, nonce size, block size, tag size)
BitHelpers.WriteTo(ptr, _symmetricAlgorithmSubkeyLengthInBytes);
ptr += sizeof(uint);
BitHelpers.WriteTo(ptr, NONCE_SIZE_IN_BYTES);
ptr += sizeof(uint);
BitHelpers.WriteTo(ptr, TAG_SIZE_IN_BYTES); // block size
ptr += sizeof(uint);
BitHelpers.WriteTo(ptr, TAG_SIZE_IN_BYTES);
ptr += sizeof(uint);
// See the design document for an explanation of the following code.
byte[] tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
fixed (byte* pbTempKeys = tempKeys)
{
byte dummy;
// Derive temporary key for encryption.
using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider())
{
provider.DeriveKey(
pbLabel: &dummy,
cbLabel: 0,
pbContext: &dummy,
cbContext: 0,
pbDerivedKey: pbTempKeys,
cbDerivedKey: (uint)tempKeys.Length);
}
// Encrypt a zero-length input string with an all-zero nonce and copy the tag to the return buffer.
byte* pbNonce = stackalloc byte[(int)NONCE_SIZE_IN_BYTES];
UnsafeBufferUtil.SecureZeroMemory(pbNonce, NONCE_SIZE_IN_BYTES);
DoGcmEncrypt(
pbKey: pbTempKeys,
cbKey: _symmetricAlgorithmSubkeyLengthInBytes,
pbNonce: pbNonce,
pbPlaintextData: &dummy,
cbPlaintextData: 0,
pbEncryptedData: &dummy,
pbTag: ptr);
}
ptr += TAG_SIZE_IN_BYTES;
CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length");
}
// retVal := { version || chainingMode || symAlgKeySize || nonceSize || symAlgBlockSize || symAlgTagSize || TAG-of-E("") }.
return retVal;
}
protected override byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
{
// Argument checking: input must at the absolute minimum contain a key modifier, nonce, and tag
if (cbCiphertext < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)
{
throw Error.CryptCommon_PayloadInvalid();
}
// Assumption: pbCipherText := { keyModifier || nonce || encryptedData || authenticationTag }
uint cbPlaintext = checked(cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES));
byte[] retVal = new byte[cbPlaintext];
fixed (byte* pbRetVal = retVal)
{
// Calculate offsets
byte* pbKeyModifier = pbCiphertext;
byte* pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
byte* pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES];
byte* pbAuthTag = &pbEncryptedData[cbPlaintext];
// Use the KDF to recreate the symmetric block cipher key
// We'll need a temporary buffer to hold the symmetric encryption subkey
byte* pbSymmetricDecryptionSubkey = stackalloc byte[checked((int)_symmetricAlgorithmSubkeyLengthInBytes)];
try
{
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
pbLabel: pbAdditionalAuthenticatedData,
cbLabel: cbAdditionalAuthenticatedData,
contextHeader: _contextHeader,
pbContext: pbKeyModifier,
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
pbDerivedKey: pbSymmetricDecryptionSubkey,
cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes);
// Perform the decryption operation
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
{
byte dummy;
byte* pbPlaintext = (pbRetVal != null) ? pbRetVal : &dummy; // CLR doesn't like pinning empty buffers
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Init(out authInfo);
authInfo.pbNonce = pbNonce;
authInfo.cbNonce = NONCE_SIZE_IN_BYTES;
authInfo.pbTag = pbAuthTag;
authInfo.cbTag = TAG_SIZE_IN_BYTES;
// The call to BCryptDecrypt will also validate the authentication tag
uint cbDecryptedBytesWritten;
int ntstatus = UnsafeNativeMethods.BCryptDecrypt(
hKey: decryptionSubkeyHandle,
pbInput: pbEncryptedData,
cbInput: cbPlaintext,
pPaddingInfo: &authInfo,
pbIV: null, // IV not used; nonce provided in pPaddingInfo
cbIV: 0,
pbOutput: pbPlaintext,
cbOutput: cbPlaintext,
pcbResult: out cbDecryptedBytesWritten,
dwFlags: 0);
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
CryptoUtil.Assert(cbDecryptedBytesWritten == cbPlaintext, "cbDecryptedBytesWritten == cbPlaintext");
// At this point, retVal := { decryptedPayload }
// And we're done!
return retVal;
}
}
finally
{
// The buffer contains key material, so nuke it.
UnsafeBufferUtil.SecureZeroMemory(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes);
}
}
}
public override void Dispose()
{
_sp800_108_ctr_hmac_provider.Dispose();
// We don't want to dispose of the underlying algorithm instances because they
// might be reused.
}
// 'pbNonce' must point to a 96-bit buffer.
// 'pbTag' must point to a 128-bit buffer.
// 'pbEncryptedData' must point to a buffer the same length as 'pbPlaintextData'.
private void DoGcmEncrypt(byte* pbKey, uint cbKey, byte* pbNonce, byte* pbPlaintextData, uint cbPlaintextData, byte* pbEncryptedData, byte* pbTag)
{
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authCipherInfo;
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Init(out authCipherInfo);
authCipherInfo.pbNonce = pbNonce;
authCipherInfo.cbNonce = NONCE_SIZE_IN_BYTES;
authCipherInfo.pbTag = pbTag;
authCipherInfo.cbTag = TAG_SIZE_IN_BYTES;
using (var keyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbKey, cbKey))
{
uint cbResult;
int ntstatus = UnsafeNativeMethods.BCryptEncrypt(
hKey: keyHandle,
pbInput: pbPlaintextData,
cbInput: cbPlaintextData,
pPaddingInfo: &authCipherInfo,
pbIV: null,
cbIV: 0,
pbOutput: pbEncryptedData,
cbOutput: cbPlaintextData,
pcbResult: out cbResult,
dwFlags: 0);
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
CryptoUtil.Assert(cbResult == cbPlaintextData, "cbResult == cbPlaintextData");
}
}
protected override byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer)
{
// Allocate a buffer to hold the key modifier, nonce, encrypted data, and tag.
// In GCM, the encrypted output will be the same length as the plaintext input.
byte[] retVal = new byte[checked(cbPreBuffer + KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + cbPlaintext + TAG_SIZE_IN_BYTES + cbPostBuffer)];
fixed (byte* pbRetVal = retVal)
{
// Calculate offsets
byte* pbKeyModifier = &pbRetVal[cbPreBuffer];
byte* pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
byte* pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES];
byte* pbAuthTag = &pbEncryptedData[cbPlaintext];
// Randomly generate the key modifier and nonce
_genRandom.GenRandom(pbKeyModifier, KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES);
// At this point, retVal := { preBuffer | keyModifier | nonce | _____ | _____ | postBuffer }
// Use the KDF to generate a new symmetric block cipher key
// We'll need a temporary buffer to hold the symmetric encryption subkey
byte* pbSymmetricEncryptionSubkey = stackalloc byte[checked((int)_symmetricAlgorithmSubkeyLengthInBytes)];
try
{
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
pbLabel: pbAdditionalAuthenticatedData,
cbLabel: cbAdditionalAuthenticatedData,
contextHeader: _contextHeader,
pbContext: pbKeyModifier,
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
pbDerivedKey: pbSymmetricEncryptionSubkey,
cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes);
// Perform the encryption operation
DoGcmEncrypt(
pbKey: pbSymmetricEncryptionSubkey,
cbKey: _symmetricAlgorithmSubkeyLengthInBytes,
pbNonce: pbNonce,
pbPlaintextData: pbPlaintext,
cbPlaintextData: cbPlaintext,
pbEncryptedData: pbEncryptedData,
pbTag: pbAuthTag);
// At this point, retVal := { preBuffer | keyModifier | nonce | encryptedData | authenticationTag | postBuffer }
// And we're done!
return retVal;
}
finally
{
// The buffer contains key material, so nuke it.
UnsafeBufferUtil.SecureZeroMemory(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes);
}
}
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
internal unsafe interface IBCryptGenRandom
{
void GenRandom(byte* pbBuffer, uint cbBuffer);
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
[Flags]
internal enum NCryptEncryptFlags
{
NCRYPT_NO_PADDING_FLAG = 0x00000001,
NCRYPT_PAD_PKCS1_FLAG = 0x00000002,
NCRYPT_PAD_OAEP_FLAG = 0x00000004,
NCRYPT_PAD_PSS_FLAG = 0x00000008,
NCRYPT_SILENT_FLAG = 0x00000040,
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection.Cng
{
internal static class OSVersionUtil
{
private static readonly OSVersion _osVersion = GetOSVersion();
private static OSVersion GetOSVersion()
{
const string BCRYPT_LIB = "bcrypt.dll";
SafeLibraryHandle bcryptLibHandle = null;
try
{
bcryptLibHandle = SafeLibraryHandle.Open(BCRYPT_LIB);
}
catch
{
// we'll handle the exceptional case later
}
if (bcryptLibHandle != null)
{
using (bcryptLibHandle)
{
if (bcryptLibHandle.DoesProcExist("BCryptKeyDerivation"))
{
// We're running on Win8+.
return OSVersion.Win8OrLater;
}
else
{
// We're running on Win7+.
return OSVersion.Win7OrLater;
}
}
}
else
{
// Not running on Win7+.
return OSVersion.NotWindows;
}
}
public static bool IsBCryptOnWin7OrLaterAvailable()
{
return (_osVersion >= OSVersion.Win7OrLater);
}
public static bool IsBCryptOnWin8OrLaterAvailable()
{
return (_osVersion >= OSVersion.Win8OrLater);
}
private enum OSVersion
{
NotWindows = 0,
Win7OrLater = 1,
Win8OrLater = 2
}
}
}

View File

@ -1,11 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
// from bcrypt.h
// The majority of these are from bcrypt.h
internal static class Constants
{
internal const int MAX_STACKALLOC_BYTES = 256; // greatest number of bytes that we'll ever allow to stackalloc in a single frame
@ -15,11 +15,11 @@ namespace Microsoft.AspNet.Security.DataProtection
internal const string BCRYPT_KEY_DATA_BLOB = "KeyDataBlob";
internal const string BCRYPT_AES_WRAP_KEY_BLOB = "Rfc3565KeyWrapBlob";
// Microsoft built-in providers.
// Microsoft built-in providers
internal const string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider";
internal const string MS_PLATFORM_CRYPTO_PROVIDER = "Microsoft Platform Crypto Provider";
// Common algorithm identifiers.
// Common algorithm identifiers
internal const string BCRYPT_RSA_ALGORITHM = "RSA";
internal const string BCRYPT_RSA_SIGN_ALGORITHM = "RSA_SIGN";
internal const string BCRYPT_DH_ALGORITHM = "DH";

View File

@ -1,31 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Helper class to populate buffers with cryptographically random data.
/// </summary>
public static class CryptRand
{
/// <summary>
/// Populates a buffer with cryptographically random data.
/// </summary>
/// <param name="buffer">The buffer to populate.</param>
public static unsafe void FillBuffer(ArraySegment<byte> buffer)
{
// the ArraySegment<> ctor performs bounds checking
var unused = new ArraySegment<byte>(buffer.Array, buffer.Offset, buffer.Count);
if (buffer.Count != 0)
{
fixed (byte* pBuffer = &buffer.Array[buffer.Offset])
{
BCryptUtil.GenRandom(pBuffer, buffer.Count);
}
}
}
}
}

View File

@ -4,9 +4,14 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Cng
#if !ASPNETCORE50
using System.Runtime.ConstrainedExecution;
#endif
namespace Microsoft.AspNet.Security.DataProtection
{
internal unsafe static class CryptoUtil
{
@ -20,6 +25,13 @@ namespace Microsoft.AspNet.Security.DataProtection.Cng
}
}
// This isn't a typical Debug.Assert; the check is always performed, even in retail builds.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertSafeHandleIsValid(SafeHandle safeHandle)
{
Assert(safeHandle != null && !safeHandle.IsInvalid, "Safe handle is invalid.");
}
// This isn't a typical Debug.Fail; an error always occurs, even in retail builds.
// This method doesn't return, but since the CLR doesn't allow specifying a 'never'
// return type, we mimic it by specifying our return type as Exception. That way
@ -31,5 +43,40 @@ namespace Microsoft.AspNet.Security.DataProtection.Cng
Debug.Fail(message);
throw new CryptographicException("Assertion failed: " + message);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static T Fail<T>(string message) where T : class
{
throw Fail(message);
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
#if !ASPNETCORE50
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
#endif
public static bool TimeConstantBuffersAreEqual(byte* bufA, byte* bufB, uint count)
{
bool areEqual = true;
for (uint i = 0; i < count; i++)
{
areEqual &= (bufA[i] == bufB[i]);
}
return areEqual;
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static bool TimeConstantBuffersAreEqual(byte[] bufA, int offsetA, int countA, byte[] bufB, int offsetB, int countB)
{
// Technically this is an early exit scenario, but it means that the caller did something bizarre.
// An error at the call site isn't usable for timing attacks.
Assert(countA == countB, "countA == countB");
bool areEqual = true;
for (int i = 0; i < countA; i++)
{
areEqual &= (bufA[offsetA + i] == bufB[offsetB + i]);
}
return areEqual;
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
public class DataProtectionOptions
{
public string ApplicationDiscriminator { get; set; }
}
}

View File

@ -1,117 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
#if NET45
using System.Security.Cryptography;
#endif
using System.Text;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Provides methods for creating IDataProtectionProvider instances.
/// </summary>
public unsafe static class DataProtectionProvider
{
const int MASTER_KEY_REQUIRED_LENGTH = 512 / 8;
private static readonly byte[] MASTER_SUBKEY_GENERATOR = Encoding.ASCII.GetBytes("Microsoft.AspNet.Security.DataProtection");
/// <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()
{
return CreateFromDpapi(protectToLocalMachine: false);
}
#if NET45
// These are for mono
public static IDataProtectionProvider CreateFromLegacyDpapi()
{
return CreateFromLegacyDpapi(DataProtectionScope.CurrentUser);
}
public static IDataProtectionProvider CreateFromLegacyDpapi(DataProtectionScope scope)
{
return new ProtectedDataProtectionProvider(scope);
}
#endif
/// <summary>
/// Creates a new IDataProtectionProvider backed by DPAPI.
/// </summary>
/// <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_SUBKEY_GENERATOR, protectToLocalMachine);
}
/// <summary>
/// Creates a new IDataProtectionProvider with a randomly-generated master key.
/// </summary>
public static IDataProtectionProvider CreateNew()
{
byte* masterKey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH];
try
{
BCryptUtil.GenRandom(masterKey, MASTER_KEY_REQUIRED_LENGTH);
return CreateImpl(masterKey, MASTER_KEY_REQUIRED_LENGTH);
}
finally
{
BufferUtil.SecureZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH);
}
}
/// <summary>
/// Creates a new IDataProtectionProvider with the provided master key.
/// </summary>
public static IDataProtectionProvider CreateFromKey(byte[] masterKey)
{
if (masterKey == null)
{
throw new ArgumentNullException("masterKey");
}
if (masterKey.Length < MASTER_KEY_REQUIRED_LENGTH)
{
string errorMessage = String.Format(CultureInfo.CurrentCulture, Res.DataProtectorFactory_MasterKeyTooShort, MASTER_KEY_REQUIRED_LENGTH);
throw new ArgumentOutOfRangeException("masterKey", errorMessage);
}
fixed (byte* pMasterKey = masterKey)
{
return CreateImpl(pMasterKey, masterKey.Length);
}
}
private static DataProtectionProviderImpl CreateImpl(byte* masterKey, int masterKeyLengthInBytes)
{
// We don't use the master key directly. We derive a master subkey via HMAC_{master_key}(MASTER_SUBKEY_GENERATOR).
byte* masterSubkey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH];
try
{
using (var hashHandle = BCryptUtil.CreateHMACHandle(Algorithms.HMACSHA512AlgorithmHandle, masterKey, masterKeyLengthInBytes))
{
fixed (byte* pMasterSubkeyGenerator = MASTER_SUBKEY_GENERATOR)
{
BCryptUtil.HashData(hashHandle, pMasterSubkeyGenerator, MASTER_SUBKEY_GENERATOR.Length, masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
}
}
byte[] protectedKdk = BufferUtil.ToProtectedManagedByteArray(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
return new DataProtectionProviderImpl(protectedKdk);
}
finally
{
BufferUtil.SecureZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
}
}
}
}

View File

@ -1,32 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
internal unsafe sealed class DataProtectionProviderImpl : IDataProtectionProvider
{
private readonly byte[] _protectedKdk;
public DataProtectionProviderImpl(byte[] protectedKdk)
{
_protectedKdk = protectedKdk;
}
public IDataProtector CreateProtector(string purpose)
{
BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle;
byte[] newProtectedKdfSubkey;
BCryptUtil.DeriveKeysSP800108(_protectedKdk, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newProtectedKdfSubkey);
return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newProtectedKdfSubkey);
}
public void Dispose()
{
// no-op: we hold no protected resources
}
}
}

View File

@ -0,0 +1,153 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.Security.DataProtection.Cng;
using Microsoft.AspNet.Security.DataProtection.Dpapi;
using Microsoft.AspNet.Security.DataProtection.KeyManagement;
using Microsoft.AspNet.Security.DataProtection.Repositories;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Security.DataProtection
{
public static class DataProtectionServices
{
public static IEnumerable<IServiceDescriptor> GetDefaultServices()
{
return GetDefaultServices(new Configuration());
}
public static IEnumerable<IServiceDescriptor> GetDefaultServices(IConfiguration configuration)
{
var describe = new ServiceDescriber(configuration);
List<IServiceDescriptor> descriptors = new List<IServiceDescriptor>();
descriptors.AddRange(OptionsServices.GetDefaultServices(configuration));
descriptors.AddRange(OSVersionUtil.IsBCryptOnWin7OrLaterAvailable()
? GetDefaultServicesWindows(describe)
: GetDefaultServicesNonWindows(describe));
return descriptors;
}
private static IEnumerable<IServiceDescriptor> GetDefaultServicesNonWindows(ServiceDescriber describe)
{
// If we're not running on Windows, we can't use CNG.
// TODO: Replace this with something else. Mono's implementation of the
// DPAPI routines don't provide authenticity.
return new[]
{
describe.Instance<IDataProtectionProvider>(new DpapiDataProtectionProvider(DataProtectionScope.CurrentUser))
};
}
private static IEnumerable<IServiceDescriptor> GetDefaultServicesWindows(ServiceDescriber describe)
{
List<ServiceDescriptor> descriptors = new List<ServiceDescriptor>();
// Are we running in Azure Web Sites?
DirectoryInfo azureWebSitesKeysFolder = TryGetKeysFolderForAzureWebSites();
if (azureWebSitesKeysFolder != null)
{
// We'll use a null protector at the moment until the
// cloud DPAPI service comes online.
descriptors.AddRange(new[]
{
describe.Singleton<IXmlEncryptor,NullXmlEncryptor>(),
describe.Instance<IXmlRepository>(new FileSystemXmlRepository(azureWebSitesKeysFolder))
});
}
else
{
// Are we running with the user profile loaded?
DirectoryInfo localAppDataKeysFolder = TryGetLocalAppDataKeysFolderForUser();
if (localAppDataKeysFolder != null)
{
descriptors.AddRange(new[]
{
describe.Singleton<IXmlEncryptor, DpapiXmlEncryptor>(),
describe.Instance<IXmlRepository>(new FileSystemXmlRepository(localAppDataKeysFolder))
});
}
else
{
// Are we running with no user profile (e.g., IIS service)?
// Fall back to DPAPI for now.
// TODO: We should use the IIS auto-gen reg keys as our repository.
return new[] {
describe.Instance<IDataProtectionProvider>(new DpapiDataProtectionProvider(DataProtectionScope.LocalMachine))
};
}
}
// We use CNG CBC + HMAC by default.
descriptors.AddRange(new[]
{
describe.Singleton<IAuthenticatedEncryptorConfigurationFactory, CngCbcAuthenticatedEncryptorConfigurationFactory>(),
describe.Singleton<ITypeActivator, TypeActivator>(),
describe.Singleton<IKeyManager, XmlKeyManager>(),
describe.Singleton<IDataProtectionProvider, DefaultDataProtectionProvider>()
});
return descriptors;
}
private static DirectoryInfo TryGetKeysFolderForAzureWebSites()
{
// There are two environment variables we care about.
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID")))
{
return null;
}
string homeEnvVar = Environment.GetEnvironmentVariable("HOME");
if (String.IsNullOrEmpty(homeEnvVar))
{
return null;
}
// TODO: Remove BETA moniker from below.
string fullPathToKeys = Path.Combine(homeEnvVar, "ASP.NET", "keys-BETA");
return new DirectoryInfo(fullPathToKeys);
}
private static DirectoryInfo TryGetLocalAppDataKeysFolderForUser()
{
#if !ASPNETCORE50
// Environment.GetFolderPath returns null if the user profile isn't loaded.
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (folderPath != null)
{
// TODO: Remove BETA moniker from below.
return new DirectoryInfo(Path.Combine(folderPath, "ASP.NET", "keys-BETA"));
}
else
{
return null;
}
#else
// On core CLR, we need to fall back to environment variables.
string folderPath = Environment.GetEnvironmentVariable("LOCALAPPDATA")
?? Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "AppData", "Local");
// TODO: Remove BETA moniker from below.
DirectoryInfo retVal = new DirectoryInfo(Path.Combine(folderPath, "ASP.NET", "keys-BETA"));
try
{
retVal.Create(); // throws if we don't have access, e.g., user profile not loaded
return retVal;
} catch
{
return null;
}
#endif
}
}
}

View File

@ -1,190 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection
{
internal unsafe sealed class DataProtectorImpl : IDataProtector
{
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 byte[] _protectedKdk;
public DataProtectorImpl(BCryptKeyHandle aesKeyHandle, BCryptHashHandle hmacHashHandle, byte[] protectedKdk)
{
_aesKeyHandle = aesKeyHandle;
_hmacHashHandle = hmacHashHandle;
_protectedKdk = protectedKdk;
}
private static int CalculateTotalProtectedDataSize(int unprotectedDataSizeInBytes)
{
Debug.Assert(unprotectedDataSizeInBytes >= 0);
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()
{
return new CryptographicException(Res.DataProtectorImpl_BadEncryptedData);
}
public IDataProtector CreateSubProtector(string purpose)
{
BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle;
byte[] newProtectedKdfSubkey;
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();
}
public byte[] Protect(byte[] unprotectedData)
{
if (unprotectedData == null)
{
throw new ArgumentNullException("unprotectedData");
}
// When this method finishes, protectedData will contain { IV || ciphertext || HMAC(IV || ciphertext) }
byte[] protectedData = new byte[CalculateTotalProtectedDataSize(unprotectedData.Length)];
fixed (byte* pProtectedData = protectedData)
{
// first, generate a random IV for CBC mode encryption
byte* pIV = pProtectedData;
BCryptUtil.GenRandom(pIV, AES_IV_LENGTH_IN_BYTES);
// then, encrypt the plaintext contents
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_IV_LENGTH_IN_BYTES, pCiphertext, expectedCiphertextLength);
if (actualCiphertextLength != expectedCiphertextLength)
{
throw new InvalidOperationException("Unexpected error while encrypting data.");
}
}
// finally, calculate an HMAC over { IV || ciphertext }
byte* pMac = &pCiphertext[expectedCiphertextLength];
using (var clonedHashHandle = BCryptUtil.DuplicateHash(_hmacHashHandle))
{
// 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_IV_LENGTH_IN_BYTES + expectedCiphertextLength, pMac, MAC_LENGTH_IN_BYTES);
}
}
return protectedData;
}
public byte[] Unprotect(byte[] protectedData)
{
if (protectedData == null)
{
throw new ArgumentNullException("protectedData");
}
byte[] retVal = null;
try
{
retVal = UnprotectImpl(protectedData);
}
catch
{
// swallow all exceptions; we'll homogenize
}
if (retVal != null)
{
return retVal;
}
else
{
throw CreateGenericCryptographicException();
}
}
private byte[] UnprotectImpl(byte[] protectedData)
{
Debug.Assert(protectedData != null);
// is the protected data even long enough to be valid?
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;
}
fixed (byte* pProtectedData = protectedData)
{
// calculate pointer offsets
byte* pIV = pProtectedData;
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
byte* pCalculatedMac = stackalloc byte[MAC_LENGTH_IN_BYTES];
using (var clonedHashHandle = BCryptUtil.DuplicateHash(_hmacHashHandle))
{
// see comments in Protect(byte[]) for why we duplicate the hash
BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_IV_LENGTH_IN_BYTES + ciphertextLength, pCalculatedMac, MAC_LENGTH_IN_BYTES);
}
if (!BCryptUtil.BuffersAreEqualSecure(pSuppliedMac, pCalculatedMac, MAC_LENGTH_IN_BYTES))
{
return null; // MAC check failed
}
// next, perform the actual decryption
// we don't know the actual plaintext length, but we know it must be strictly less than the ciphertext length
int plaintextBufferLength = ciphertextLength;
byte[] heapAllocatedPlaintext = null;
if (ciphertextLength > Constants.MAX_STACKALLOC_BYTES)
{
heapAllocatedPlaintext = new byte[plaintextBufferLength];
}
fixed (byte* pHeapAllocatedPlaintext = heapAllocatedPlaintext)
{
byte* pPlaintextBuffer = pHeapAllocatedPlaintext;
if (pPlaintextBuffer == null)
{
byte* temp = stackalloc byte[plaintextBufferLength]; // will be released when frame pops
pPlaintextBuffer = temp;
}
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
return BufferUtil.ToManagedByteArray(pPlaintextBuffer, actualPlaintextLength);
}
}
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.KeyManagement;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.Fallback;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Security.DataProtection
{
public class DefaultDataProtectionProvider : IDataProtectionProvider
{
private readonly IDataProtectionProvider _innerProvider;
public DefaultDataProtectionProvider()
{
// use DI defaults
var collection = new ServiceCollection();
var defaultServices = DataProtectionServices.GetDefaultServices();
collection.Add(defaultServices);
var serviceProvider = collection.BuildServiceProvider();
_innerProvider = (IDataProtectionProvider)serviceProvider.GetService(typeof(IDataProtectionProvider));
CryptoUtil.Assert(_innerProvider != null, "_innerProvider != null");
}
public DefaultDataProtectionProvider(
[NotNull] IOptionsAccessor<DataProtectionOptions> optionsAccessor,
[NotNull] IKeyManager keyManager)
{
KeyRingBasedDataProtectionProvider rootProvider = new KeyRingBasedDataProtectionProvider(new KeyRingProvider(keyManager));
var options = optionsAccessor.Options;
_innerProvider = (!String.IsNullOrEmpty(options.ApplicationDiscriminator))
? (IDataProtectionProvider)rootProvider.CreateProtector(options.ApplicationDiscriminator)
: rootProvider;
}
public IDataProtector CreateProtector([NotNull] string purpose)
{
return _innerProvider.CreateProtector(purpose);
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// We only define this type in core CLR since desktop CLR already contains it.
#if ASPNETCORE50
using System;
namespace System.Security.Cryptography
{
//
// Summary:
// Specifies the scope of the data protection to be applied by the System.Security.Cryptography.ProtectedData.Protect(System.Byte[],System.Byte[],System.Security.Cryptography.DataProtectionScope)
// method.
internal enum DataProtectionScope
{
//
// Summary:
// The protected data is associated with the current user. Only threads running
// under the current user context can unprotect the data.
CurrentUser,
//
// Summary:
// The protected data is associated with the machine context. Any process running
// on the computer can unprotect data. This enumeration value is usually used in
// server-specific applications that run on a server where untrusted users are not
// allowed access.
LocalMachine
}
}
#endif

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Dpapi
{
// Provides a temporary implementation of IDataProtectionProvider for non-Windows machines
// or for Windows machines where we can't depend on the user profile.
internal sealed class DpapiDataProtectionProvider : IDataProtectionProvider
{
private readonly DpapiDataProtector _innerProtector;
public DpapiDataProtectionProvider(DataProtectionScope scope)
{
_innerProtector = new DpapiDataProtector(new ProtectedDataImpl(), new byte[0], scope);
}
public IDataProtector CreateProtector([NotNull] string purpose)
{
return _innerProtector.CreateProtector(purpose);
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Microsoft.AspNet.Security.DataProtection.Dpapi
{
// Provides a temporary implementation of IDataProtector for non-Windows machines
// or for Windows machines where we can't depend on the user profile.
internal sealed class DpapiDataProtector : IDataProtector
{
private static readonly UTF8Encoding _secureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private readonly byte[] _combinedPurposes;
private readonly DataProtectionScope _scope;
private readonly IProtectedData _shim;
internal DpapiDataProtector(IProtectedData shim, byte[] combinedPurposes, DataProtectionScope scope)
{
_combinedPurposes = combinedPurposes;
_scope = scope;
_shim = shim;
}
public IDataProtector CreateProtector([NotNull] string purpose)
{
// Appends the provided purpose to the existing list
using (var memoryStream = new MemoryStream())
{
memoryStream.Write(_combinedPurposes, 0, _combinedPurposes.Length);
using (var writer = new BinaryWriter(memoryStream, _secureUtf8Encoding, leaveOpen: true))
{
writer.Write(purpose);
}
return new DpapiDataProtector(_shim, memoryStream.ToArray(), _scope);
}
}
public byte[] Protect([NotNull] byte[] unprotectedData)
{
try
{
return _shim.Protect(unprotectedData, _combinedPurposes, _scope)
?? CryptoUtil.Fail<byte[]>("Null return value.");
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// Homogenize to CryptographicException
throw Error.CryptCommon_GenericError(ex);
}
}
public byte[] Unprotect([NotNull] byte[] protectedData)
{
try
{
return _shim.Unprotect(protectedData, _combinedPurposes, _scope)
?? CryptoUtil.Fail<byte[]>("Null return value.");
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// Homogenize to CryptographicException
throw Error.CryptCommon_GenericError(ex);
}
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Dpapi
{
internal interface IProtectedData
{
byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtectionScope scope);
byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope);
}
}

View File

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.Cng;
namespace Microsoft.AspNet.Security.DataProtection.Dpapi
{
internal unsafe sealed class ProtectedDataImpl : IProtectedData
{
public byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtectionScope scope)
{
#if ASPNETCORE50
fixed (byte* pbUserData = userData)
{
fixed (byte* pbOptionalEntropy = optionalEntropy)
{
return DpapiSecretSerializerHelper.ProtectWithDpapiImpl(
pbSecret: pbUserData,
cbSecret: (userData != null) ? (uint)userData.Length : 0,
pbOptionalEntropy: pbOptionalEntropy,
cbOptionalEntropy: (optionalEntropy != null) ? (uint)optionalEntropy.Length : 0,
fLocalMachine: (scope == DataProtectionScope.LocalMachine));
}
}
#else
return ProtectedData.Protect(userData, optionalEntropy, scope);
#endif
}
public byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope)
{
#if ASPNETCORE50
ProtectedMemoryBlob blob;
fixed (byte* pbEncryptedData = encryptedData)
{
fixed (byte* pbOptionalEntropy = optionalEntropy)
{
blob = DpapiSecretSerializerHelper.UnprotectWithDpapiImpl(
pbProtectedData: pbEncryptedData,
cbProtectedData: (encryptedData != null) ? (uint)encryptedData.Length : 0,
pbOptionalEntropy: pbOptionalEntropy,
cbOptionalEntropy: (optionalEntropy != null) ? (uint)optionalEntropy.Length : 0);
}
}
using (blob)
{
byte[] retVal = new byte[blob.Length];
blob.WriteSecretIntoBuffer(new ArraySegment<byte>(retVal));
return retVal;
}
#else
return ProtectedData.Unprotect(encryptedData, optionalEntropy, scope);
#endif
}
}
}

View File

@ -1,31 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
namespace Microsoft.AspNet.Security.DataProtection
{
internal sealed class DpapiDataProtectionProviderImpl : IDataProtectionProvider
{
private readonly byte[] _entropy;
private readonly bool _protectToLocalMachine;
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), _protectToLocalMachine);
}
public void Dispose()
{
// no-op; no unmanaged resources to dispose
}
}
}

View File

@ -1,161 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.Util;
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;
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)
{
string message = (isErrorDueToProfileNotLoaded) ? Res.DpapiDataProtectorImpl_ProfileNotLoaded : Res.DataProtectorImpl_BadEncryptedData;
return new CryptographicException(message);
}
public IDataProtector CreateSubProtector(string purpose)
{
return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose), _protectToLocalMachine);
}
public void 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)
{
if (unprotectedData == null)
{
throw new ArgumentNullException("unprotectedData");
}
DATA_BLOB dataOut = default(DATA_BLOB);
#if NET45
RuntimeHelpers.PrepareConstrainedRegions();
#endif
try
{
bool success;
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, GetCryptProtectUnprotectFlags(), out dataOut);
}
}
// Did a failure occur?
if (!success)
{
int errorCode = Marshal.GetLastWin32Error();
bool isErrorDueToProfileNotLoaded = ((errorCode & 0xffff) == 2 /* ERROR_FILE_NOT_FOUND */);
throw CreateGenericCryptographicException(isErrorDueToProfileNotLoaded);
}
// OOMs may be marked as success but won't return a valid pointer
if (dataOut.pbData == null)
{
throw new OutOfMemoryException();
}
return BufferUtil.ToManagedByteArray(dataOut.pbData, dataOut.cbData);
}
finally
{
// per MSDN, we need to use LocalFree (implemented by Marshal.FreeHGlobal) to clean up CAPI-allocated memory
if (dataOut.pbData != null)
{
Marshal.FreeHGlobal((IntPtr)dataOut.pbData);
}
}
}
public byte[] Unprotect(byte[] protectedData)
{
if (protectedData == null)
{
throw new ArgumentNullException("protectedData");
}
DATA_BLOB dataOut = default(DATA_BLOB);
#if NET45
RuntimeHelpers.PrepareConstrainedRegions();
#endif
try
{
bool success;
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, GetCryptProtectUnprotectFlags(), out dataOut);
}
}
// Did a failure occur?
if (!success)
{
throw CreateGenericCryptographicException();
}
// OOMs may be marked as success but won't return a valid pointer
if (dataOut.pbData == null)
{
throw new OutOfMemoryException();
}
return BufferUtil.ToManagedByteArray(dataOut.pbData, dataOut.cbData);
}
finally
{
// per MSDN, we need to use LocalFree (implemented by Marshal.FreeHGlobal) to clean up CAPI-allocated memory
if (dataOut.pbData != null)
{
Marshal.FreeHGlobal((IntPtr)dataOut.pbData);
}
}
}
}
}

View File

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.Security.DataProtection.Cng;
using Microsoft.AspNet.Security.DataProtection.KeyManagement;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// An IDataProtectionProvider that is transient.
/// </summary>
/// <remarks>
/// Payloads generated by a given EphemeralDataProtectionProvider instance can only
/// be deciphered by that same instance. Once the instance is lost, all ciphertexts
/// generated by that instance are permanently undecipherable.
/// </remarks>
public sealed class EphemeralDataProtectionProvider : IDataProtectionProvider
{
private readonly KeyRingBasedDataProtectionProvider _dataProtectionProvider;
public EphemeralDataProtectionProvider()
{
IKeyRingProvider keyringProvider;
if (OSVersionUtil.IsBCryptOnWin7OrLaterAvailable())
{
// Fastest implementation: AES-GCM
keyringProvider = new CngEphemeralKeyRing();
}
else
{
// Slowest implementation: managed CBC + HMAC
keyringProvider = new ManagedEphemeralKeyRing();
}
_dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyringProvider);
}
public IDataProtector CreateProtector([NotNull] string purpose)
{
// just forward to the underlying provider
return _dataProtectionProvider.CreateProtector(purpose);
}
private sealed class DefaultOptionsAccessor<T> : IOptionsAccessor<T> where T : class, new()
{
public T Options { get; } = new T();
public T GetNamedOptions(string name)
{
return Options;
}
}
// A special key ring that only understands one key id and which uses CNG.
private sealed class CngEphemeralKeyRing : IKeyRing, IKeyRingProvider
{
public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor { get; } = new CngGcmAuthenticatedEncryptorConfigurationFactory(new DefaultOptionsAccessor<CngGcmAuthenticatedEncryptorConfigurationOptions>()).CreateNewConfiguration().CreateEncryptorInstance();
public Guid DefaultKeyId { get; } = default(Guid);
public IAuthenticatedEncryptor GetAuthenticatedEncryptorByKeyId(Guid keyId, out bool isRevoked)
{
isRevoked = false;
return (keyId == default(Guid)) ? DefaultAuthenticatedEncryptor : null;
}
public IKeyRing GetCurrentKeyRing()
{
return this;
}
}
// A special key ring that only understands one key id and which uses managed CBC + HMAC.
private sealed class ManagedEphemeralKeyRing : IKeyRing, IKeyRingProvider
{
public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor { get; } = new ManagedAuthenticatedEncryptorConfigurationFactory(new DefaultOptionsAccessor<ManagedAuthenticatedEncryptorConfigurationOptions>()).CreateNewConfiguration().CreateEncryptorInstance();
public Guid DefaultKeyId { get; } = default(Guid);
public IAuthenticatedEncryptor GetAuthenticatedEncryptorByKeyId(Guid keyId, out bool isRevoked)
{
isRevoked = false;
return (keyId == default(Guid)) ? DefaultAuthenticatedEncryptor : null;
}
public IKeyRing GetCurrentKeyRing()
{
return this;
}
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection
{
internal static class Error
{
public static CryptographicException BCryptAlgorithmHandle_ProviderNotFound(string algorithmId)
{
string message = String.Format(CultureInfo.CurrentCulture, Resources.BCryptAlgorithmHandle_ProviderNotFound, algorithmId);
return new CryptographicException(message);
}
public static ArgumentException Common_BufferIncorrectlySized(string parameterName, int actualSize, int expectedSize)
{
string message = String.Format(CultureInfo.CurrentCulture, Resources.Common_BufferIncorrectlySized, actualSize, expectedSize);
return new ArgumentException(message, parameterName);
}
public static CryptographicException CryptCommon_GenericError(Exception inner = null)
{
return new CryptographicException(Resources.CryptCommon_GenericError, inner);
}
public static CryptographicException CryptCommon_PayloadInvalid()
{
string message = Resources.CryptCommon_PayloadInvalid;
return new CryptographicException(message);
}
public static InvalidOperationException Common_PropertyCannotBeNullOrEmpty(string propertyName)
{
string message = String.Format(CultureInfo.CurrentCulture, Resources.Common_PropertyCannotBeNullOrEmpty, propertyName);
throw new InvalidOperationException(message);
}
public static CryptographicException Common_EncryptionFailed(Exception inner = null)
{
return new CryptographicException(Resources.Common_EncryptionFailed, inner);
}
public static CryptographicException Common_KeyNotFound(Guid id)
{
string message = String.Format(CultureInfo.CurrentCulture, Resources.Common_KeyNotFound, id);
return new CryptographicException(message);
}
public static CryptographicException Common_KeyRevoked(Guid id)
{
string message = String.Format(CultureInfo.CurrentCulture, Resources.Common_KeyRevoked, id);
return new CryptographicException(message);
}
public static CryptographicException Common_NotAValidProtectedPayload()
{
return new CryptographicException(Resources.Common_NotAValidProtectedPayload);
}
public static CryptographicException Common_PayloadProducedByNewerVersion()
{
return new CryptographicException(Resources.Common_PayloadProducedByNewerVersion);
}
public static CryptographicException DecryptionFailed(Exception inner)
{
return new CryptographicException(Resources.Common_DecryptionFailed, inner);
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -6,15 +6,21 @@ using System;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// A factory that can provide IDataProtector instances.
/// An interface that can be used to create IDataProtector instances.
/// </summary>
public interface IDataProtectionProvider : IDisposable
public interface IDataProtectionProvider
{
/// <summary>
/// Given a purpose, returns a new IDataProtector that has unique cryptographic keys tied to this purpose.
/// Creates an IDataProtector given a purpose.
/// </summary>
/// <param name="purpose">The consumer of the IDataProtector.</param>
/// <returns>An IDataProtector.</returns>
/// <param name="purposes">
/// The purpose to be assigned to the newly-created IDataProtector.
/// This parameter must be unique for the intended use case; two different IDataProtector
/// instances created with two different 'purpose' strings will not be able
/// to understand each other's payloads. The 'purpose' parameter is not intended to be
/// kept secret.
/// </param>
/// <returns>An IDataProtector tied to the provided purpose.</returns>
IDataProtector CreateProtector(string purpose);
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -6,33 +6,26 @@ using System;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Represents an object that can perform cryptographic operations.
/// An interface that can provide data protection services.
/// </summary>
public interface IDataProtector : IDisposable
public interface IDataProtector : IDataProtectionProvider
{
/// <summary>
/// Given a subpurpose, returns a new IDataProtector that has unique cryptographic keys tied <em>both</em> the purpose
/// that was used to create this IDataProtector instance <em>and</em> the purpose that is provided as a parameter
/// to this method.
/// Cryptographically protects a piece of plaintext data.
/// </summary>
/// <param name="purpose">The sub-consumer of the IDataProtector.</param>
/// <returns>An IDataProtector.</returns>
IDataProtector CreateSubProtector(string purpose);
/// <summary>
/// Cryptographically protects some input data.
/// </summary>
/// <param name="unprotectedData">The data to be protected.</param>
/// <returns>An array containing cryptographically protected data.</returns>
/// <remarks>To retrieve the original data, call Unprotect on the protected data.</remarks>
/// <param name="unprotectedData">The plaintext data to protect.</param>
/// <returns>The protected form of the plaintext data.</returns>
byte[] Protect(byte[] unprotectedData);
/// <summary>
/// Retrieves the original data that was protected by a call to Protect.
/// Cryptographically unprotects a piece of protected data.
/// </summary>
/// <param name="protectedData">The protected data to be decrypted.</param>
/// <returns>The original data.</returns>
/// <remarks>Throws CryptographicException if the <em>protectedData</em> parameter has been tampered with.</remarks>
/// <param name="protectedData">The protected data to unprotect.</param>
/// <returns>The plaintext form of the protected data.</returns>
/// <remarks>
/// Implementations should throw CryptographicException if the protected data is
/// invalid or malformed.
/// </remarks>
byte[] Unprotect(byte[] protectedData);
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Represents a secret value.
/// </summary>
public interface ISecret : IDisposable
{
/// <summary>
/// The length (in bytes) of the value.
/// </summary>
int Length { get; }
/// <summary>
/// Writes the secret value to the specified buffer.
/// </summary>
/// <param name="buffer">The buffer which should receive the secret value.</param>
/// <remarks>
/// The buffer size must exactly match the length of the secret value.
/// </remarks>
void WriteSecretIntoBuffer(ArraySegment<byte> buffer);
}
}

View File

@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.Cng.PBKDF2;
using Microsoft.AspNet.Security.DataProtection.PBKDF2;
namespace Microsoft.AspNet.Security.DataProtection.Cng
namespace Microsoft.AspNet.Security.DataProtection
{
public static class KeyDerivation
{

View File

@ -3,7 +3,7 @@
using System;
namespace Microsoft.AspNet.Security.DataProtection.Cng
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Specifies the PRF which should be used for the key derivation algorithm.

View File

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
/// <summary>
/// The basic interface for representing an authenticated encryption key.
/// </summary>
public interface IKey
{
/// <summary>
/// The date at which encryptions with this key can begin taking place.
/// </summary>
DateTimeOffset ActivationDate { get; }
/// <summary>
/// The date on which this key was created.
/// </summary>
DateTimeOffset CreationDate { get; }
/// <summary>
/// The date after which encryptions with this key may no longer take place.
/// </summary>
/// <remarks>
/// An expired key may still be used to decrypt existing payloads.
/// </remarks>
DateTimeOffset ExpirationDate { get; }
/// <summary>
/// Returns a value stating whether this key was revoked.
/// </summary>
/// <remarks>
/// A revoked key may still be used to decrypt existing payloads, but the payloads
/// must be treated as potentially unauthentic unless the application has some
/// other assurance that the payloads are authentic.
/// </remarks>
bool IsRevoked { get; }
/// <summary>
/// The id of the key.
/// </summary>
Guid KeyId { get; }
/// <summary>
/// Creates an IAuthenticatedEncryptor instance that can be used to encrypt data
/// to and decrypt data from this key.
/// </summary>
/// <returns>An IAuthenticatedEncryptor.</returns>
IAuthenticatedEncryptor CreateEncryptorInstance();
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
/// <summary>
/// The basic interface for performing key management operations.
/// </summary>
public interface IKeyManager
{
/// <summary>
/// Creates a new key with the specified activation and expiration dates.
/// </summary>
/// <param name="activationDate">The date on which encryptions to this key may begin.</param>
/// <param name="expirationDate">The date after which encryptions to this key may no longer take place.</param>
/// <returns>The newly-created IKey instance.</returns>
/// <remarks>
/// This method also persists the newly-created IKey instance to the underlying repository.
/// </remarks>
IKey CreateNewKey(DateTimeOffset activationDate, DateTimeOffset expirationDate);
/// <summary>
/// Fetches all keys from the underlying repository.
/// </summary>
/// <returns>The collection of all keys.</returns>
IReadOnlyCollection<IKey> GetAllKeys();
/// <summary>
/// Revokes a specific key.
/// </summary>
/// <param name="keyId">The id of the key to revoke.</param>
/// <param name="reason">An optional human-readable reason for revocation.</param>
/// <remarks>
/// This method will not mutate existing IKey instances. After calling this method,
/// all existing IKey instances should be discarded, and GetAllKeys should be called again.
/// </remarks>
void RevokeKey(Guid keyId, string reason = null);
/// <summary>
/// Revokes all keys created before a specified date.
/// </summary>
/// <param name="revocationDate">The revocation date. All keys with a creation date before
/// this value will be revoked.</param>
/// <param name="reason">An optional human-readable reason for revocation.</param>
/// <remarks>
/// This method will not mutate existing IKey instances. After calling this method,
/// all existing IKey instances should be discarded, and GetAllKeys should be called again.
/// </remarks>
void RevokeAllKeys(DateTimeOffset revocationDate, string reason = null);
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal interface IKeyRing
{
IAuthenticatedEncryptor DefaultAuthenticatedEncryptor { get; }
Guid DefaultKeyId { get; }
IAuthenticatedEncryptor GetAuthenticatedEncryptorByKeyId(Guid keyId, out bool isRevoked);
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal interface IKeyRingProvider
{
IKeyRing GetCurrentKeyRing();
}
}

View File

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal sealed class Key : IKey
{
private readonly IAuthenticatedEncryptorConfiguration _encryptorConfiguration;
public Key(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate, IAuthenticatedEncryptorConfiguration encryptorConfiguration)
{
KeyId = keyId;
CreationDate = creationDate;
ActivationDate = activationDate;
ExpirationDate = expirationDate;
_encryptorConfiguration = encryptorConfiguration;
}
public DateTimeOffset ActivationDate
{
get;
private set;
}
public DateTimeOffset CreationDate
{
get;
private set;
}
public DateTimeOffset ExpirationDate
{
get;
private set;
}
public bool IsRevoked
{
get;
private set;
}
public Guid KeyId
{
get;
private set;
}
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return _encryptorConfiguration.CreateEncryptorInstance();
}
internal void SetRevoked()
{
IsRevoked = true;
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal static class KeyExtensions
{
public static bool IsExpired(this IKey key, DateTime utcNow)
{
return (key.ExpirationDate.UtcDateTime <= utcNow);
}
}
}

View File

@ -0,0 +1,97 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal sealed class KeyRing : IKeyRing
{
private readonly AuthenticatedEncryptorHolder _defaultEncryptorHolder;
private readonly Dictionary<Guid, AuthenticatedEncryptorHolder> _keyToEncryptorMap;
public KeyRing(Guid defaultKeyId, IKey[] keys)
{
DefaultKeyId = defaultKeyId;
_keyToEncryptorMap = CreateEncryptorMap(defaultKeyId, keys, out _defaultEncryptorHolder);
}
public KeyRing(Guid defaultKeyId, KeyRing other)
{
DefaultKeyId = defaultKeyId;
_keyToEncryptorMap = other._keyToEncryptorMap;
_defaultEncryptorHolder = _keyToEncryptorMap[defaultKeyId];
}
public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor
{
get
{
bool unused;
return _defaultEncryptorHolder.GetEncryptorInstance(out unused);
}
}
public Guid DefaultKeyId { get; private set; }
private static Dictionary<Guid, AuthenticatedEncryptorHolder> CreateEncryptorMap(Guid defaultKeyId, IKey[] keys, out AuthenticatedEncryptorHolder defaultEncryptorHolder)
{
defaultEncryptorHolder = null;
var encryptorMap = new Dictionary<Guid, AuthenticatedEncryptorHolder>(keys.Length);
foreach (var key in keys)
{
var holder = new AuthenticatedEncryptorHolder(key);
encryptorMap.Add(key.KeyId, holder);
if (key.KeyId == defaultKeyId)
{
defaultEncryptorHolder = holder;
}
}
return encryptorMap;
}
public IAuthenticatedEncryptor GetAuthenticatedEncryptorByKeyId(Guid keyId, out bool isRevoked)
{
isRevoked = false;
AuthenticatedEncryptorHolder holder;
_keyToEncryptorMap.TryGetValue(keyId, out holder);
return holder?.GetEncryptorInstance(out isRevoked);
}
private sealed class AuthenticatedEncryptorHolder
{
private readonly IKey _key;
private IAuthenticatedEncryptor _encryptor;
internal AuthenticatedEncryptorHolder(IKey key)
{
_key = key;
}
internal IAuthenticatedEncryptor GetEncryptorInstance(out bool isRevoked)
{
// simple double-check lock pattern
// we can't use LazyInitializer<T> because we don't have a simple value factory
IAuthenticatedEncryptor encryptor = Volatile.Read(ref _encryptor);
if (encryptor == null)
{
lock (this)
{
encryptor = Volatile.Read(ref _encryptor);
if (encryptor == null)
{
encryptor = _key.CreateEncryptorInstance();
Volatile.Write(ref _encryptor, encryptor);
}
}
}
isRevoked = _key.IsRevoked;
return encryptor;
}
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal unsafe sealed class KeyRingBasedDataProtectionProvider : IDataProtectionProvider
{
private readonly IKeyRingProvider _keyringProvider;
public KeyRingBasedDataProtectionProvider(IKeyRingProvider keyringProvider)
{
_keyringProvider = keyringProvider;
}
public IDataProtector CreateProtector([NotNull] string purpose)
{
return new KeyRingBasedDataProtector(_keyringProvider, new[] { purpose });
}
}
}

View File

@ -0,0 +1,302 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal unsafe sealed class KeyRingBasedDataProtector : IDataProtector
{
// This magic header identifies a v0 protected data blob.
// It's the high 28 bits of the SHA1 hash of "Microsoft.AspNet.Security.DataProtection.MultiplexingDataProtector" [US-ASCII].
// The last 4 bits are reserved for version information.
private const uint MAGIC_HEADER_V0 = 0xE123CF30;
private byte[] _additionalAuthenticatedDataTemplate;
private readonly IKeyRingProvider _keyringProvider;
private readonly string[] _purposes;
public KeyRingBasedDataProtector(IKeyRingProvider keyringProvider, string[] purposes)
{
_additionalAuthenticatedDataTemplate = GenerateAdditionalAuthenticatedDataTemplateFromPurposes(purposes);
_keyringProvider = keyringProvider;
_purposes = purposes;
}
private static byte[] ApplyEncryptorIdToAdditionalAuthenticatedDataTemplate(Guid encryptorId, byte[] additionalAuthenticatedDataTemplate)
{
CryptoUtil.Assert(additionalAuthenticatedDataTemplate.Length >= sizeof(uint) + sizeof(Guid), "additionalAuthenticatedDataTemplate.Length >= sizeof(uint) + sizeof(Guid)");
// Optimization: just return the original template if the GUID already matches.
fixed (byte* pbOriginal = additionalAuthenticatedDataTemplate)
{
if (Read32bitAlignedGuid(&pbOriginal[sizeof(uint)]) == encryptorId)
{
return additionalAuthenticatedDataTemplate;
}
}
// Clone the template since the input is immutable, then inject the encryptor ID into the new template
byte[] cloned = (byte[])additionalAuthenticatedDataTemplate.Clone();
fixed (byte* pbCloned = cloned)
{
Write32bitAlignedGuid(&pbCloned[sizeof(uint)], encryptorId);
}
return cloned;
}
public IDataProtector CreateProtector([NotNull] string purpose)
{
// Append the incoming purpose to the end of the original array to form a hierarchy
string[] newPurposes = new string[_purposes.Length + 1];
Array.Copy(_purposes, 0, newPurposes, 0, _purposes.Length);
newPurposes[newPurposes.Length - 1] = purpose;
// Use the same keyring as the current instance
return new KeyRingBasedDataProtector(_keyringProvider, newPurposes);
}
private static byte[] GenerateAdditionalAuthenticatedDataTemplateFromPurposes(string[] purposes)
{
const int MEMORYSTREAM_DEFAULT_CAPACITY = 0x100; // matches MemoryStream.EnsureCapacity
var ms = new MemoryStream(MEMORYSTREAM_DEFAULT_CAPACITY);
// additionalAuthenticatedData := { magicHeader || encryptor-GUID || purposeCount || (purpose)* }
// purpose := { utf8ByteCount || utf8Text }
using (var writer = new PurposeBinaryWriter(ms))
{
writer.WriteBigEndian(MAGIC_HEADER_V0);
Debug.Assert(ms.Position == sizeof(uint));
writer.Seek(sizeof(Guid), SeekOrigin.Current); // skip over where the encryptor GUID will be stored; we'll fill it in later
if (purposes != null)
{
writer.Write7BitEncodedInt(purposes.Length);
foreach (var purpose in purposes)
{
if (String.IsNullOrEmpty(purpose))
{
writer.Write7BitEncodedInt(0); // blank purpose
}
else
{
writer.Write(purpose);
}
}
}
else
{
writer.Write7BitEncodedInt(0); // empty purposes array
}
}
return ms.ToArray();
}
public byte[] Protect(byte[] unprotectedData)
{
// argument & state checking
if (unprotectedData == null)
{
throw new ArgumentNullException("unprotectedData");
}
// Perform the encryption operation using the current default encryptor.
var currentKeyRing = _keyringProvider.GetCurrentKeyRing();
var defaultKeyId = currentKeyRing.DefaultKeyId;
var defaultEncryptorInstance = currentKeyRing.DefaultAuthenticatedEncryptor;
CryptoUtil.Assert(defaultEncryptorInstance != null, "defaultEncryptorInstance != null");
// We'll need to apply the default encryptor ID to the template if it hasn't already been applied.
// If the default encryptor ID has been updated since the last call to Protect, also write back the updated template.
byte[] aadTemplate = Volatile.Read(ref _additionalAuthenticatedDataTemplate);
byte[] aadForInvocation = ApplyEncryptorIdToAdditionalAuthenticatedDataTemplate(defaultKeyId, aadTemplate);
if (aadTemplate != aadForInvocation)
{
Volatile.Write(ref _additionalAuthenticatedDataTemplate, aadForInvocation);
}
// We allocate a 20-byte pre-buffer so that we can inject the magic header and encryptor id into the return value.
byte[] retVal;
try
{
retVal = defaultEncryptorInstance.Encrypt(
plaintext: new ArraySegment<byte>(unprotectedData),
additionalAuthenticatedData: new ArraySegment<byte>(aadForInvocation),
preBufferSize: (uint)(sizeof(uint) + sizeof(Guid)),
postBufferSize: 0);
CryptoUtil.Assert(retVal != null && retVal.Length >= sizeof(uint) + sizeof(Guid), "retVal != null && retVal.Length >= sizeof(uint) + sizeof(Guid)");
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// homogenize all errors to CryptographicException
throw Error.Common_EncryptionFailed(ex);
}
// At this point: retVal := { 000..000 || encryptorSpecificProtectedPayload },
// where 000..000 is a placeholder for our magic header and encryptor ID.
// Write out the magic header and encryptor ID
fixed (byte* pbRetVal = retVal)
{
WriteBigEndianInteger(pbRetVal, MAGIC_HEADER_V0);
Write32bitAlignedGuid(&pbRetVal[sizeof(uint)], defaultKeyId);
}
// At this point, retVal := { magicHeader || encryptor-GUID || encryptorSpecificProtectedPayload }
// And we're done!
return retVal;
}
// Helper function to read a GUID from a 32-bit alignment; useful on ARM where unaligned reads
// can result in weird behaviors at runtime.
private static Guid Read32bitAlignedGuid(void* ptr)
{
Debug.Assert((long)ptr % 4 == 0);
Guid retVal;
((int*)&retVal)[0] = ((int*)ptr)[0];
((int*)&retVal)[1] = ((int*)ptr)[1];
((int*)&retVal)[2] = ((int*)ptr)[2];
((int*)&retVal)[3] = ((int*)ptr)[3];
return retVal;
}
private static uint ReadBigEndian32BitInteger(byte* ptr)
{
return ((uint)ptr[0] << 24)
| ((uint)ptr[1] << 16)
| ((uint)ptr[2] << 8)
| ((uint)ptr[3]);
}
private static bool TryGetVersionFromMagicHeader(uint magicHeader, out int version)
{
const uint MAGIC_HEADER_VERSION_MASK = 0xFU;
if ((magicHeader & ~MAGIC_HEADER_VERSION_MASK) == MAGIC_HEADER_V0)
{
version = (int)(magicHeader & MAGIC_HEADER_VERSION_MASK);
return true;
}
else
{
version = default(int);
return false;
}
}
public byte[] Unprotect(byte[] protectedData)
{
// argument & state checking
if (protectedData == null)
{
throw new ArgumentNullException("protectedData");
}
if (protectedData.Length < sizeof(uint) /* magic header */ + sizeof(Guid) /* key id */)
{
throw Error.Common_NotAValidProtectedPayload();
}
// Need to check that protectedData := { magicHeader || encryptor-GUID || encryptorSpecificProtectedPayload }
// Parse the payload version number and encryptor ID.
uint payloadMagicHeader;
Guid payloadEncryptorId;
fixed (byte* pbInput = protectedData)
{
payloadMagicHeader = ReadBigEndian32BitInteger(pbInput);
payloadEncryptorId = Read32bitAlignedGuid(&pbInput[sizeof(uint)]);
}
// Are the magic header and version information correct?
int payloadVersion;
if (!TryGetVersionFromMagicHeader(payloadMagicHeader, out payloadVersion))
{
throw Error.Common_NotAValidProtectedPayload();
}
else if (payloadVersion != 0)
{
throw Error.Common_PayloadProducedByNewerVersion();
}
// Find the correct encryptor in the keyring.
bool keyWasRevoked;
var requestedEncryptor = _keyringProvider.GetCurrentKeyRing().GetAuthenticatedEncryptorByKeyId(payloadEncryptorId, out keyWasRevoked);
if (requestedEncryptor == null)
{
throw Error.Common_KeyNotFound(payloadEncryptorId);
}
if (keyWasRevoked)
{
throw Error.Common_KeyRevoked(payloadEncryptorId);
}
// Perform the decryption operation.
ArraySegment<byte> ciphertext = new ArraySegment<byte>(protectedData, sizeof(uint) + sizeof(Guid), protectedData.Length - (sizeof(uint) + sizeof(Guid))); // chop off magic header + encryptor id
ArraySegment<byte> additionalAuthenticatedData = new ArraySegment<byte>(ApplyEncryptorIdToAdditionalAuthenticatedDataTemplate(payloadEncryptorId, Volatile.Read(ref _additionalAuthenticatedDataTemplate)));
try
{
// At this point, cipherText := { encryptorSpecificPayload },
// so all that's left is to invoke the decryption routine directly.
byte[] retVal = requestedEncryptor.Decrypt(ciphertext, additionalAuthenticatedData);
CryptoUtil.Assert(retVal != null, "retVal != null");
return retVal;
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// homogenize all failures to CryptographicException
throw Error.DecryptionFailed(ex);
}
}
// Helper function to write a GUID to a 32-bit alignment; useful on ARM where unaligned reads
// can result in weird behaviors at runtime.
private static void Write32bitAlignedGuid(void* ptr, Guid value)
{
Debug.Assert((long)ptr % 4 == 0);
((int*)ptr)[0] = ((int*)&value)[0];
((int*)ptr)[1] = ((int*)&value)[1];
((int*)ptr)[2] = ((int*)&value)[2];
((int*)ptr)[3] = ((int*)&value)[3];
}
private static void WriteBigEndianInteger(byte* ptr, uint value)
{
ptr[0] = (byte)(value >> 24);
ptr[1] = (byte)(value >> 16);
ptr[2] = (byte)(value >> 8);
ptr[3] = (byte)(value);
}
private sealed class PurposeBinaryWriter : BinaryWriter
{
// Strings should never contain invalid UTF16 chars, so we'll use a secure encoding.
private static readonly UTF8Encoding _secureEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private static readonly byte[] _guidBuffer = new byte[sizeof(Guid)];
public PurposeBinaryWriter(MemoryStream stream) : base(stream, _secureEncoding, leaveOpen: true) { }
public new void Write7BitEncodedInt(int value)
{
base.Write7BitEncodedInt(value);
}
// Writes a big-endian 32-bit integer to the underlying stream.
public void WriteBigEndian(uint value)
{
var outStream = BaseStream; // property accessor also performs a flush
outStream.WriteByte((byte)(value >> 24));
outStream.WriteByte((byte)(value >> 16));
outStream.WriteByte((byte)(value >> 8));
outStream.WriteByte((byte)(value));
}
}
}
}

View File

@ -0,0 +1,205 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
internal sealed class KeyRingProvider : IKeyRingProvider
{
// TODO: Should the below be 3 months?
private static readonly TimeSpan KEY_DEFAULT_LIFETIME = TimeSpan.FromDays(30 * 6); // how long should keys be active once created?
private static readonly TimeSpan KEYRING_REFRESH_PERIOD = TimeSpan.FromDays(1); // how often should we check for updates to the repository?
private static readonly TimeSpan KEY_EXPIRATION_BUFFER = TimeSpan.FromDays(7); // how close to key expiration should we generate a new key?
private static readonly TimeSpan MAX_SERVER_TO_SERVER_CLOCK_SKEW = TimeSpan.FromMinutes(10); // max skew we expect to see between servers using the key ring
private CachedKeyRing _cachedKeyRing;
private readonly object _cachedKeyRingLockObj = new object();
private readonly IKeyManager _keyManager;
public KeyRingProvider(IKeyManager keyManager)
{
_keyManager = keyManager;
}
private CachedKeyRing CreateCachedKeyRingInstanceUnderLock(DateTime utcNow, CachedKeyRing existingCachedKeyRing)
{
bool shouldCreateNewKeyWithDeferredActivation; // flag stating whether the default key will soon expire and doesn't have a suitable replacement
// Must we discard the cached keyring and refresh directly from the manager?
if (existingCachedKeyRing != null && existingCachedKeyRing.HardRefreshTimeUtc <= utcNow)
{
existingCachedKeyRing = null;
}
// Try to locate the current default key, using the cached keyring if we can.
IKey defaultKey;
if (existingCachedKeyRing != null)
{
defaultKey = FindDefaultKey(utcNow, existingCachedKeyRing.Keys, out shouldCreateNewKeyWithDeferredActivation);
if (defaultKey != null && !shouldCreateNewKeyWithDeferredActivation)
{
return new CachedKeyRing
{
KeyRing = new KeyRing(defaultKey.KeyId, existingCachedKeyRing.KeyRing), // this overload allows us to use existing IAuthenticatedEncryptor instances
Keys = existingCachedKeyRing.Keys,
HardRefreshTimeUtc = existingCachedKeyRing.HardRefreshTimeUtc,
SoftRefreshTimeUtc = MinDateTime(existingCachedKeyRing.HardRefreshTimeUtc, utcNow + KEYRING_REFRESH_PERIOD)
};
}
}
// That didn't work, so refresh from the underlying key manager.
var allKeys = _keyManager.GetAllKeys().ToArray();
defaultKey = FindDefaultKey(utcNow, allKeys, out shouldCreateNewKeyWithDeferredActivation);
if (defaultKey != null && shouldCreateNewKeyWithDeferredActivation)
{
// If we need to create a new key with deferred activation, do so now.
_keyManager.CreateNewKey(activationDate: defaultKey.ExpirationDate, expirationDate: utcNow + KEY_DEFAULT_LIFETIME);
allKeys = _keyManager.GetAllKeys().ToArray();
defaultKey = FindDefaultKey(utcNow, allKeys);
}
else if (defaultKey == null)
{
// If there's no default key, create one now with immediate activation.
_keyManager.CreateNewKey(utcNow, utcNow + KEY_DEFAULT_LIFETIME);
allKeys = _keyManager.GetAllKeys().ToArray();
defaultKey = FindDefaultKey(utcNow, allKeys);
}
// We really should have a default key at this point.
CryptoUtil.Assert(defaultKey != null, "defaultKey != null");
var cachedKeyRingHardRefreshTime = GetNextHardRefreshTime(utcNow);
return new CachedKeyRing
{
KeyRing = new KeyRing(defaultKey.KeyId, allKeys),
Keys = allKeys,
HardRefreshTimeUtc = cachedKeyRingHardRefreshTime,
SoftRefreshTimeUtc = MinDateTime(defaultKey.ExpirationDate.UtcDateTime, cachedKeyRingHardRefreshTime)
};
}
private static IKey FindDefaultKey(DateTime utcNow, IKey[] allKeys)
{
bool unused;
return FindDefaultKey(utcNow, allKeys, out unused);
}
private static IKey FindDefaultKey(DateTime utcNow, IKey[] allKeys, out bool callerShouldGenerateNewKey)
{
callerShouldGenerateNewKey = false;
// Find the keys with the nearest past and future activation dates.
IKey keyWithNearestPastActivationDate = null;
IKey keyWithNearestFutureActivationDate = null;
foreach (var candidateKey in allKeys)
{
// Revoked keys are never eligible candidates to be the default key.
if (candidateKey.IsRevoked)
{
continue;
}
if (candidateKey.ActivationDate.UtcDateTime <= utcNow)
{
if (keyWithNearestPastActivationDate == null || keyWithNearestPastActivationDate.ActivationDate < candidateKey.ActivationDate)
{
keyWithNearestPastActivationDate = candidateKey;
}
}
else
{
if (keyWithNearestFutureActivationDate == null || keyWithNearestFutureActivationDate.ActivationDate > candidateKey.ActivationDate)
{
keyWithNearestFutureActivationDate = candidateKey;
}
}
}
// If the most recently activated key hasn't yet expired, use it as the default key.
if (keyWithNearestPastActivationDate != null && !keyWithNearestPastActivationDate.IsExpired(utcNow))
{
// Additionally, if it's about to expire and there will be a gap in the keyring during which there
// is no valid default encryption key, the caller should generate a new key with deferred activation.
if (keyWithNearestPastActivationDate.ExpirationDate.UtcDateTime - utcNow <= KEY_EXPIRATION_BUFFER)
{
if (keyWithNearestFutureActivationDate == null || keyWithNearestFutureActivationDate.ActivationDate > keyWithNearestPastActivationDate.ExpirationDate)
{
callerShouldGenerateNewKey = true;
}
}
return keyWithNearestPastActivationDate;
}
// Failing that, is any key due for imminent activation? If so, use it as the default key.
// This allows us to account for clock skew when multiple servers touch the repository.
if (keyWithNearestFutureActivationDate != null
&& (keyWithNearestFutureActivationDate.ActivationDate.UtcDateTime - utcNow) < MAX_SERVER_TO_SERVER_CLOCK_SKEW
&& !keyWithNearestFutureActivationDate.IsExpired(utcNow) /* sanity check: expiration can't occur before activation */)
{
return keyWithNearestFutureActivationDate;
}
// Otherwise, there's no default key.
return null;
}
public IKeyRing GetCurrentKeyRing()
{
DateTime utcNow = DateTime.UtcNow;
// Can we return the cached keyring to the caller?
var existingCachedKeyRing = Volatile.Read(ref _cachedKeyRing);
if (existingCachedKeyRing != null && existingCachedKeyRing.SoftRefreshTimeUtc > utcNow)
{
return existingCachedKeyRing.KeyRing;
}
// The cached keyring hasn't been created or must be refreshed.
lock (_cachedKeyRingLockObj)
{
// Did somebody update the keyring while we were waiting for the lock?
existingCachedKeyRing = Volatile.Read(ref _cachedKeyRing);
if (existingCachedKeyRing != null && existingCachedKeyRing.SoftRefreshTimeUtc > utcNow)
{
return existingCachedKeyRing.KeyRing;
}
// It's up to us to refresh the cached keyring.
var newCachedKeyRing = CreateCachedKeyRingInstanceUnderLock(utcNow, existingCachedKeyRing);
Volatile.Write(ref _cachedKeyRing, newCachedKeyRing);
return newCachedKeyRing.KeyRing;
}
}
private static DateTime GetNextHardRefreshTime(DateTime utcNow)
{
// We'll fudge the refresh period up to 20% so that multiple applications don't try to
// hit a single repository simultaneously. For instance, if the refresh period is 1 hour,
// we'll calculate the new refresh time as somewhere between 48 - 60 minutes from now.
var skewedRefreshPeriod = TimeSpan.FromTicks((long)(KEYRING_REFRESH_PERIOD.Ticks * ((new Random().NextDouble() / 5) + 0.8d)));
return utcNow + skewedRefreshPeriod;
}
private static DateTime MinDateTime(DateTime a, DateTime b)
{
Debug.Assert(a.Kind == DateTimeKind.Utc);
Debug.Assert(b.Kind == DateTimeKind.Utc);
return (a < b) ? a : b;
}
private sealed class CachedKeyRing
{
internal DateTime HardRefreshTimeUtc;
internal KeyRing KeyRing;
internal IKey[] Keys;
internal DateTime SoftRefreshTimeUtc;
}
}
}

View File

@ -0,0 +1,256 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.Security.DataProtection.Repositories;
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
{
public sealed class XmlKeyManager : IKeyManager
{
private const string KEY_MANAGEMENT_XML_NAMESPACE_STRING = "http://www.asp.net/dataProtection/2014";
internal static readonly XNamespace KeyManagementXmlNamespace = XNamespace.Get(KEY_MANAGEMENT_XML_NAMESPACE_STRING);
internal static readonly XName ActivationDateElementName = KeyManagementXmlNamespace.GetName("activationDate");
internal static readonly XName AuthenticatedEncryptorElementName = KeyManagementXmlNamespace.GetName("authenticatedEncryptor");
internal static readonly XName CreationDateElementName = KeyManagementXmlNamespace.GetName("creationDate");
internal static readonly XName ExpirationDateElementName = KeyManagementXmlNamespace.GetName("expirationDate");
internal static readonly XName IdAttributeName = XNamespace.None.GetName("id");
internal static readonly XName KeyElementName = KeyManagementXmlNamespace.GetName("key");
internal static readonly XName ReaderAttributeName = XNamespace.None.GetName("reader");
internal static readonly XName ReasonElementName = KeyManagementXmlNamespace.GetName("reason");
internal static readonly XName RevocationDateElementName = KeyManagementXmlNamespace.GetName("revocationDate");
internal static readonly XName RevocationElementName = KeyManagementXmlNamespace.GetName("revocation");
internal static readonly XName VersionAttributeName = XNamespace.None.GetName("version");
private readonly IAuthenticatedEncryptorConfigurationFactory _authenticatedEncryptorConfigurationFactory;
private readonly IServiceProvider _serviceProvider;
private readonly ITypeActivator _typeActivator;
private readonly IXmlRepository _xmlRepository;
private readonly IXmlEncryptor _xmlEncryptor;
public XmlKeyManager(
[NotNull] IServiceProvider serviceProvider,
[NotNull] IAuthenticatedEncryptorConfigurationFactory authenticatedEncryptorConfigurationFactory,
[NotNull] ITypeActivator typeActivator,
[NotNull] IXmlRepository xmlRepository,
[NotNull] IXmlEncryptor xmlEncryptor)
{
_serviceProvider = serviceProvider;
_authenticatedEncryptorConfigurationFactory = authenticatedEncryptorConfigurationFactory;
_typeActivator = typeActivator;
_xmlRepository = xmlRepository;
_xmlEncryptor = xmlEncryptor;
}
public IKey CreateNewKey(DateTimeOffset activationDate, DateTimeOffset expirationDate)
{
return CreateNewKey(Guid.NewGuid(), DateTimeOffset.UtcNow, activationDate, expirationDate);
}
private IKey CreateNewKey(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate)
{
// <key id="{GUID}" version="1" xmlns="{XMLNS}">
// <creationDate>...</creationDate>
// <activationDate>...</activationDate>
// <expirationDate>...</expirationDate>
// <authenticatedEncryptor>
// <... parser="{TYPE}" />
// </authenticatedEncryptor>
// </xxx:key>
// Create the <xxx:authenticatedEncryptor /> element and make sure it's well-formed.
var encryptorConfiguration = _authenticatedEncryptorConfigurationFactory.CreateNewConfiguration();
var encryptorElementAsXml = encryptorConfiguration.ToXml(_xmlEncryptor);
CryptoUtil.Assert(!String.IsNullOrEmpty((string)encryptorElementAsXml.Attribute(ReaderAttributeName)), "!String.IsNullOrEmpty((string)encryptorElementAsXml.Attribute(ParserAttributeName))");
// Create the <xxx:key /> element.
var keyElement = new XElement(KeyElementName,
new XAttribute(IdAttributeName, keyId),
new XAttribute(VersionAttributeName, 1),
new XElement(CreationDateElementName, creationDate),
new XElement(ActivationDateElementName, activationDate),
new XElement(ExpirationDateElementName, expirationDate),
new XElement(AuthenticatedEncryptorElementName,
encryptorElementAsXml));
// Persist it to the underlying repository
string friendlyName = String.Format(CultureInfo.InvariantCulture, "key-{0:D}", keyId);
_xmlRepository.StoreElement(keyElement, friendlyName);
// And we're done!
return new Key(
keyId: keyId,
creationDate: creationDate,
activationDate: activationDate,
expirationDate: expirationDate,
encryptorConfiguration: encryptorConfiguration);
}
public IReadOnlyCollection<IKey> GetAllKeys()
{
var allElements = _xmlRepository.GetAllElements();
Dictionary<Guid, Key> idToKeyMap = new Dictionary<Guid, Key>();
HashSet<Guid> revokedKeyIds = null;
DateTimeOffset? mostRecentMassRevocationDate = null;
foreach (var element in allElements)
{
if (element.Name == KeyElementName)
{
var thisKey = ParseKeyElement(element);
if (idToKeyMap.ContainsKey(thisKey.KeyId))
{
CryptoUtil.Fail("TODO: Duplicate key.");
}
idToKeyMap.Add(thisKey.KeyId, thisKey);
}
else if (element.Name == RevocationElementName)
{
object revocationInfo = ParseRevocationElement(element);
DateTimeOffset? revocationInfoAsDate = revocationInfo as DateTimeOffset?;
if (revocationInfoAsDate != null)
{
// We're revoking all keys created on or after a specific date.
if (!mostRecentMassRevocationDate.HasValue || mostRecentMassRevocationDate < revocationInfoAsDate)
{
// This new value is the most recent mass revocation date.
mostRecentMassRevocationDate = revocationInfoAsDate;
}
}
else
{
// We're revoking only a specific key
if (revokedKeyIds == null)
{
revokedKeyIds = new HashSet<Guid>();
}
revokedKeyIds.Add((Guid)revocationInfo);
}
}
else
{
CryptoUtil.Fail("TODO: Unknown element.");
}
}
// Now process all revocations
if (revokedKeyIds != null || mostRecentMassRevocationDate.HasValue)
{
foreach (Key key in idToKeyMap.Values)
{
if ((revokedKeyIds != null && revokedKeyIds.Contains(key.KeyId))
|| (mostRecentMassRevocationDate.HasValue && mostRecentMassRevocationDate >= key.CreationDate))
{
key.SetRevoked();
}
}
}
// And we're done!
return idToKeyMap.Values.ToArray();
}
private Key ParseKeyElement(XElement keyElement)
{
Debug.Assert(keyElement.Name == KeyElementName);
int version = (int)keyElement.Attribute(VersionAttributeName);
CryptoUtil.Assert(version == 1, "TODO: version == 1");
XElement encryptorConfigurationAsXml = keyElement.Element(AuthenticatedEncryptorElementName).Elements().Single();
string encryptorConfigurationParserTypeName = (string)encryptorConfigurationAsXml.Attribute(ReaderAttributeName);
Type encryptorConfigurationParserType = Type.GetType(encryptorConfigurationParserTypeName, throwOnError: true);
CryptoUtil.Assert(typeof(IAuthenticatedEncryptorConfigurationXmlReader).IsAssignableFrom(encryptorConfigurationParserType),
"TODO: typeof(IAuthenticatedEncryptorConfigurationXmlReader).IsAssignableFrom(encryptorConfigurationParserType)");
var parser = (IAuthenticatedEncryptorConfigurationXmlReader)_typeActivator.CreateInstance(_serviceProvider, encryptorConfigurationParserType);
var encryptorConfiguration = parser.FromXml(encryptorConfigurationAsXml);
Guid keyId = (Guid)keyElement.Attribute(IdAttributeName);
DateTimeOffset creationDate = (DateTimeOffset)keyElement.Element(CreationDateElementName);
DateTimeOffset activationDate = (DateTimeOffset)keyElement.Element(ActivationDateElementName);
DateTimeOffset expirationDate = (DateTimeOffset)keyElement.Element(ExpirationDateElementName);
return new Key(
keyId: keyId,
creationDate: creationDate,
activationDate: activationDate,
expirationDate: expirationDate,
encryptorConfiguration: encryptorConfiguration);
}
// returns a Guid (for specific keys) or a DateTimeOffset (for all keys created on or before a specific date)
private object ParseRevocationElement(XElement revocationElement)
{
Debug.Assert(revocationElement.Name == RevocationElementName);
string keyIdAsString = revocationElement.Element(KeyElementName).Attribute(IdAttributeName).Value;
if (keyIdAsString == "*")
{
// all keys
return (DateTimeOffset)revocationElement.Element(RevocationDateElementName);
}
else
{
// only one key
return new Guid(keyIdAsString);
}
}
public void RevokeAllKeys(DateTimeOffset revocationDate, string reason = null)
{
// <revocation version="1" xmlns="{XMLNS}">
// <revocationDate>...</revocationDate>
// <key id="*" />
// <reason>...</reason>
// </revocation>
var revocationElement = new XElement(RevocationElementName,
new XAttribute(VersionAttributeName, 1),
new XElement(RevocationDateElementName, revocationDate),
new XElement(KeyElementName,
new XAttribute(IdAttributeName, "*")),
new XElement(ReasonElementName, reason));
// Persist it to the underlying repository
string friendlyName = String.Format(CultureInfo.InvariantCulture, "revocation-{0:X16}", (ulong)revocationDate.UtcTicks);
_xmlRepository.StoreElement(revocationElement, friendlyName);
}
public void RevokeKey(Guid keyId, string reason = null)
{
RevokeSingleKey(keyId, DateTimeOffset.UtcNow, reason);
}
private void RevokeSingleKey(Guid keyId, DateTimeOffset utcNow, string reason)
{
// <revocation version="1" xmlns="{XMLNS}">
// <revocationDate>...</revocationDate>
// <key id="{GUID}" />
// <reason>...</reason>
// </revocation>
var revocationElement = new XElement(RevocationElementName,
new XAttribute(VersionAttributeName, 1),
new XElement(RevocationDateElementName, utcNow),
new XElement(KeyElementName,
new XAttribute(IdAttributeName, keyId)),
new XElement(ReasonElementName, reason));
// Persist it to the underlying repository
string friendlyName = String.Format(CultureInfo.InvariantCulture, "revocation-{0:D}", keyId);
_xmlRepository.StoreElement(revocationElement, friendlyName);
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Managed
{
internal static class HashAlgorithmExtensions
{
public static int GetDigestSizeInBytes(this HashAlgorithm hashAlgorithm)
{
var hashSizeInBits = hashAlgorithm.HashSize;
CryptoUtil.Assert(hashSizeInBits >= 0 && hashSizeInBits % 8 == 0, "hashSizeInBits >= 0 && hashSizeInBits % 8 == 0");
return hashSizeInBits / 8;
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection.Managed
{
internal interface IManagedGenRandom
{
byte[] GenRandom(int numBytes);
}
}

View File

@ -0,0 +1,400 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.Security.DataProtection.SP800_108;
namespace Microsoft.AspNet.Security.DataProtection.Managed
{
// An encryptor which does Encrypt(CBC) + HMAC using SymmetricAlgorithm and HashAlgorithm.
// The payloads produced by this encryptor should be compatible with the payloads
// produced by the CNG-based Encrypt(CBC) + HMAC authenticated encryptor.
internal unsafe sealed class ManagedAuthenticatedEncryptor : IAuthenticatedEncryptor, IDisposable
{
// Even when IVs are chosen randomly, CBC is susceptible to IV collisions within a single
// key. For a 64-bit block cipher (like 3DES), we'd expect a collision after 2^32 block
// encryption operations, which a high-traffic web server might perform in mere hours.
// AES and other 128-bit block ciphers are less susceptible to this due to the larger IV
// space, but unfortunately some organizations require older 64-bit block ciphers. To address
// the collision issue, we'll feed 128 bits of entropy to the KDF when performing subkey
// generation. This creates >= 192 bits total entropy for each operation, so we shouldn't
// expect a collision until >= 2^96 operations. Even 2^80 operations still maintains a <= 2^-32
// probability of collision, and this is acceptable for the expected KDK lifetime.
private const int KEY_MODIFIER_SIZE_IN_BYTES = 128 / 8;
// Our analysis re: IV collision resistance only holds if we're working with block ciphers
// with a block length of 64 bits or greater.
internal const int SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES = 64 / 8;
// Min security bar: authentication tag must have at least 128 bits of output.
internal const int HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES = 128 / 8;
private static readonly Func<byte[], HashAlgorithm> _kdkPrfFactory = key => new HMACSHA512(key); // currently hardcoded to SHA512
private readonly byte[] _contextHeader;
private readonly IManagedGenRandom _genRandom;
private readonly ProtectedMemoryBlob _keyDerivationKey;
private readonly Func<SymmetricAlgorithm> _symmetricAlgorithmFactory;
private readonly int _symmetricAlgorithmBlockSizeInBytes;
private readonly int _symmetricAlgorithmSubkeyLengthInBytes;
private readonly int _validationAlgorithmDigestLengthInBytes;
private readonly int _validationAlgorithmSubkeyLengthInBytes;
private readonly Func<KeyedHashAlgorithm> _validationAlgorithmFactory;
public ManagedAuthenticatedEncryptor(ProtectedMemoryBlob keyDerivationKey, Func<SymmetricAlgorithm> symmetricAlgorithmFactory, int symmetricAlgorithmKeySizeInBytes, Func<KeyedHashAlgorithm> validationAlgorithmFactory, IManagedGenRandom genRandom = null)
{
CryptoUtil.Assert(KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES,
"KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES");
_genRandom = _genRandom ?? ManagedGenRandomImpl.Instance;
_keyDerivationKey = keyDerivationKey;
// Validate that the symmetric algorithm has the properties we require
using (var symmetricAlgorithm = symmetricAlgorithmFactory())
{
_symmetricAlgorithmFactory = symmetricAlgorithmFactory;
_symmetricAlgorithmBlockSizeInBytes = symmetricAlgorithm.GetBlockSizeInBytes();
_symmetricAlgorithmSubkeyLengthInBytes = symmetricAlgorithmKeySizeInBytes;
}
// Validate that the MAC algorithm has the properties we require
using (var validationAlgorithm = validationAlgorithmFactory())
{
_validationAlgorithmFactory = validationAlgorithmFactory;
_validationAlgorithmDigestLengthInBytes = validationAlgorithm.GetDigestSizeInBytes();
_validationAlgorithmSubkeyLengthInBytes = _validationAlgorithmDigestLengthInBytes; // for simplicity we'll generate MAC subkeys with a length equal to the digest length
}
CryptoUtil.Assert(SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES <= _symmetricAlgorithmBlockSizeInBytes && _symmetricAlgorithmBlockSizeInBytes <= Constants.MAX_STACKALLOC_BYTES,
"SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES <= _symmetricAlgorithmBlockSizeInBytes && _symmetricAlgorithmBlockSizeInBytes <= Constants.MAX_STACKALLOC_BYTES");
CryptoUtil.Assert(HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES <= _validationAlgorithmDigestLengthInBytes,
"HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES <= _validationAlgorithmDigestLengthInBytes");
CryptoUtil.Assert(KEY_MODIFIER_SIZE_IN_BYTES <= _validationAlgorithmSubkeyLengthInBytes && _validationAlgorithmSubkeyLengthInBytes <= Constants.MAX_STACKALLOC_BYTES,
"KEY_MODIFIER_SIZE_IN_BYTES <= _validationAlgorithmSubkeyLengthInBytes && _validationAlgorithmSubkeyLengthInBytes <= Constants.MAX_STACKALLOC_BYTES");
_contextHeader = CreateContextHeader();
}
private byte[] CreateContextHeader()
{
var EMPTY_ARRAY = new byte[0];
var EMPTY_ARRAY_SEGMENT = new ArraySegment<byte>(EMPTY_ARRAY);
byte[] retVal = new byte[checked(
1 /* KDF alg */
+ 1 /* chaining mode */
+ sizeof(uint) /* sym alg key size */
+ sizeof(uint) /* sym alg block size */
+ sizeof(uint) /* hmac alg key size */
+ sizeof(uint) /* hmac alg digest size */
+ _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */
+ _validationAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)];
int idx = 0;
// First is the two-byte header
retVal[idx++] = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
retVal[idx++] = 0; // 0x00 = CBC encryption + HMAC authentication
// Next is information about the symmetric algorithm (key size followed by block size)
BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmSubkeyLengthInBytes);
BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmBlockSizeInBytes);
// Next is information about the keyed hash algorithm (key size followed by digest size)
BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmSubkeyLengthInBytes);
BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmDigestLengthInBytes);
// See the design document for an explanation of the following code.
byte[] tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _validationAlgorithmSubkeyLengthInBytes];
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
kdk: EMPTY_ARRAY,
label: EMPTY_ARRAY_SEGMENT,
context: EMPTY_ARRAY_SEGMENT,
prfFactory: _kdkPrfFactory,
output: new ArraySegment<byte>(tempKeys));
// At this point, tempKeys := { K_E || K_H }.
// Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer.
using (var symmetricAlg = CreateSymmetricAlgorithm())
{
using (var cryptoTransform = symmetricAlg.CreateEncryptor(
rgbKey: new ArraySegment<byte>(tempKeys, 0, _symmetricAlgorithmSubkeyLengthInBytes).AsStandaloneArray(),
rgbIV: new byte[_symmetricAlgorithmBlockSizeInBytes]))
{
byte[] ciphertext = cryptoTransform.TransformFinalBlock(EMPTY_ARRAY, 0, 0);
CryptoUtil.Assert(ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes, "ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes");
Buffer.BlockCopy(ciphertext, 0, retVal, idx, ciphertext.Length);
}
}
idx += _symmetricAlgorithmBlockSizeInBytes;
// MAC a zero-length input string and copy the digest to the return buffer.
using (var hashAlg = CreateValidationAlgorithm(new ArraySegment<byte>(tempKeys, _symmetricAlgorithmSubkeyLengthInBytes, _validationAlgorithmSubkeyLengthInBytes).AsStandaloneArray()))
{
byte[] digest = hashAlg.ComputeHash(EMPTY_ARRAY);
CryptoUtil.Assert(digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes, "digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes");
Buffer.BlockCopy(digest, 0, retVal, idx, digest.Length);
}
idx += _validationAlgorithmDigestLengthInBytes;
CryptoUtil.Assert(idx == retVal.Length, "idx == retVal.Length");
// retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || macAlgKeySize || macAlgDigestSize || E("") || MAC("") }.
return retVal;
}
private SymmetricAlgorithm CreateSymmetricAlgorithm()
{
var retVal = _symmetricAlgorithmFactory();
CryptoUtil.Assert(retVal != null, "retVal != null");
retVal.Mode = CipherMode.CBC;
retVal.Padding = PaddingMode.PKCS7;
return retVal;
}
private KeyedHashAlgorithm CreateValidationAlgorithm(byte[] key)
{
var retVal = _validationAlgorithmFactory();
CryptoUtil.Assert(retVal != null, "retVal != null");
retVal.Key = key;
return retVal;
}
public byte[] Decrypt(ArraySegment<byte> protectedPayload, ArraySegment<byte> additionalAuthenticatedData)
{
protectedPayload.Validate();
additionalAuthenticatedData.Validate();
// Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
if (protectedPayload.Count < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes))
{
throw Error.CryptCommon_PayloadInvalid();
}
// Assumption: protectedPayload := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
try
{
// Step 1: Extract the key modifier and IV from the payload.
int keyModifierOffset; // position in protectedPayload.Array where key modifier begins
int ivOffset; // position in protectedPayload.Array where key modifier ends / IV begins
int ciphertextOffset; // position in protectedPayload.Array where IV ends / ciphertext begins
int macOffset; // position in protectedPayload.Array where ciphertext ends / MAC begins
int eofOffset; // position in protectedPayload.Array where MAC ends
checked
{
keyModifierOffset = protectedPayload.Offset;
ivOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
ciphertextOffset = ivOffset + _symmetricAlgorithmBlockSizeInBytes;
}
ArraySegment<byte> keyModifier = new ArraySegment<byte>(protectedPayload.Array, keyModifierOffset, ivOffset - keyModifierOffset);
byte[] iv = new byte[_symmetricAlgorithmBlockSizeInBytes];
Buffer.BlockCopy(protectedPayload.Array, ivOffset, iv, 0, iv.Length);
// Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys.
// We pin all unencrypted keys to limit their exposure via GC relocation.
byte[] decryptedKdk = new byte[_keyDerivationKey.Length];
byte[] decryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
byte[] validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes];
byte[] derivedKeysBuffer = new byte[checked(decryptionSubkey.Length + validationSubkey.Length)];
fixed (byte* __unused__1 = decryptedKdk)
fixed (byte* __unused__2 = decryptionSubkey)
fixed (byte* __unused__3 = validationSubkey)
fixed (byte* __unused__4 = derivedKeysBuffer)
{
try
{
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
DeriveKeysWithContextHeader(
kdk: decryptedKdk,
label: additionalAuthenticatedData,
contextHeader: _contextHeader,
context: keyModifier,
prfFactory: _kdkPrfFactory,
output: new ArraySegment<byte>(derivedKeysBuffer));
Buffer.BlockCopy(derivedKeysBuffer, 0, decryptionSubkey, 0, decryptionSubkey.Length);
Buffer.BlockCopy(derivedKeysBuffer, decryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length);
// Step 3: Calculate the correct MAC for this payload.
// correctHash := MAC(IV || ciphertext)
byte[] correctHash;
using (var hashAlgorithm = CreateValidationAlgorithm(validationSubkey))
{
checked
{
eofOffset = protectedPayload.Offset + protectedPayload.Count;
macOffset = eofOffset - _validationAlgorithmDigestLengthInBytes;
}
correctHash = hashAlgorithm.ComputeHash(protectedPayload.Array, ivOffset, macOffset - ivOffset);
}
// Step 4: Validate the MAC provided as part of the payload.
if (!CryptoUtil.TimeConstantBuffersAreEqual(correctHash, 0, correctHash.Length, protectedPayload.Array, macOffset, eofOffset - macOffset))
{
throw Error.CryptCommon_PayloadInvalid(); // integrity check failure
}
// Step 5: Decipher the ciphertext and return it to the caller.
using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
using (var cryptoTransform = symmetricAlgorithm.CreateDecryptor(decryptionSubkey, iv))
{
var outputStream = new MemoryStream();
using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(protectedPayload.Array, ciphertextOffset, macOffset - ciphertextOffset);
cryptoStream.FlushFinalBlock();
// At this point, outputStream := { plaintext }, and we're done!
return outputStream.ToArray();
}
}
}
finally
{
// nuke since these contain secret material
Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
Array.Clear(decryptionSubkey, 0, decryptionSubkey.Length);
Array.Clear(validationSubkey, 0, validationSubkey.Length);
Array.Clear(derivedKeysBuffer, 0, derivedKeysBuffer.Length);
}
}
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// Homogenize all exceptions to CryptographicException.
throw Error.CryptCommon_GenericError(ex);
}
}
private static void DeriveKeysWithContextHeader(byte[] kdk, ArraySegment<byte> label, byte[] contextHeader, ArraySegment<byte> context, Func<byte[], HashAlgorithm> prfFactory, ArraySegment<byte> output)
{
byte[] combinedContext = new byte[checked(contextHeader.Length + context.Count)];
Buffer.BlockCopy(contextHeader, 0, combinedContext, 0, contextHeader.Length);
Buffer.BlockCopy(context.Array, context.Offset, combinedContext, contextHeader.Length, context.Count);
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(kdk, label, new ArraySegment<byte>(combinedContext), prfFactory, output);
}
public void Dispose()
{
_keyDerivationKey.Dispose();
}
public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
{
plaintext.Validate();
additionalAuthenticatedData.Validate();
try
{
var outputStream = new MemoryStream();
// Step 1: Generate a random key modifier and IV for this operation.
// Both will be equal to the block size of the block cipher algorithm.
byte[] keyModifier = _genRandom.GenRandom(_symmetricAlgorithmSubkeyLengthInBytes);
byte[] iv = _genRandom.GenRandom(_symmetricAlgorithmBlockSizeInBytes);
// Step 2: Copy the key modifier and the IV to the output stream since they'll act as a header.
outputStream.Write(keyModifier, 0, keyModifier.Length);
outputStream.Write(iv, 0, iv.Length);
// At this point, outputStream := { keyModifier || IV }.
// Step 3: Decrypt the KDK, and use it to generate new encryption and HMAC keys.
// We pin all unencrypted keys to limit their exposure via GC relocation.
byte[] decryptedKdk = new byte[_keyDerivationKey.Length];
byte[] encryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
byte[] validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes];
byte[] derivedKeysBuffer = new byte[checked(encryptionSubkey.Length + validationSubkey.Length)];
fixed (byte* __unused__1 = decryptedKdk)
fixed (byte* __unused__2 = encryptionSubkey)
fixed (byte* __unused__3 = validationSubkey)
fixed (byte* __unused__4 = derivedKeysBuffer)
{
try
{
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
DeriveKeysWithContextHeader(
kdk: decryptedKdk,
label: additionalAuthenticatedData,
contextHeader: _contextHeader,
context: new ArraySegment<byte>(keyModifier),
prfFactory: _kdkPrfFactory,
output: new ArraySegment<byte>(derivedKeysBuffer));
Buffer.BlockCopy(derivedKeysBuffer, 0, encryptionSubkey, 0, encryptionSubkey.Length);
Buffer.BlockCopy(derivedKeysBuffer, encryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length);
// Step 4: Perform the encryption operation.
using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
using (var cryptoTransform = symmetricAlgorithm.CreateEncryptor(encryptionSubkey, iv))
using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(plaintext.Array, plaintext.Offset, plaintext.Count);
cryptoStream.FlushFinalBlock();
// At this point, outputStream := { keyModifier || IV || ciphertext }
// Step 5: Calculate the digest over the IV and ciphertext.
// We don't need to calculate the digest over the key modifier since that
// value has already been mixed into the KDF used to generate the MAC key.
using (var validationAlgorithm = CreateValidationAlgorithm(validationSubkey))
{
#if !ASPNETCORE50
// As an optimization, avoid duplicating the underlying buffer if we're on desktop CLR.
byte[] underlyingBuffer = outputStream.GetBuffer();
#else
byte[] underlyingBuffer = outputStream.ToArray();
#endif
byte[] mac = validationAlgorithm.ComputeHash(underlyingBuffer, KEY_MODIFIER_SIZE_IN_BYTES, checked((int)outputStream.Length - KEY_MODIFIER_SIZE_IN_BYTES));
outputStream.Write(mac, 0, mac.Length);
// At this point, outputStream := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
// And we're done!
return outputStream.ToArray();
}
}
}
finally
{
// nuke since these contain secret material
Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
Array.Clear(encryptionSubkey, 0, encryptionSubkey.Length);
Array.Clear(validationSubkey, 0, validationSubkey.Length);
Array.Clear(derivedKeysBuffer, 0, derivedKeysBuffer.Length);
}
}
}
catch (Exception ex) if (!(ex is CryptographicException))
{
// Homogenize all exceptions to CryptographicException.
throw Error.CryptCommon_GenericError(ex);
}
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Managed
{
internal unsafe sealed class ManagedGenRandomImpl : IManagedGenRandom
{
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
public static readonly ManagedGenRandomImpl Instance = new ManagedGenRandomImpl();
private ManagedGenRandomImpl()
{
}
public byte[] GenRandom(int numBytes)
{
byte[] bytes = new byte[numBytes];
_rng.GetBytes(bytes);
return bytes;
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Managed
{
internal static class SymmetricAlgorithmExtensions
{
public static int GetBlockSizeInBytes(this SymmetricAlgorithm symmetricAlgorithm)
{
var blockSizeInBits = symmetricAlgorithm.BlockSize;
CryptoUtil.Assert(blockSizeInBits >= 0 && blockSizeInBits % 8 == 0, "blockSizeInBits >= 0 && blockSizeInBits % 8 == 0");
return blockSizeInBits / 8;
}
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Support for generating random data.
/// </summary>
internal unsafe static class MemoryProtection
{
// from dpapi.h
private const uint CRYPTPROTECTMEMORY_SAME_PROCESS = 0x00;
public static void CryptProtectMemory(SafeHandle pBuffer, uint byteCount)
{
if (!UnsafeNativeMethods.CryptProtectMemory(pBuffer, byteCount, CRYPTPROTECTMEMORY_SAME_PROCESS))
{
UnsafeNativeMethods.ThrowExceptionForLastCrypt32Error();
}
}
public static void CryptUnprotectMemory(byte* pBuffer, uint byteCount)
{
if (!UnsafeNativeMethods.CryptUnprotectMemory(pBuffer, byteCount, CRYPTPROTECTMEMORY_SAME_PROCESS))
{
UnsafeNativeMethods.ThrowExceptionForLastCrypt32Error();
}
}
public static void CryptUnprotectMemory(SafeHandle pBuffer, uint byteCount)
{
if (!UnsafeNativeMethods.CryptUnprotectMemory(pBuffer, byteCount, CRYPTPROTECTMEMORY_SAME_PROCESS))
{
UnsafeNativeMethods.ThrowExceptionForLastCrypt32Error();
}
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Security.DataProtection
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
internal sealed class NotNullAttribute : Attribute
{
}
}

Some files were not shown because too many files have changed in this diff Show More